diff options
Diffstat (limited to 'src')
241 files changed, 12344 insertions, 7855 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index feca199c1..3aa645df9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 2.6) project(minetest) INCLUDE(CheckIncludeFiles) +INCLUDE(CheckLibraryExists) # Add custom SemiDebug build mode set(CMAKE_CXX_FLAGS_SEMIDEBUG "-O1 -g -Wall -Wabi" CACHE STRING @@ -189,6 +190,36 @@ if(ENABLE_CURSES) endif() endif(ENABLE_CURSES) +option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE) +set(USE_POSTGRESQL FALSE) + +if(ENABLE_POSTGRESQL) + find_program(POSTGRESQL_CONFIG_EXECUTABLE pg_config DOC "pg_config") + find_library(POSTGRESQL_LIBRARY pq) + if(POSTGRESQL_CONFIG_EXECUTABLE) + execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} --includedir-server + OUTPUT_VARIABLE POSTGRESQL_SERVER_INCLUDE_DIRS + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} + OUTPUT_VARIABLE POSTGRESQL_CLIENT_INCLUDE_DIRS + OUTPUT_STRIP_TRAILING_WHITESPACE) + # This variable is case sensitive for the cmake PostgreSQL module + set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${POSTGRESQL_SERVER_INCLUDE_DIRS} ${POSTGRESQL_CLIENT_INCLUDE_DIRS}) + endif() + + find_package("PostgreSQL") + + if(POSTGRESQL_FOUND) + set(USE_POSTGRESQL TRUE) + message(STATUS "PostgreSQL backend enabled") + # This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR + message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIR}") + include_directories(${PostgreSQL_INCLUDE_DIR}) + else() + message(STATUS "PostgreSQL not found!") + endif() +endif(ENABLE_POSTGRESQL) + option(ENABLE_LEVELDB "Enable LevelDB backend" TRUE) set(USE_LEVELDB FALSE) @@ -262,9 +293,10 @@ if(WIN32) set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5" CACHE PATH "Zlib include directory") set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.lib" - CACHE FILEPATH "Path to zlibwapi.lib") + CACHE FILEPATH "Path to zlib library (usually zlibwapi.lib)") set(ZLIB_DLL "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.dll" - CACHE FILEPATH "Path to zlibwapi.dll (for installation)") + CACHE FILEPATH "Path to zlib DLL (for installation)") + set(ZLIBWAPI_DLL "" CACHE FILEPATH "Path to zlibwapi DLL") set(IRRLICHT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../irrlicht-1.7.2" CACHE PATH "irrlicht dir") if(USE_FREETYPE) @@ -306,7 +338,10 @@ else() if(APPLE) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) else() - set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS}) + check_library_exists(rt clock_gettime "" HAVE_LIBRT) + if (HAVE_LIBRT) + set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS}) + endif(HAVE_LIBRT) endif(APPLE) # This way Xxf86vm is found on OpenBSD too @@ -346,6 +381,7 @@ add_subdirectory(network) add_subdirectory(script) add_subdirectory(unittest) add_subdirectory(util) +add_subdirectory(irrlicht_changes) set(common_SRCS ban.cpp @@ -361,6 +397,7 @@ set(common_SRCS craftdef.cpp database-dummy.cpp database-leveldb.cpp + database-postgresql.cpp database-redis.cpp database-sqlite3.cpp database.cpp @@ -379,6 +416,7 @@ set(common_SRCS light.cpp log.cpp map.cpp + map_settings_manager.cpp mapblock.cpp mapgen.cpp mapgen_flat.cpp @@ -407,6 +445,8 @@ set(common_SRCS porting.cpp profiler.cpp quicktune.cpp + reflowscan.cpp + remoteplayer.cpp rollback.cpp rollback_interface.cpp serialization.cpp @@ -435,6 +475,7 @@ set(common_SRCS # This gives us the icon and file version information if(WIN32) set(WINRESOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../misc/winresource.rc") + set(MINETEST_EXE_MANIFEST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../misc/minetest.exe.manifest") if(MINGW) if(NOT CMAKE_RC_COMPILER) set(CMAKE_RC_COMPILER "windres.exe") @@ -447,7 +488,7 @@ if(WIN32) DEPENDS ${WINRESOURCE_FILE}) SET(common_SRCS ${common_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o) else(MINGW) # Probably MSVC - set(common_SRCS ${common_SRCS} ${WINRESOURCE_FILE}) + set(common_SRCS ${common_SRCS} ${WINRESOURCE_FILE} ${MINETEST_EXE_MANIFEST_FILE}) endif(MINGW) endif() @@ -462,6 +503,7 @@ set(client_SRCS ${common_SRCS} ${sound_SRCS} ${client_network_SRCS} + ${client_irrlicht_changes_SRCS} camera.cpp client.cpp clientmap.cpp @@ -499,6 +541,7 @@ set(client_SRCS sky.cpp wieldmesh.cpp ${client_SCRIPT_SRCS} + ${UNITTEST_CLIENT_SRCS} ) list(SORT client_SRCS) @@ -522,6 +565,7 @@ include_directories( ${LUA_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${JSON_INCLUDE_DIR} + ${X11_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/script ) @@ -592,6 +636,9 @@ if(BUILD_CLIENT) if (USE_CURSES) target_link_libraries(${PROJECT_NAME} ${CURSES_LIBRARIES}) endif() + if (USE_POSTGRESQL) + target_link_libraries(${PROJECT_NAME} ${POSTGRESQL_LIBRARY}) + endif() if (USE_LEVELDB) target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY}) endif() @@ -622,6 +669,9 @@ if(BUILD_SERVER) if (USE_CURSES) target_link_libraries(${PROJECT_NAME}server ${CURSES_LIBRARIES}) endif() + if (USE_POSTGRESQL) + target_link_libraries(${PROJECT_NAME}server ${POSTGRESQL_LIBRARY}) + endif() if (USE_LEVELDB) target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY}) endif() @@ -639,6 +689,28 @@ if(BUILD_SERVER) endif() endif(BUILD_SERVER) +# Blacklisted locales that don't work. +# see issue #4638 +set(GETTEXT_BLACKLISTED_LOCALES + be + he + ko + ky + zh_CN + zh_TW +) + +option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE) + +if (GETTEXT_FOUND AND APPLY_LOCALE_BLACKLIST) + set(GETTEXT_USED_LOCALES "") + foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + if (NOT ";${GETTEXT_BLACKLISTED_LOCALES};" MATCHES ";${LOCALE};") + list(APPEND GETTEXT_USED_LOCALES ${LOCALE}) + endif() + endforeach() + message(STATUS "Locale blacklist applied; Locales used: ${GETTEXT_USED_LOCALES}") +endif() # Set some optimizations and tweaks @@ -648,9 +720,9 @@ if(MSVC) # Visual Studio # EHa enables SEH exceptions (used for catching segfaults) - set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /Ob2 /Oi /Ot /Oy /GL /FD /MT /GS- /arch:SSE /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0 /TP") + set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /GL /FD /MT /GS- /Zi /arch:SSE /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0 /TP") #set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /NODEFAULTLIB:\"libcmtd.lib\" /NODEFAULTLIB:\"libcmt.lib\"") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /DEBUG /OPT:REF /OPT:ICF") set(CMAKE_CXX_FLAGS_SEMIDEBUG "/MDd /Zi /Ob0 /O1 /RTC1") @@ -678,8 +750,14 @@ else() set(OTHER_FLAGS "${OTHER_FLAGS} -Wsign-compare") endif() + if(WIN32 AND NOT ZLIBWAPI_DLL AND CMAKE_SIZEOF_VOID_P EQUAL 4) + set(OTHER_FLAGS "${OTHER_FLAGS} -DWIN32_NO_ZLIB_WINAPI") + message(WARNING "Defaulting to cdecl for zlib on win32 because ZLIBWAPI_DLL" + " isn't set, ensure that ZLIBWAPI_DLL is set if you want stdcall.") + endif() + if(MINGW) - set(OTHER_FLAGS "-mthreads -fexceptions") + set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads -fexceptions") endif() set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -ffast-math -Wall -pipe -funroll-loops") @@ -751,7 +829,7 @@ if(BUILD_CLIENT) endif() if(USE_GETTEXT) - foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + foreach(LOCALE ${GETTEXT_USED_LOCALES}) set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) set(MO_BUILD_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") install(FILES ${MO_BUILD_PATH} DESTINATION ${MO_DEST_PATH}) @@ -780,7 +858,7 @@ endif() if (USE_GETTEXT) set(MO_FILES) - foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + foreach(LOCALE ${GETTEXT_USED_LOCALES}) 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") diff --git a/src/camera.cpp b/src/camera.cpp index 6893b8cbf..43980db1c 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -103,6 +103,7 @@ Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount"); m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); m_cache_fov = g_settings->getFloat("fov"); + m_cache_zoom_fov = g_settings->getFloat("zoom_fov"); m_cache_view_bobbing = g_settings->getBool("view_bobbing"); m_nametags.clear(); } @@ -387,8 +388,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) m_camera_position = my_cp; - // Get FOV setting - f32 fov_degrees = m_cache_fov; + // Get FOV + f32 fov_degrees; + if (player->getPlayerControl().zoom && m_gamedef->checkLocalPrivilege("zoom")) { + fov_degrees = m_cache_zoom_fov; + } else { + fov_degrees = m_cache_fov; + } fov_degrees = MYMAX(fov_degrees, 10.0); fov_degrees = MYMIN(fov_degrees, 170.0); @@ -466,7 +472,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, { // Start animation m_view_bobbing_state = 1; - m_view_bobbing_speed = MYMIN(speed.getLength(), 40); + m_view_bobbing_speed = MYMIN(speed.getLength(), 70); } else if (m_view_bobbing_state == 1) { @@ -478,13 +484,12 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, void Camera::updateViewingRange() { + f32 viewing_range = g_settings->getFloat("viewing_range"); + m_draw_control.wanted_range = viewing_range; if (m_draw_control.range_all) { m_cameranode->setFarValue(100000.0); return; } - - f32 viewing_range = g_settings->getFloat("viewing_range"); - m_draw_control.wanted_range = viewing_range; m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); } @@ -546,7 +551,7 @@ void Camera::drawNametags() // shadow can remain. continue; } - v3f pos = nametag->parent_node->getPosition() + v3f(0.0, 1.1 * BS, 0.0); + v3f pos = nametag->parent_node->getAbsolutePosition() + v3f(0.0, 1.1 * BS, 0.0); f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; trans.multiplyWith1x4Matrix(transformed_pos); if (transformed_pos[3] > 0) { diff --git a/src/camera.h b/src/camera.h index ce46c3190..cb0e9686d 100644 --- a/src/camera.h +++ b/src/camera.h @@ -231,6 +231,7 @@ private: f32 m_cache_fall_bobbing_amount; f32 m_cache_view_bobbing_amount; f32 m_cache_fov; + f32 m_cache_zoom_fov; bool m_cache_view_bobbing; std::list<Nametag *> m_nametags; diff --git a/src/cavegen.cpp b/src/cavegen.cpp index b8abfbca5..bb6aa25a6 100644 --- a/src/cavegen.cpp +++ b/src/cavegen.cpp @@ -23,46 +23,183 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen_v5.h" #include "mapgen_v6.h" #include "mapgen_v7.h" +#include "mg_biome.h" #include "cavegen.h" -NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); +static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); -///////////////////////////////////////// Caves V5 +//// +//// CavesNoiseIntersection +//// +CavesNoiseIntersection::CavesNoiseIntersection( + INodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize, + NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width) +{ + assert(nodedef); + assert(biomemgr); + + m_ndef = nodedef; + m_bmgr = biomemgr; + + m_csize = chunksize; + m_cave_width = cave_width; -CaveV5::CaveV5(Mapgen *mg, PseudoRandom *ps) + m_ystride = m_csize.X; + m_zstride_1d = m_csize.X * (m_csize.Y + 1); + + // Noises are created using 1-down overgeneration + // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for + // re-carving the solid overtop placed for blocking sunlight + noise_cave1 = new Noise(np_cave1, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); + noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); +} + + +CavesNoiseIntersection::~CavesNoiseIntersection() { - this->mg = mg; - this->vm = mg->vm; - this->ndef = mg->ndef; - this->water_level = mg->water_level; - this->ps = ps; - c_water_source = ndef->getId("mapgen_water_source"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_ice = ndef->getId("mapgen_ice"); - this->np_caveliquids = &nparams_caveliquids; - this->ystride = mg->csize.X; - - if (c_ice == CONTENT_IGNORE) - c_ice = CONTENT_AIR; + delete noise_cave1; + delete noise_cave2; +} - 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)); +void CavesNoiseIntersection::generateCaves(MMVManip *vm, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + assert(vm); + assert(biomemap); + + noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + + v3s16 em = vm->m_area.getExtent(); + u32 index2d = 0; + + for (s16 z = nmin.Z; z <= nmax.Z; z++) + for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { + bool column_is_open = false; // Is column open to overground + bool is_under_river = false; // Is column under river water + bool is_tunnel = false; // Is tunnel or tunnel floor + u32 vi = vm->m_area.index(x, nmax.Y, z); + u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride + + (x - nmin.X); + // Biome of column + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]); + + // Don't excavate the overgenerated stone at nmax.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + // This 'roof' is removed when the mapchunk above is generated. + for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, + index3d -= m_ystride, + vm->m_area.add_y(em, vi, -1)) { + + content_t c = vm->m_data[vi].getContent(); + if (c == CONTENT_AIR || c == biome->c_water_top || + c == biome->c_water) { + column_is_open = true; + continue; + } else if (c == biome->c_river_water) { + column_is_open = true; + is_under_river = true; + continue; + } + // Ground + float d1 = contour(noise_cave1->result[index3d]); + float d2 = contour(noise_cave2->result[index3d]); + + if (d1 * d2 > m_cave_width && m_ndef->get(c).is_ground_content) { + // In tunnel and ground content, excavate + vm->m_data[vi] = MapNode(CONTENT_AIR); + is_tunnel = true; + } else { + // Not in tunnel or not ground content + if (is_tunnel && column_is_open && + (c == biome->c_filler || c == biome->c_stone)) { + // Tunnel entrance floor + if (is_under_river) + vm->m_data[vi] = MapNode(biome->c_riverbed); + else + vm->m_data[vi] = MapNode(biome->c_top); + } - large_cave_is_flat = (ps->range(0, 1) == 0); + column_is_open = false; + is_tunnel = false; + } + } + } } -void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) +//// +//// CavesRandomWalk +//// + +CavesRandomWalk::CavesRandomWalk( + INodeDefManager *ndef, + GenerateNotifier *gennotify, + s32 seed, + int water_level, + content_t water_source, + content_t lava_source) { - node_min = nmin; - node_max = nmax; + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; + this->seed = seed; + this->water_level = water_level; + this->np_caveliquids = &nparams_caveliquids; + this->lava_depth = DEFAULT_LAVA_DEPTH; + + c_water_source = water_source; + if (c_water_source == CONTENT_IGNORE) + c_water_source = ndef->getId("mapgen_water_source"); + if (c_water_source == CONTENT_IGNORE) + c_water_source = CONTENT_AIR; + + c_lava_source = lava_source; + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = ndef->getId("mapgen_lava_source"); + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = CONTENT_AIR; +} + + +void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, + PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap) +{ + assert(vm); + assert(ps); + + this->vm = vm; + this->ps = ps; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; + this->large_cave = is_large_cave; + + this->ystride = nmax.X - nmin.X + 1; + + // Set initial parameters from randomness + int dswitchint = ps->range(1, 14); + flooded = ps->range(1, 2) == 2; + + if (large_cave) { + part_max_length_rs = ps->range(2, 4); + 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); + } + + large_cave_is_flat = (ps->range(0, 1) == 0); + main_direction = v3f(0, 0, 0); // Allowed route area size in nodes @@ -72,10 +209,10 @@ void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) // Allow a bit more //(this should be more than the maximum radius of the tunnel) - s16 insure = 10; + const 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; + 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 @@ -84,13 +221,15 @@ void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) // Limit maximum to area route_y_max = rangelim(route_y_max, 0, ar.Y - 1); - s16 min = 0; + if (large_cave) { + s16 minpos = 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; + minpos = 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); + route_y_min = ps->range(minpos, minpos + 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; @@ -99,30 +238,42 @@ void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) 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 - ); + orp.Z = (float)(ps->next() % ar.Z) + 0.5f; + orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f; + orp.X = (float)(ps->next() % ar.X) + 0.5f; // 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); + if (gennotify) { + 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; + 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); + if (gennotify) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + gennotify->addEvent(notifytype, abs_pos); + } } -void CaveV5::makeTunnel(bool dirswitch) +void CavesRandomWalk::makeTunnel(bool dirswitch) { + if (dirswitch && !large_cave) { + main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10; + main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30; + main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10; + + main_direction *= (float)ps->range(0, 10) / 10; + } + // Randomize size s16 min_d = min_tunnel_diameter; s16 max_d = max_tunnel_diameter; @@ -130,47 +281,38 @@ void CaveV5::makeTunnel(bool dirswitch) 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 - ); + 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 + ); + } 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 - ); + if (!large_cave && ps->range(0, 12) == 0) { + vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y; + vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2; + } else { + vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2; + vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 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) * 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) * ystride + (p.X - node_min.X); - s16 h = mg->heightmap[index]; - if (h < p.Y) - return; - } else if (p.Y > water_level) { + v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2; + v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1; + if (isPosAboveSurface(p1) || isPosAboveSurface(p2)) return; - } vec += main_direction; @@ -193,21 +335,21 @@ void CaveV5::makeTunnel(bool dirswitch) vec = rp - orp; float veclen = vec.getLength(); - if (veclen < 0.05) - veclen = 1.0; + if (veclen < 0.05f) + veclen = 1.0f; // 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) + for (float f = 0.f; f < 1.0f; f += 1.0f / veclen) carveRoute(vec, f, randomize_xz); orp = rp; } -void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz) +void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) { MapNode airnode(CONTENT_AIR); MapNode waternode(c_water_source); @@ -217,22 +359,24 @@ void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz) startp += of; float nval = NoisePerlin3D(np_caveliquids, startp.X, - startp.Y, startp.Z, mg->seed); - MapNode liquidnode = (nval < 0.40 && node_max.Y < MGV5_LAVA_DEPTH) ? + startp.Y, startp.Z, seed); + MapNode liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ? lavanode : waternode; v3f fp = orp + vec * f; - fp.X += 0.1 * ps->range(-10, 10); - fp.Z += 0.1 * ps->range(-10, 10); + fp.X += 0.1f * ps->range(-10, 10); + fp.Z += 0.1f * ps->range(-10, 10); v3s16 cp(fp.X, fp.Y, fp.Z); - s16 d0 = -rs/2; + s16 d0 = -rs / 2; s16 d1 = d0 + rs; if (randomize_xz) { d0 += ps->range(-1, 1); d1 += ps->range(-1, 1); } + bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2; + 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++) { @@ -241,6 +385,10 @@ void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz) 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) @@ -258,63 +406,105 @@ void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz) 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 + 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; + vm->m_data[i] = airnode; + vm->m_flags[i] |= VMANIP_FLAG_CAVE; + } } } } } -///////////////////////////////////////// Caves V6 +inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p) +{ + if (heightmap != NULL && + 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) * ystride + (p.X - node_min.X); + if (heightmap[index] < p.Y) + return true; + } else if (p.Y > water_level) { + return true; + } + + return false; +} + + +//// +//// CavesV6 +//// + +CavesV6::CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify, + int water_level, content_t water_source, content_t lava_source) +{ + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; + this->water_level = water_level; + + c_water_source = water_source; + if (c_water_source == CONTENT_IGNORE) + c_water_source = ndef->getId("mapgen_water_source"); + if (c_water_source == CONTENT_IGNORE) + c_water_source = CONTENT_AIR; + + c_lava_source = lava_source; + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = ndef->getId("mapgen_lava_source"); + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = CONTENT_AIR; +} -CaveV6::CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool is_large_cave) +void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, + PseudoRandom *ps, PseudoRandom *ps2, + bool is_large_cave, int max_stone_height, s16 *heightmap) { - 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; + assert(vm); + assert(ps); + assert(ps2); + + this->vm = vm; + this->ps = ps; + this->ps2 = ps2; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; + this->large_cave = is_large_cave; + + this->ystride = nmax.X - nmin.X + 1; + // Set initial parameters from randomness min_tunnel_diameter = 2; max_tunnel_diameter = ps->range(2, 6); - dswitchint = ps->range(1, 14); - flooded = true; - + int dswitchint = ps->range(1, 14); 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)); + 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)); + part_max_length_rs = ps->range(2, 9); + tunnel_routepoints = ps->range(10, ps->range(15, 30)); } + large_cave_is_flat = (ps->range(0, 1) == 0); - large_cave_is_flat = (ps->range(0,1) == 0); -} - - -void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) -{ - node_min = nmin; - node_max = nmax; - max_stone_y = max_stone_height; main_direction = v3f(0, 0, 0); // Allowed route area size in nodes @@ -325,67 +515,68 @@ void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) // Allow a bit more //(this should be more than the maximum radius of the tunnel) const s16 max_spread_amount = MAP_BLOCKSIZE; - s16 insure = 10; + const s16 insure = 10; s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1); - ar += v3s16(1,0,1) * more * 2; - of -= v3s16(1,0,1) * more; + 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; + route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7; // Limit maximum to area route_y_max = rangelim(route_y_max, 0, ar.Y - 1); if (large_cave) { - s16 min = 0; + s16 minpos = 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; + minpos = 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 = ps->range(minpos, minpos + 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); + 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 - ); + orp.Z = (float)(ps->next() % ar.Z) + 0.5f; + orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f; + orp.X = (float)(ps->next() % ar.X) + 0.5f; // 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; - mg->gennotify.addEvent(notifytype, abs_pos); + if (gennotify != NULL) { + 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; + 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 = large_cave ? - GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; - mg->gennotify.addEvent(notifytype, abs_pos); + if (gennotify != NULL) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + gennotify->addEvent(notifytype, abs_pos); + } } -void CaveV6::makeTunnel(bool dirswitch) +void CavesV6::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.Z = ((float)(ps->next() % 20) - (float)10) / 10; + main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30; + main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10; + main_direction *= (float)ps->range(0, 10) / 10; } @@ -410,54 +601,30 @@ void CaveV6::makeTunnel(bool dirswitch) ); } - v3f vec( - (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 - ); + v3f vec; + vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2; + vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2; // Jump downward sometimes if (!large_cave && ps->range(0, 12) == 0) { - vec = v3f( - (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, - (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y, - (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 - ); + vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y; + vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2; } - // Do not make caves that are entirely above ground, to fix - // shadow bugs caused by overgenerated large caves. + // 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 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2; + v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1; - 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; + // If startpoint and endpoint are above ground, disable placement of nodes + // in carveRoute while still running all PseudoRandom calls to ensure caves + // are consistent with existing worlds. + bool tunnel_above_ground = + p1.Y > getSurfaceFromHeightmap(p1) && + p2.Y > getSurfaceFromHeightmap(p2); vec += main_direction; @@ -481,21 +648,22 @@ void CaveV6::makeTunnel(bool dirswitch) float veclen = vec.getLength(); // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE - if (veclen < 0.05) - veclen = 1.0; + if (veclen < 0.05f) + veclen = 1.0f; // Every second section is rough bool randomize_xz = (ps2->range(1, 2) == 1); // Carve routes - for (float f = 0; f < 1.0; f += 1.0 / veclen) + for (float f = 0.f; f < 1.0f; f += 1.0f / veclen) carveRoute(vec, f, randomize_xz, tunnel_above_ground); orp = rp; } -void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground) +void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, + bool tunnel_above_ground) { MapNode airnode(CONTENT_AIR); MapNode waternode(c_water_source); @@ -505,11 +673,11 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_g startp += of; v3f fp = orp + vec * f; - fp.X += 0.1 * ps->range(-10, 10); - fp.Z += 0.1 * ps->range(-10, 10); + fp.X += 0.1f * ps->range(-10, 10); + fp.Z += 0.1f * ps->range(-10, 10); v3s16 cp(fp.X, fp.Y, fp.Z); - s16 d0 = -rs/2; + s16 d0 = -rs / 2; s16 d1 = d0 + rs; if (randomize_xz) { d0 += ps->range(-1, 1); @@ -546,13 +714,10 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_g 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 - 2) ? - lavanode : airnode; + if (full_ymin < water_level && full_ymax > water_level) { + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + } else if (full_ymax < water_level) { + vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode; } else { vm->m_data[i] = airnode; } @@ -569,246 +734,14 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_g } -///////////////////////////////////////// Caves V7 - - -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; - 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 CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) +inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p) { - node_min = nmin; - node_max = nmax; - max_stone_y = max_stone_height; - 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 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; - 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->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 && + if (heightmap != NULL && + 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; - } - - 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 CaveV7::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 && node_max.Y < MGV7_LAVA_DEPTH) ? - 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; - } - } + u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X); + return heightmap[index]; + } else { + return water_level; } } diff --git a/src/cavegen.h b/src/cavegen.h index a1124711b..2bf503d47 100644 --- a/src/cavegen.h +++ b/src/cavegen.h @@ -21,69 +21,79 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CAVEGEN_HEADER #define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1 -#define MGV5_LAVA_DEPTH -256 -#define MGV7_LAVA_DEPTH -256 +#define DEFAULT_LAVA_DEPTH (-256) -class MapgenV5; -class MapgenV6; -class MapgenV7; +class GenerateNotifier; -class CaveV5 { -public: - Mapgen *mg; - MMVManip *vm; - INodeDefManager *ndef; +/* + CavesNoiseIntersection is a cave digging algorithm that carves smooth, + web-like, continuous tunnels at points where the density of the intersection + between two separate 3d noises is above a certain value. This value, + cave_width, can be modified to set the effective width of these tunnels. - NoiseParams *np_caveliquids; + This algorithm is relatively heavyweight, taking ~80ms to generate an + 80x80x80 chunk of map on a modern processor. Use sparingly! - 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; + TODO(hmmmm): Remove dependency on biomes + TODO(hmmmm): Find alternative to overgeneration as solution for sunlight issue +*/ +class CavesNoiseIntersection { +public: + CavesNoiseIntersection(INodeDefManager *nodedef, BiomeManager *biomemgr, + v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2, + s32 seed, float cave_width); + ~CavesNoiseIntersection(); - s16 max_stone_y; - v3s16 node_min; - v3s16 node_max; + void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap); - 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; +private: + INodeDefManager *m_ndef; + BiomeManager *m_bmgr; - s16 route_y_min; - s16 route_y_max; + // configurable parameters + v3s16 m_csize; + float m_cave_width; - PseudoRandom *ps; + // intermediate state variables + u16 m_ystride; + u16 m_zstride_1d; - content_t c_water_source; - content_t c_lava_source; - content_t c_ice; + Noise *noise_cave1; + Noise *noise_cave2; +}; - int water_level; - int ystride; +/* + CavesRandomWalk is an implementation of a cave-digging algorithm that + operates on the principle of a "random walk" to approximate the stochiastic + activity of cavern development. - CaveV5() {} - CaveV5(Mapgen *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); -}; + In summary, this algorithm works by carving a randomly sized tunnel in a + random direction a random amount of times, randomly varying in width. + All randomness here is uniformly distributed; alternative distributions have + not yet been implemented. -class CaveV6 { + This algorithm is very fast, executing in less than 1ms on average for an + 80x80x80 chunk of map on a modern processor. +*/ +class CavesRandomWalk { public: - MapgenV6 *mg; MMVManip *vm; INodeDefManager *ndef; + GenerateNotifier *gennotify; + s16 *heightmap; + + // configurable parameters + s32 seed; + int water_level; + int lava_depth; + NoiseParams *np_caveliquids; + + // intermediate state variables + u16 ystride; s16 min_tunnel_diameter; s16 max_tunnel_diameter; u16 tunnel_routepoints; - int dswitchint; int part_max_length_rs; bool large_cave; @@ -104,38 +114,70 @@ public: s16 route_y_max; PseudoRandom *ps; - PseudoRandom *ps2; content_t c_water_source; content_t c_lava_source; - int water_level; - - CaveV6() {} - CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool large_cave); - void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + // ndef is a mandatory parameter. + // If gennotify is NULL, generation events are not logged. + CavesRandomWalk(INodeDefManager *ndef, + GenerateNotifier *gennotify = NULL, + s32 seed = 0, + int water_level = 1, + content_t water_source = CONTENT_IGNORE, + content_t lava_source = CONTENT_IGNORE); + + // vm and ps are mandatory parameters. + // If heightmap is NULL, the surface level at all points is assumed to + // be water_level. + void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps, + bool is_large_cave, int max_stone_height, s16 *heightmap); + +private: void makeTunnel(bool dirswitch); - void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground); + void carveRoute(v3f vec, float f, bool randomize_xz); + + inline bool isPosAboveSurface(v3s16 p); }; -class CaveV7 { +/* + CavesV6 is the original version of caves used with Mapgen V6. + + Though it uses the same fundamental algorithm as CavesRandomWalk, it is made + separate to preserve the exact sequence of PseudoRandom calls - any change + to this ordering results in the output being radically different. + Because caves in Mapgen V6 are responsible for a large portion of the basic + terrain shape, modifying this will break our contract of reverse + compatibility for a 'stable' mapgen such as V6. + + tl;dr, + *** DO NOT TOUCH THIS CLASS UNLESS YOU KNOW WHAT YOU ARE DOING *** +*/ +class CavesV6 { public: - MapgenV7 *mg; MMVManip *vm; INodeDefManager *ndef; + GenerateNotifier *gennotify; + PseudoRandom *ps; + PseudoRandom *ps2; - NoiseParams *np_caveliquids; + // configurable parameters + s16 *heightmap; + content_t c_water_source; + content_t c_lava_source; + int water_level; + + // intermediate state variables + u16 ystride; s16 min_tunnel_diameter; s16 max_tunnel_diameter; u16 tunnel_routepoints; - int dswitchint; int part_max_length_rs; + bool large_cave; bool large_cave_is_flat; - bool flooded; - s16 max_stone_y; v3s16 node_min; v3s16 node_max; @@ -148,19 +190,26 @@ public: 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; - - CaveV7() {} - CaveV7(MapgenV7 *mg, PseudoRandom *ps); - void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + // ndef is a mandatory parameter. + // If gennotify is NULL, generation events are not logged. + CavesV6(INodeDefManager *ndef, + GenerateNotifier *gennotify = NULL, + int water_level = 1, + content_t water_source = CONTENT_IGNORE, + content_t lava_source = CONTENT_IGNORE); + + // vm, ps, and ps2 are mandatory parameters. + // If heightmap is NULL, the surface level at all points is assumed to + // be water_level. + void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, + PseudoRandom *ps, PseudoRandom *ps2, + bool is_large_cave, int max_stone_height, s16 *heightmap = NULL); + +private: 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); + + inline s16 getSurfaceFromHeightmap(v3s16 p); }; #endif diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp index 2342eb748..c2d37c6c0 100644 --- a/src/cguittfont/CGUITTFont.cpp +++ b/src/cguittfont/CGUITTFont.cpp @@ -1,6 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman + Copyright (c) 2016 NathanaĆ«l Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -545,6 +546,13 @@ void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hintin void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip) { + draw(EnrichedString(std::wstring(text.c_str()), color), position, color, hcenter, vcenter, clip); +} + +void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip) +{ + std::vector<video::SColor> colors = text.getColors(); + if (!Driver) return; @@ -572,7 +580,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position } // Convert to a unicode string. - core::ustring utext(text); + core::ustring utext = text.getString(); // Set up our render map. core::map<u32, CGUITTGlyphPage*> Render_Map; @@ -581,6 +589,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position u32 n; uchar32_t previousChar = 0; core::ustring::const_iterator iter(utext); + std::vector<video::SColor> applied_colors; while (!iter.atEnd()) { uchar32_t currentChar = *iter; @@ -590,7 +599,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position if (currentChar == L'\r') // Mac or Windows breaks { lineBreak = true; - if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. currentChar = *(++iter); } else if (currentChar == (uchar32_t)'\n') // Unix breaks @@ -627,6 +636,9 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); page->render_source_rects.push_back(glyph.source_rect); Render_Map.set(glyph.glyph_page, page); + u32 current_color = iter.getPos(); + if (current_color < colors.size()) + applied_colors.push_back(colors[current_color]); } offset.X += getWidthFromCharacter(currentChar); @@ -645,8 +657,6 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position CGUITTGlyphPage* page = n->getValue(); - if (!use_transparency) color.color |= 0xff000000; - if (shadow_offset) { for (size_t i = 0; i < page->render_positions.size(); ++i) page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset); @@ -654,7 +664,17 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position for (size_t i = 0; i < page->render_positions.size(); ++i) page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset); } - Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true); + for (size_t i = 0; i < page->render_positions.size(); ++i) { + irr::video::SColor col; + if (!applied_colors.empty()) { + col = applied_colors[i < applied_colors.size() ? i : 0]; + } else { + col = irr::video::SColor(255, 255, 255, 255); + } + if (!use_transparency) + col.color |= 0xff000000; + Driver->draw2DImage(page->texture, page->render_positions[i], page->render_source_rects[i], clip, col, true); + } } } diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h index e24d8f18b..0aa540c5c 100644 --- a/src/cguittfont/CGUITTFont.h +++ b/src/cguittfont/CGUITTFont.h @@ -1,6 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman + Copyright (c) 2016 NathanaĆ«l Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -33,6 +34,8 @@ #include <irrlicht.h> #include <ft2build.h> +#include <vector> +#include "util/enriched_string.h" #include FT_FREETYPE_H namespace irr @@ -258,6 +261,10 @@ namespace gui virtual void draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter=false, bool vcenter=false, const core::rect<s32>* clip=0); + + virtual void draw(const EnrichedString& text, const core::rect<s32>& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect<s32>* clip=0); //! Returns the dimension of a character produced by this font. virtual core::dimension2d<u32> getCharDimension(const wchar_t ch) const; diff --git a/src/chat.cpp b/src/chat.cpp index cebe31225..46555b3dc 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chat.h" #include "debug.h" +#include "config.h" #include "util/strfnd.h" #include <cctype> #include <sstream> @@ -251,8 +252,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, u32 hanging_indentation = 0; // Format the sender name and produce fragments - if (!line.name.empty()) - { + if (!line.name.empty()) { temp_frag.text = L"<"; temp_frag.column = 0; //temp_frag.bold = 0; @@ -267,22 +267,20 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, next_frags.push_back(temp_frag); } + std::wstring name_sanitized = line.name.c_str(); + // Choose an indentation level - if (line.name.empty()) - { + if (line.name.empty()) { // Server messages hanging_indentation = 0; - } - else if (line.name.size() + 3 <= cols/2) - { + } else if (name_sanitized.size() + 3 <= cols/2) { // Names shorter than about half the console width hanging_indentation = line.name.size() + 3; - } - else - { + } else { // Very long names hanging_indentation = 2; } + //EnrichedString line_text(line.text); next_line.first = true; bool text_processing = false; @@ -338,7 +336,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, while (frag_length < remaining_in_input && frag_length < remaining_in_output) { - if (isspace(line.text[in_pos + frag_length])) + if (isspace(line.text.getString()[in_pos + frag_length])) space_pos = frag_length; ++frag_length; } @@ -686,9 +684,6 @@ ChatBackend::~ChatBackend() void ChatBackend::addMessage(std::wstring name, std::wstring text) { - name = unescape_enriched(name); - text = unescape_enriched(text); - // Note: A message may consist of multiple lines, for example the MOTD. WStrfnd fnd(text); while (!fnd.at_end()) @@ -732,19 +727,22 @@ ChatBuffer& ChatBackend::getRecentBuffer() return m_recent_buffer; } -std::wstring ChatBackend::getRecentChat() +EnrichedString ChatBackend::getRecentChat() { - std::wostringstream stream; + EnrichedString result; for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) { const ChatLine& line = m_recent_buffer.getLine(i); if (i != 0) - stream << L"\n"; - if (!line.name.empty()) - stream << L"<" << line.name << L"> "; - stream << line.text; + result += L"\n"; + if (!line.name.empty()) { + result += L"<"; + result += line.name; + result += L"> "; + } + result += line.text; } - return stream.str(); + return result; } ChatPrompt& ChatBackend::getPrompt() diff --git a/src/chat.h b/src/chat.h index db4146d35..11061fd39 100644 --- a/src/chat.h +++ b/src/chat.h @@ -20,11 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef CHAT_HEADER #define CHAT_HEADER -#include "irrlichttypes.h" #include <string> #include <vector> #include <list> +#include "irrlichttypes.h" +#include "util/enriched_string.h" + // Chat console related classes struct ChatLine @@ -32,9 +34,9 @@ struct ChatLine // age in seconds f32 age; // name of sending player, or empty if sent by server - std::wstring name; + EnrichedString name; // message text - std::wstring text; + EnrichedString text; ChatLine(std::wstring a_name, std::wstring a_text): age(0.0), @@ -42,12 +44,19 @@ struct ChatLine text(a_text) { } + + ChatLine(EnrichedString a_name, EnrichedString a_text): + age(0.0), + name(a_name), + text(a_text) + { + } }; struct ChatFormattedFragment { // text string - std::wstring text; + EnrichedString text; // starting column u32 column; // formatting @@ -260,7 +269,7 @@ public: // Get the recent messages buffer ChatBuffer& getRecentBuffer(); // Concatenate all recent messages - std::wstring getRecentChat(); + EnrichedString getRecentChat(); // Get the console prompt ChatPrompt& getPrompt(); diff --git a/src/client.cpp b/src/client.cpp index 4ffcec6ba..5476aad0e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <algorithm> #include <sstream> +#include <cmath> #include <IFileSystem.h> #include "threading/mutex_auto_lock.h" #include "util/auth.h" @@ -257,7 +258,7 @@ Client::Client( m_localdb(NULL) { // Add local player - m_env.addPlayer(new LocalPlayer(this, playername)); + m_env.setLocalPlayer(new LocalPlayer(this, playername)); m_mapper = new Mapper(device, this); m_cache_save_interval = g_settings->getU16("server_map_save_interval"); @@ -303,7 +304,7 @@ Client::~Client() delete m_inventory_from_server; // Delete detached inventories - for (std::map<std::string, Inventory*>::iterator + for (UNORDERED_MAP<std::string, Inventory*>::iterator i = m_detached_inventories.begin(); i != m_detached_inventories.end(); ++i) { delete i->second; @@ -383,7 +384,7 @@ void Client::step(float dtime) if(counter <= 0.0) { counter = 2.0; - Player *myplayer = m_env.getLocalPlayer(); + LocalPlayer *myplayer = m_env.getLocalPlayer(); FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); u16 proto_version_min = g_settings->getFlag("send_pre_v25_init") ? @@ -613,7 +614,7 @@ void Client::step(float dtime) { // Do this every <interval> seconds after TOCLIENT_INVENTORY // Reset the locally changed inventory to the authoritative inventory - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); player->inventory = *m_inventory_from_server; m_inventory_updated = true; } @@ -623,10 +624,8 @@ void Client::step(float dtime) Update positions of sounds attached to objects */ { - for(std::map<int, u16>::iterator - i = m_sounds_to_objects.begin(); - i != m_sounds_to_objects.end(); ++i) - { + for(UNORDERED_MAP<int, u16>::iterator i = m_sounds_to_objects.begin(); + i != m_sounds_to_objects.end(); ++i) { int client_id = i->first; u16 object_id = i->second; ClientActiveObject *cao = m_env.getActiveObject(object_id); @@ -645,8 +644,7 @@ void Client::step(float dtime) m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them std::vector<s32> removed_server_ids; - for(std::map<s32, int>::iterator - i = m_sounds_server_to_client.begin(); + for(UNORDERED_MAP<s32, int>::iterator i = m_sounds_server_to_client.begin(); i != m_sounds_server_to_client.end();) { s32 server_id = i->first; int client_id = i->second; @@ -820,7 +818,7 @@ void Client::initLocalMapSaving(const Address &address, const std::string world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "server_" - + hostname + "_" + to_string(address.getPort()); + + hostname + "_" + std::to_string(address.getPort()); fs::CreateAllDirs(world_path); @@ -932,6 +930,36 @@ void Client::Send(NetworkPacket* pkt) serverCommandFactoryTable[pkt->getCommand()].reliable); } +// Will fill up 12 + 12 + 4 + 4 + 4 bytes +void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt) +{ + v3f pf = myplayer->getPosition() * 100; + v3f sf = myplayer->getSpeed() * 100; + s32 pitch = myplayer->getPitch() * 100; + s32 yaw = myplayer->getYaw() * 100; + u32 keyPressed = myplayer->keyPressed; + // scaled by 80, so that pi can fit into a u8 + u8 fov = clientMap->getCameraFov() * 80; + u8 wanted_range = MYMIN(255, + std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE)); + + v3s32 position(pf.X, pf.Y, pf.Z); + v3s32 speed(sf.X, sf.Y, sf.Z); + + /* + Format: + [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 + [12+12+4+4+4] u8 fov*80 + [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) + */ + *pkt << position << speed << pitch << yaw << keyPressed; + *pkt << fov << wanted_range; +} + void Client::interact(u8 action, const PointedThing& pointed) { if(m_state != LC_Ready) { @@ -941,12 +969,17 @@ void Client::interact(u8 action, const PointedThing& pointed) return; } + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if (myplayer == NULL) + return; + /* [0] u16 command [2] u8 action [3] u16 item - [5] u32 length of the next item + [5] u32 length of the next item (plen) [9] serialized PointedThing + [9 + plen] player position information actions: 0: start digging (from undersurface) or use 1: stop digging (all parameters ignored) @@ -966,6 +999,8 @@ void Client::interact(u8 action, const PointedThing& pointed) pkt.putLongString(tmp_os.str()); + writePlayerPos(myplayer, &m_env.getClientMap(), &pkt); + Send(&pkt); } @@ -1194,7 +1229,7 @@ void Client::sendChatMessage(const std::wstring &message) void Client::sendChangePassword(const std::string &oldpassword, const std::string &newpassword) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); if (player == NULL) return; @@ -1268,19 +1303,30 @@ void Client::sendPlayerPos() if(myplayer == NULL) return; + ClientMap &map = m_env.getClientMap(); + + u8 camera_fov = map.getCameraFov(); + u8 wanted_range = map.getControl().wanted_range; + // Save bandwidth by only updating position when something changed if(myplayer->last_position == myplayer->getPosition() && - myplayer->last_speed == myplayer->getSpeed() && - myplayer->last_pitch == myplayer->getPitch() && - myplayer->last_yaw == myplayer->getYaw() && - myplayer->last_keyPressed == myplayer->keyPressed) + myplayer->last_speed == myplayer->getSpeed() && + myplayer->last_pitch == myplayer->getPitch() && + myplayer->last_yaw == myplayer->getYaw() && + myplayer->last_keyPressed == myplayer->keyPressed && + myplayer->last_camera_fov == camera_fov && + myplayer->last_wanted_range == wanted_range) return; - myplayer->last_position = myplayer->getPosition(); - myplayer->last_speed = myplayer->getSpeed(); - myplayer->last_pitch = myplayer->getPitch(); - myplayer->last_yaw = myplayer->getYaw(); - myplayer->last_keyPressed = myplayer->keyPressed; + myplayer->last_position = myplayer->getPosition(); + myplayer->last_speed = myplayer->getSpeed(); + myplayer->last_pitch = myplayer->getPitch(); + myplayer->last_yaw = myplayer->getYaw(); + myplayer->last_keyPressed = myplayer->keyPressed; + myplayer->last_camera_fov = camera_fov; + myplayer->last_wanted_range = wanted_range; + + //infostream << "Sending Player Position information" << std::endl; u16 our_peer_id; { @@ -1294,33 +1340,16 @@ void Client::sendPlayerPos() assert(myplayer->peer_id == our_peer_id); - v3f pf = myplayer->getPosition(); - v3f sf = myplayer->getSpeed(); - s32 pitch = myplayer->getPitch() * 100; - s32 yaw = myplayer->getYaw() * 100; - u32 keyPressed = myplayer->keyPressed; + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1); - v3s32 position(pf.X*100, pf.Y*100, pf.Z*100); - v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100); - /* - Format: - [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 - */ - - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4); - - pkt << position << speed << pitch << yaw << keyPressed; + writePlayerPos(myplayer, &map, &pkt); Send(&pkt); } void Client::sendPlayerItem(u16 item) { - Player *myplayer = m_env.getLocalPlayer(); + LocalPlayer *myplayer = m_env.getLocalPlayer(); if(myplayer == NULL) return; @@ -1401,7 +1430,7 @@ bool Client::getLocalInventoryUpdated() // Copies the inventory of the local player to parameter void Client::getLocalInventory(Inventory &dst) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); dst = player->inventory; } @@ -1414,15 +1443,16 @@ Inventory* Client::getInventory(const InventoryLocation &loc) break; case InventoryLocation::CURRENT_PLAYER: { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); return &player->inventory; } break; case InventoryLocation::PLAYER: { - Player *player = m_env.getPlayer(loc.name.c_str()); - if(!player) + // Check if we are working with local player inventory + LocalPlayer *player = m_env.getLocalPlayer(); + if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) return NULL; return &player->inventory; } @@ -1437,7 +1467,7 @@ Inventory* Client::getInventory(const InventoryLocation &loc) break; case InventoryLocation::DETACHED: { - if(m_detached_inventories.count(loc.name) == 0) + if (m_detached_inventories.count(loc.name) == 0) return NULL; return m_detached_inventories[loc.name]; } @@ -1503,11 +1533,6 @@ ClientActiveObject * Client::getSelectedActiveObject( return NULL; } -std::list<std::string> Client::getConnectedPlayerNames() -{ - return m_env.getPlayerNames(); -} - float Client::getAnimationTime() { return m_animation_time; @@ -1540,18 +1565,11 @@ void Client::setCrack(int level, v3s16 pos) u16 Client::getHP() { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); return player->hp; } -u16 Client::getBreath() -{ - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); - return player->getBreath(); -} - bool Client::getChatMessage(std::wstring &message) { if(m_chat_queue.size() == 0) @@ -1674,7 +1692,7 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur ClientEvent Client::getClientEvent() { ClientEvent event; - if(m_client_event_queue.size() == 0) { + if (m_client_event_queue.empty()) { event.type = CE_NONE; } else { diff --git a/src/client.h b/src/client.h index cdadb9d3e..9f5bda059 100644 --- a/src/client.h +++ b/src/client.h @@ -182,6 +182,7 @@ struct ClientEvent f32 expirationtime; f32 size; bool collisiondetection; + bool collision_removal; bool vertical; std::string *texture; } spawn_particle; @@ -199,6 +200,8 @@ struct ClientEvent f32 minsize; f32 maxsize; bool collisiondetection; + bool collision_removal; + u16 attached_id; bool vertical; std::string *texture; u32 id; @@ -450,7 +453,10 @@ public: core::line3d<f32> shootline_on_map ); - std::list<std::string> getConnectedPlayerNames(); + const std::list<std::string> &getConnectedPlayerNames() + { + return m_env.getPlayerNames(); + } float getAnimationTime(); @@ -458,9 +464,8 @@ public: void setCrack(int level, v3s16 pos); u16 getHP(); - u16 getBreath(); - bool checkPrivilege(const std::string &priv) + bool checkPrivilege(const std::string &priv) const { return (m_privileges.count(priv) != 0); } bool getChatMessage(std::wstring &message); @@ -497,6 +502,9 @@ public: u8 getProtoVersion() { return m_proto_ver; } + bool connectedToServer() + { return m_con.Connected(); } + float mediaReceiveProgress(); void afterContentReceived(IrrlichtDevice *device); @@ -658,18 +666,18 @@ private: // Sounds float m_removed_sounds_check_timer; // Mapping from server sound ids to our sound ids - std::map<s32, int> m_sounds_server_to_client; + UNORDERED_MAP<s32, int> m_sounds_server_to_client; // And the other way! - std::map<int, s32> m_sounds_client_to_server; + UNORDERED_MAP<int, s32> m_sounds_client_to_server; // And relations to objects - std::map<int, u16> m_sounds_to_objects; + UNORDERED_MAP<int, u16> m_sounds_to_objects; // Privileges - std::set<std::string> m_privileges; + UNORDERED_SET<std::string> m_privileges; // Detached inventories // key = name - std::map<std::string, Inventory*> m_detached_inventories; + UNORDERED_MAP<std::string, Inventory*> m_detached_inventories; // Storage for mesh data for creating multiple instances of the same mesh StringMap m_mesh_data; diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index a1ec37fe3..5faa186a7 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -1,6 +1,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp PARENT_SCOPE ) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 404a16310..6145e3dde 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -32,7 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiEngine.h" #include "player.h" #include "fontengine.h" +#include "joystick_controller.h" #include "clientlauncher.h" +#include "version.h" /* mainmenumanager.h */ @@ -112,6 +114,8 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) porting::setXorgClassHint(video_driver->getExposedVideoData(), PROJECT_NAME_C); + porting::setXorgWindowIcon(device); + /* This changes the minimum allowed number of vertices in a VBO. Default is 500. @@ -184,7 +188,9 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) { // Set the window caption const wchar_t *text = wgettext("Main Menu"); - device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + L" [" + text + L"]").c_str()); + device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + + L" " + utf8_to_wide(g_version_hash) + + L" [" + text + L"]").c_str()); delete[] text; try { // This is used for catching disconnects @@ -499,7 +505,8 @@ void ClientLauncher::main_menu(MainMenuData *menudata) #endif /* show main menu */ - GUIEngine mymenu(device, guiroot, &g_menumgr, smgr, menudata, *kill); + GUIEngine mymenu(device, &input->joystick, guiroot, + &g_menumgr, smgr, menudata, *kill); smgr->clear(); /* leave scene manager in a clean state */ } @@ -558,6 +565,22 @@ bool ClientLauncher::create_engine_device() device = createDeviceEx(params); if (device) { + if (g_settings->getBool("enable_joysticks")) { + irr::core::array<irr::SJoystickInfo> infos; + std::vector<irr::SJoystickInfo> joystick_infos; + // Make sure this is called maximum once per + // irrlicht device, otherwise it will give you + // multiple events for the same joystick. + if (device->activateJoysticks(infos)) { + infostream << "Joystick support enabled" << std::endl; + joystick_infos.reserve(infos.size()); + for (u32 i = 0; i < infos.size(); i++) { + joystick_infos.push_back(infos[i]); + } + } else { + errorstream << "Could not activate joystick support." << std::endl; + } + } porting::initIrrlicht(device); } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 69e4b25fa..824b0da2e 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define INPUT_HANDLER_H #include "irrlichttypes_extrabloated.h" +#include "joystick_controller.h" class MyEventReceiver : public IEventReceiver { @@ -42,11 +43,15 @@ public: // 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); + const KeyPress &keyCode = event.KeyInput; + if (keysListenedFor[keyCode]) { + if (event.KeyInput.PressedDown) { + keyIsDown.set(keyCode); + keyWasDown.set(keyCode); + } else { + keyIsDown.unset(keyCode); + } + return true; } } @@ -58,6 +63,14 @@ public: return true; } #endif + + if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { + /* TODO add a check like: + if (event.JoystickEvent != joystick_we_listen_for) + return false; + */ + return joystick->handleEvent(event.JoystickEvent); + } // handle mouse events if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { if (noMenuActive() == false) { @@ -116,6 +129,15 @@ public: return b; } + void listenForKey(const KeyPress &keyCode) + { + keysListenedFor.set(keyCode); + } + void dontListenForKeys() + { + keysListenedFor.clear(); + } + s32 getMouseWheel() { s32 a = mouse_wheel; @@ -159,6 +181,8 @@ public: s32 mouse_wheel; + JoystickController *joystick; + #ifdef HAVE_TOUCHSCREENGUI TouchScreenGUI* m_touchscreengui; #endif @@ -168,6 +192,12 @@ private: KeyList keyIsDown; // Whether a key has been pressed or not KeyList keyWasDown; + // List of keys we listen for + // TODO perhaps the type of this is not really + // performant as KeyList is designed for few but + // often changing keys, and keysListenedFor is expected + // to change seldomly but contain lots of keys. + KeyList keysListenedFor; }; @@ -183,6 +213,7 @@ public: m_receiver(receiver), m_mousepos(0,0) { + m_receiver->joystick = &joystick; } virtual bool isKeyDown(const KeyPress &keyCode) { @@ -192,6 +223,14 @@ public: { return m_receiver->WasKeyDown(keyCode); } + virtual void listenForKey(const KeyPress &keyCode) + { + m_receiver->listenForKey(keyCode); + } + virtual void dontListenForKeys() + { + m_receiver->dontListenForKeys(); + } virtual v2s32 getMousePos() { if (m_device->getCursorControl()) { @@ -261,6 +300,7 @@ public: void clear() { + joystick.clear(); m_receiver->clearInput(); } private: diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp new file mode 100644 index 000000000..ef8d18ab0 --- /dev/null +++ b/src/client/joystick_controller.cpp @@ -0,0 +1,179 @@ +/* +Minetest +Copyright (C) 2016 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 "joystick_controller.h" +#include "irrlichttypes_extrabloated.h" +#include "keys.h" +#include "settings.h" +#include "gettime.h" + +bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const +{ + u32 buttons = ev.ButtonStates; + + buttons &= filter_mask; + return buttons == compare_mask; +} + +bool JoystickAxisCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const +{ + s16 ax_val = ev.Axis[axis_to_compare]; + + return (ax_val * direction < 0) && (thresh * direction > ax_val * direction); +} + +// spares many characters +#define JLO_B_PB(A, B, C) jlo.button_keys.push_back(JoystickButtonCmb(A, B, C)) +#define JLO_A_PB(A, B, C, D) jlo.axis_keys.push_back(JoystickAxisCmb(A, B, C, D)) + +static JoystickLayout create_default_layout() +{ + JoystickLayout jlo; + + jlo.axes_dead_border = 1024; + + const JoystickAxisLayout axes[JA_COUNT] = { + {0, 1}, // JA_SIDEWARD_MOVE + {1, 1}, // JA_FORWARD_MOVE + {3, 1}, // JA_FRUSTUM_HORIZONTAL + {4, 1}, // JA_FRUSTUM_VERTICAL + }; + memcpy(jlo.axes, axes, sizeof(jlo.axes)); + + u32 sb = 1 << 7; // START button mask + u32 fb = 1 << 3; // FOUR button mask + u32 bm = sb | fb; // Mask for Both Modifiers + + // The back button means "ESC". + JLO_B_PB(KeyType::ESC, 1 << 6, 1 << 6); + + // The start button counts as modifier as well as use key. + // JLO_B_PB(KeyType::USE, sb, sb)); + + // Accessible without start modifier button pressed + // regardless whether four is pressed or not + JLO_B_PB(KeyType::SNEAK, sb | 1 << 2, 1 << 2); + + // Accessible without four modifier button pressed + // regardless whether start is pressed or not + JLO_B_PB(KeyType::MOUSE_L, fb | 1 << 4, 1 << 4); + JLO_B_PB(KeyType::MOUSE_R, fb | 1 << 5, 1 << 5); + + // Accessible without any modifier pressed + JLO_B_PB(KeyType::JUMP, bm | 1 << 0, 1 << 0); + JLO_B_PB(KeyType::SPECIAL1, bm | 1 << 1, 1 << 1); + + // Accessible with start button not pressed, but four pressed + // TODO find usage for button 0 + JLO_B_PB(KeyType::DROP, bm | 1 << 1, fb | 1 << 1); + JLO_B_PB(KeyType::SCROLL_UP, bm | 1 << 4, fb | 1 << 4); + JLO_B_PB(KeyType::SCROLL_DOWN,bm | 1 << 5, fb | 1 << 5); + + // Accessible with start button and four pressed + // TODO find usage for buttons 0, 1 and 4, 5 + + // Now about the buttons simulated by the axes + + // Movement buttons, important for vessels + JLO_A_PB(KeyType::FORWARD, 1, 1, 1024); + JLO_A_PB(KeyType::BACKWARD, 1, -1, 1024); + JLO_A_PB(KeyType::LEFT, 0, 1, 1024); + JLO_A_PB(KeyType::RIGHT, 0, -1, 1024); + + // Scroll buttons + JLO_A_PB(KeyType::SCROLL_UP, 2, -1, 1024); + JLO_A_PB(KeyType::SCROLL_DOWN, 5, -1, 1024); + + return jlo; +} + +static const JoystickLayout default_layout = create_default_layout(); + +JoystickController::JoystickController() +{ + m_layout = &default_layout; + doubling_dtime = g_settings->getFloat("repeat_joystick_button_time"); + + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + m_past_pressed_time[i] = 0; + } + clear(); +} + +bool JoystickController::handleEvent(const irr::SEvent::SJoystickEvent &ev) +{ + m_internal_time = getTimeMs() / 1000.f; + + std::bitset<KeyType::INTERNAL_ENUM_COUNT> keys_pressed; + + // First generate a list of keys pressed + + for (size_t i = 0; i < m_layout->button_keys.size(); i++) { + if (m_layout->button_keys[i].isTriggered(ev)) { + keys_pressed.set(m_layout->button_keys[i].key); + } + } + + for (size_t i = 0; i < m_layout->axis_keys.size(); i++) { + if (m_layout->axis_keys[i].isTriggered(ev)) { + keys_pressed.set(m_layout->axis_keys[i].key); + } + } + + // Then update the values + + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + if (keys_pressed[i]) { + if (!m_past_pressed_keys[i] && + m_past_pressed_time[i] < m_internal_time - doubling_dtime) { + m_past_pressed_keys[i] = true; + m_past_pressed_time[i] = m_internal_time; + } + } else if (m_pressed_keys[i]) { + m_past_released_keys[i] = true; + } + + m_pressed_keys[i] = keys_pressed[i]; + } + + for (size_t i = 0; i < JA_COUNT; i++) { + const JoystickAxisLayout &ax_la = m_layout->axes[i]; + m_axes_vals[i] = ax_la.invert * ev.Axis[ax_la.axis_id]; + } + + + return true; +} + +void JoystickController::clear() +{ + m_pressed_keys.reset(); + m_past_pressed_keys.reset(); + m_past_released_keys.reset(); + memset(m_axes_vals, 0, sizeof(m_axes_vals)); +} + +s16 JoystickController::getAxisWithoutDead(JoystickAxis axis) +{ + s16 v = m_axes_vals[axis]; + if (((v > 0) && (v < m_layout->axes_dead_border)) || + ((v < 0) && (v > -m_layout->axes_dead_border))) + return 0; + return v; +} diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h new file mode 100644 index 000000000..ed0ee4068 --- /dev/null +++ b/src/client/joystick_controller.h @@ -0,0 +1,163 @@ +/* +Minetest +Copyright (C) 2016 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 JOYSTICK_HEADER +#define JOYSTICK_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "keys.h" +#include <bitset> +#include <vector> + +enum JoystickAxis { + JA_SIDEWARD_MOVE, + JA_FORWARD_MOVE, + + JA_FRUSTUM_HORIZONTAL, + JA_FRUSTUM_VERTICAL, + + // To know the count of enum values + JA_COUNT, +}; + +struct JoystickAxisLayout { + u16 axis_id; + // -1 if to invert, 1 if to keep it. + int invert; +}; + + +struct JoystickCombination { + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const=0; + + GameKeyType key; +}; + +struct JoystickButtonCmb : public JoystickCombination { + + JoystickButtonCmb() {} + JoystickButtonCmb(GameKeyType key, u32 filter_mask, u32 compare_mask) : + filter_mask(filter_mask), + compare_mask(compare_mask) + { + this->key = key; + } + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; + + u32 filter_mask; + u32 compare_mask; +}; + +struct JoystickAxisCmb : public JoystickCombination { + + JoystickAxisCmb() {} + JoystickAxisCmb(GameKeyType key, u16 axis_to_compare, int direction, s16 thresh) : + axis_to_compare(axis_to_compare), + direction(direction), + thresh(thresh) + { + this->key = key; + } + + virtual bool isTriggered(const irr::SEvent::SJoystickEvent &ev) const; + + u16 axis_to_compare; + + // if -1, thresh must be smaller than the axis value in order to trigger + // if 1, thresh must be bigger than the axis value in order to trigger + int direction; + s16 thresh; +}; + +struct JoystickLayout { + std::vector<JoystickButtonCmb> button_keys; + std::vector<JoystickAxisCmb> axis_keys; + JoystickAxisLayout axes[JA_COUNT]; + s16 axes_dead_border; +}; + +class JoystickController { + +public: + JoystickController(); + bool handleEvent(const irr::SEvent::SJoystickEvent &ev); + void clear(); + + bool wasKeyDown(GameKeyType b) + { + bool r = m_past_pressed_keys[b]; + m_past_pressed_keys[b] = false; + return r; + } + bool getWasKeyDown(GameKeyType b) + { + return m_past_pressed_keys[b]; + } + void clearWasKeyDown(GameKeyType b) + { + m_past_pressed_keys[b] = false; + } + + bool wasKeyReleased(GameKeyType b) + { + bool r = m_past_released_keys[b]; + m_past_released_keys[b] = false; + return r; + } + bool getWasKeyReleased(GameKeyType b) + { + return m_past_pressed_keys[b]; + } + void clearWasKeyReleased(GameKeyType b) + { + m_past_pressed_keys[b] = false; + } + + bool isKeyDown(GameKeyType b) + { + return m_pressed_keys[b]; + } + + s16 getAxis(JoystickAxis axis) + { + return m_axes_vals[axis]; + } + + s16 getAxisWithoutDead(JoystickAxis axis); + + f32 doubling_dtime; + +private: + const JoystickLayout *m_layout; + + s16 m_axes_vals[JA_COUNT]; + + std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_pressed_keys; + + f32 m_internal_time; + + f32 m_past_pressed_time[KeyType::INTERNAL_ENUM_COUNT]; + + std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_past_pressed_keys; + std::bitset<KeyType::INTERNAL_ENUM_COUNT> m_past_released_keys; +}; + +#endif diff --git a/src/client/keys.h b/src/client/keys.h new file mode 100644 index 000000000..6467c443e --- /dev/null +++ b/src/client/keys.h @@ -0,0 +1,86 @@ +/* +Minetest +Copyright (C) 2016 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 KEYS_HEADER +#define KEYS_HEADER + +#include<list> + +class KeyType { +public: + enum T { + // Player movement + FORWARD, + BACKWARD, + LEFT, + RIGHT, + JUMP, + SPECIAL1, + SNEAK, + AUTORUN, + + ESC, + + // Other + DROP, + INVENTORY, + CHAT, + CMD, + CONSOLE, + MINIMAP, + FREEMOVE, + FASTMOVE, + NOCLIP, + CINEMATIC, + SCREENSHOT, + TOGGLE_HUD, + TOGGLE_CHAT, + TOGGLE_FORCE_FOG_OFF, + TOGGLE_UPDATE_CAMERA, + TOGGLE_DEBUG, + TOGGLE_PROFILER, + CAMERA_MODE, + INCREASE_VIEWING_RANGE, + DECREASE_VIEWING_RANGE, + RANGESELECT, + ZOOM, + + QUICKTUNE_NEXT, + QUICKTUNE_PREV, + QUICKTUNE_INC, + QUICKTUNE_DEC, + + DEBUG_STACKS, + + // joystick specific keys + MOUSE_L, + MOUSE_R, + SCROLL_UP, + SCROLL_DOWN, + + // Fake keycode for array size and internal checks + INTERNAL_ENUM_COUNT + + }; +}; + +typedef KeyType::T GameKeyType; + + +#endif diff --git a/src/client/tile.cpp b/src/client/tile.cpp index ec8c95f02..8f0c39465 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -948,11 +948,10 @@ video::ITexture* TextureSource::generateTextureFromMesh( video::IImage* TextureSource::generateImage(const std::string &name) { - /* - Get the base image - */ + // Get the base image const char separator = '^'; + const char escape = '\\'; const char paren_open = '('; const char paren_close = ')'; @@ -960,7 +959,9 @@ video::IImage* TextureSource::generateImage(const std::string &name) s32 last_separator_pos = -1; u8 paren_bal = 0; for (s32 i = name.size() - 1; i >= 0; i--) { - switch(name[i]) { + if (i > 0 && name[i-1] == escape) + continue; + switch (name[i]) { case separator: if (paren_bal == 0) { last_separator_pos = i; @@ -1028,10 +1029,12 @@ video::IImage* TextureSource::generateImage(const std::string &name) return NULL; } core::dimension2d<u32> dim = tmp->getDimension(); - if (!baseimg) - baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); - blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); - tmp->drop(); + if (baseimg) { + blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); + tmp->drop(); + } else { + baseimg = tmp; + } } else if (!generateImagePart(last_part_of_name, baseimg)) { // Generate image according to part of name errorstream << "generateImage(): " @@ -1099,9 +1102,27 @@ video::IImage * Align2Npot2(video::IImage * image, #endif +static std::string unescape_string(const std::string &str, const char esc = '\\') +{ + std::string out; + size_t pos = 0, cpos; + out.reserve(str.size()); + while (1) { + cpos = str.find_first_of(esc, pos); + if (cpos == std::string::npos) { + out += str.substr(pos); + break; + } + out += str.substr(pos, cpos - pos) + str[cpos + 1]; + pos = cpos + 2; + } + return out; +} + bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { + const char escape = '\\'; // same as in generateImage() video::IVideoDriver* driver = m_device->getVideoDriver(); sanity_check(driver); @@ -1251,7 +1272,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, } /* [combine:WxH:X,Y=filename:X,Y=filename2 - Creates a bigger texture from an amount of smaller ones + Creates a bigger texture from any amount of smaller ones */ else if (str_starts_with(part_of_name, "[combine")) { @@ -1259,7 +1280,6 @@ bool TextureSource::generateImagePart(std::string part_of_name, sf.next(":"); u32 w0 = stoi(sf.next("x")); u32 h0 = stoi(sf.next(":")); - //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl; core::dimension2d<u32> dim(w0,h0); if (baseimg == NULL) { baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); @@ -1268,11 +1288,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, while (sf.at_end() == false) { u32 x = stoi(sf.next(",")); u32 y = stoi(sf.next("=")); - std::string filename = sf.next(":"); + std::string filename = unescape_string(sf.next_esc(":", escape), escape); infostream<<"Adding \""<<filename <<"\" to combined ("<<x<<","<<y<<")" <<std::endl; - video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + video::IImage *img = generateImage(filename); if (img) { core::dimension2d<u32> dim = img->getDimension(); infostream<<"Size "<<dim.Width @@ -1295,7 +1315,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, } } /* - "[brighten" + [brighten */ else if (str_starts_with(part_of_name, "[brighten")) { @@ -1309,7 +1329,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, brighten(baseimg); } /* - "[noalpha" + [noalpha Make image completely opaque. Used for the leaves texture when in old leaves mode, so that the transparent parts don't look completely black @@ -1336,7 +1356,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, } } /* - "[makealpha:R,G,B" + [makealpha:R,G,B Convert one color to transparent. */ else if (str_starts_with(part_of_name, "[makealpha:")) @@ -1375,7 +1395,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, } } /* - "[transformN" + [transformN Rotates and/or flips the image. N can be a number (between 0 and 7) or a transform name. @@ -1543,12 +1563,11 @@ bool TextureSource::generateImagePart(std::string part_of_name, Strfnd sf(part_of_name); sf.next(":"); u32 percent = stoi(sf.next(":")); - std::string filename = sf.next(":"); - //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl; + std::string filename = unescape_string(sf.next_esc(":", escape), escape); if (baseimg == NULL) baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16)); - video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + video::IImage *img = generateImage(filename); if (img) { core::dimension2d<u32> dim = img->getDimension(); @@ -1628,9 +1647,9 @@ bool TextureSource::generateImagePart(std::string part_of_name, } Strfnd sf(part_of_name); sf.next(":"); - std::string filename = sf.next(":"); + std::string filename = unescape_string(sf.next_esc(":", escape), escape); - video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + video::IImage *img = generateImage(filename); if (img) { apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0), img->getDimension()); @@ -1673,6 +1692,10 @@ bool TextureSource::generateImagePart(std::string part_of_name, apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha); } + /* + [applyfiltersformesh + Internal modifier + */ else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { // Apply the "clean transparent" filter, if configured. @@ -1735,6 +1758,75 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg->drop(); baseimg = image; } + /* + [opacity:R + Makes the base image transparent according to the given ratio. + R must be between 0 and 255. + 0 means totally transparent. + 255 means totally opaque. + */ + else if (str_starts_with(part_of_name, "[opacity:")) { + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + Strfnd sf(part_of_name); + sf.next(":"); + + u32 ratio = mystoi(sf.next(""), 0, 255); + + core::dimension2d<u32> dim = baseimg->getDimension(); + + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) + { + video::SColor c = baseimg->getPixel(x, y); + c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5)); + baseimg->setPixel(x, y, c); + } + } + /* + [invert:mode + Inverts the given channels of the base image. + Mode may contain the characters "r", "g", "b", "a". + Only the channels that are mentioned in the mode string + will be inverted. + */ + else if (str_starts_with(part_of_name, "[invert:")) { + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + Strfnd sf(part_of_name); + sf.next(":"); + + std::string mode = sf.next(""); + u32 mask = 0; + if (mode.find("a") != std::string::npos) + mask |= 0xff000000UL; + if (mode.find("r") != std::string::npos) + mask |= 0x00ff0000UL; + if (mode.find("g") != std::string::npos) + mask |= 0x0000ff00UL; + if (mode.find("b") != std::string::npos) + mask |= 0x000000ffUL; + + core::dimension2d<u32> dim = baseimg->getDimension(); + + for (u32 y = 0; y < dim.Height; y++) + for (u32 x = 0; x < dim.Width; x++) + { + video::SColor c = baseimg->getPixel(x, y); + c.color ^= mask; + baseimg->setPixel(x, y, c); + } + } else { errorstream << "generateImagePart(): Invalid " diff --git a/src/clientiface.cpp b/src/clientiface.cpp index a3a17d435..0390cf0ff 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -22,14 +22,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientiface.h" #include "util/numeric.h" #include "util/mathconstants.h" -#include "player.h" +#include "remoteplayer.h" #include "settings.h" #include "mapblock.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 "content_sao.h" // TODO this is used for cleanup of only #include "log.h" #include "util/srp.h" @@ -77,9 +77,13 @@ void RemoteClient::GetNextBlocks ( if(m_nothing_to_send_pause_timer >= 0) return; - Player *player = env->getPlayer(peer_id); + RemotePlayer *player = env->getPlayer(peer_id); // This can happen sometimes; clients and players are not in perfect sync. - if(player == NULL) + if (player == NULL) + return; + + PlayerSAO *sao = player->getPlayerSAO(); + if (sao == NULL) return; // Won't send anything if already sending @@ -90,7 +94,7 @@ void RemoteClient::GetNextBlocks ( return; } - v3f playerpos = player->getPosition(); + v3f playerpos = sao->getBasePosition(); v3f playerspeed = player->getSpeed(); v3f playerspeeddir(0,0,0); if(playerspeed.getLength() > 1.0*BS) @@ -103,10 +107,10 @@ void RemoteClient::GetNextBlocks ( v3s16 center = getNodeBlockPos(center_nodepos); // Camera position and direction - v3f camera_pos = player->getEyePosition(); + v3f camera_pos = sao->getEyePosition(); v3f camera_dir = v3f(0,0,1); - camera_dir.rotateYZBy(player->getPitch()); - camera_dir.rotateXZBy(player->getYaw()); + camera_dir.rotateYZBy(sao->getPitch()); + camera_dir.rotateXZBy(sao->getYaw()); /*infostream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<"," <<camera_dir.Z<<")"<<std::endl;*/ @@ -169,9 +173,20 @@ void RemoteClient::GetNextBlocks ( */ s32 new_nearest_unsent_d = -1; - const s16 full_d_max = g_settings->getS16("max_block_send_distance"); + // get view range and camera fov from the client + s16 wanted_range = sao->getWantedRange(); + float camera_fov = sao->getFov(); + // if FOV, wanted_range are not available (old client), fall back to old default + if (wanted_range <= 0) wanted_range = 1000; + if (camera_fov <= 0) camera_fov = (72.0*M_PI/180) * 4./3.; + + const s16 full_d_max = MYMIN(g_settings->getS16("max_block_send_distance"), wanted_range); + const s16 d_opt = MYMIN(g_settings->getS16("block_send_optimize_distance"), wanted_range); + const s16 d_blocks_in_sight = full_d_max * BS * MAP_BLOCKSIZE; + //infostream << "Fov from client " << camera_fov << " full_d_max " << full_d_max << std::endl; + s16 d_max = full_d_max; - s16 d_max_gen = g_settings->getS16("max_block_generate_distance"); + s16 d_max_gen = MYMIN(g_settings->getS16("max_block_generate_distance"), wanted_range); // Don't loop very much at a time s16 max_d_increment_at_time = 2; @@ -229,24 +244,13 @@ void RemoteClient::GetNextBlocks ( // If this is true, inexistent block will be made from scratch bool generate = d <= d_max_gen; - { - /*// Limit the generating area vertically to 2/3 - if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3) - generate = false;*/ - - // Limit the send area vertically to 1/2 - if (abs(p.Y - center.Y) > full_d_max / 2) - continue; - } - /* Don't generate or send if not in sight FIXME This only works if the client uses a small enough FOV setting. The default of 72 degrees is fine. */ - float camera_fov = (72.0*M_PI/180) * 4./3.; - if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false) + if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, d_blocks_in_sight) == false) { continue; } @@ -296,7 +300,7 @@ void RemoteClient::GetNextBlocks ( Block is near ground level if night-time mesh differs from day-time mesh. */ - if(d >= 4) + if(d >= d_opt) { if(block->getDayNightDiff() == false) continue; @@ -353,7 +357,7 @@ queue_full_break: } else if(nearest_emergefull_d != -1){ new_nearest_unsent_d = nearest_emergefull_d; } else { - if(d > g_settings->getS16("max_block_send_distance")){ + if(d > full_d_max){ new_nearest_unsent_d = 0; m_nothing_to_send_pause_timer = 2.0; } else { @@ -605,11 +609,8 @@ ClientInterface::~ClientInterface() { MutexAutoLock clientslock(m_clients_mutex); - for(std::map<u16, RemoteClient*>::iterator - i = m_clients.begin(); - i != m_clients.end(); ++i) - { - + for (UNORDERED_MAP<u16, RemoteClient*>::iterator i = m_clients.begin(); + i != m_clients.end(); ++i) { // Delete client delete i->second; } @@ -621,10 +622,8 @@ std::vector<u16> ClientInterface::getClientIDs(ClientState min_state) std::vector<u16> reply; MutexAutoLock clientslock(m_clients_mutex); - for(std::map<u16, RemoteClient*>::iterator - i = m_clients.begin(); - i != m_clients.end(); ++i) - { + for(UNORDERED_MAP<u16, RemoteClient*>::iterator i = m_clients.begin(); + i != m_clients.end(); ++i) { if (i->second->getState() >= min_state) reply.push_back(i->second->peer_id); } @@ -632,12 +631,6 @@ std::vector<u16> ClientInterface::getClientIDs(ClientState min_state) return reply; } -std::vector<std::string> ClientInterface::getPlayerNames() -{ - return m_clients_names; -} - - void ClientInterface::step(float dtime) { m_print_info_timer += dtime; @@ -650,8 +643,7 @@ void ClientInterface::step(float dtime) void ClientInterface::UpdatePlayerList() { - if (m_env != NULL) - { + if (m_env != NULL) { std::vector<u16> clients = getClientIDs(); m_clients_names.clear(); @@ -659,10 +651,8 @@ void ClientInterface::UpdatePlayerList() if(!clients.empty()) infostream<<"Players:"<<std::endl; - for(std::vector<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) { - Player *player = m_env->getPlayer(*i); + for (std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { + RemotePlayer *player = m_env->getPlayer(*i); if (player == NULL) continue; @@ -691,8 +681,7 @@ void ClientInterface::sendToAll(u16 channelnum, NetworkPacket* pkt, bool reliable) { MutexAutoLock clientslock(m_clients_mutex); - for(std::map<u16, RemoteClient*>::iterator - i = m_clients.begin(); + for(UNORDERED_MAP<u16, RemoteClient*>::iterator i = m_clients.begin(); i != m_clients.end(); ++i) { RemoteClient *client = i->second; @@ -705,11 +694,10 @@ void ClientInterface::sendToAll(u16 channelnum, RemoteClient* ClientInterface::getClientNoEx(u16 peer_id, ClientState state_min) { MutexAutoLock clientslock(m_clients_mutex); - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. - if(n == m_clients.end()) + if (n == m_clients.end()) return NULL; if (n->second->getState() >= state_min) @@ -720,11 +708,10 @@ RemoteClient* ClientInterface::getClientNoEx(u16 peer_id, ClientState state_min) RemoteClient* ClientInterface::lockedGetClientNoEx(u16 peer_id, ClientState state_min) { - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. - if(n == m_clients.end()) + if (n == m_clients.end()) return NULL; if (n->second->getState() >= state_min) @@ -736,11 +723,10 @@ RemoteClient* ClientInterface::lockedGetClientNoEx(u16 peer_id, ClientState stat ClientState ClientInterface::getClientState(u16 peer_id) { MutexAutoLock clientslock(m_clients_mutex); - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. - if(n == m_clients.end()) + if (n == m_clients.end()) return CS_Invalid; return n->second->getState(); @@ -749,11 +735,10 @@ ClientState ClientInterface::getClientState(u16 peer_id) void ClientInterface::setPlayerName(u16 peer_id,std::string name) { MutexAutoLock clientslock(m_clients_mutex); - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. - if(n != m_clients.end()) + if (n != m_clients.end()) n->second->setName(name); } @@ -762,11 +747,10 @@ void ClientInterface::DeleteClient(u16 peer_id) MutexAutoLock conlock(m_clients_mutex); // Error check - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. - if(n == m_clients.end()) + if (n == m_clients.end()) return; /* @@ -797,10 +781,9 @@ void ClientInterface::CreateClient(u16 peer_id) MutexAutoLock conlock(m_clients_mutex); // Error check - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // The client shouldn't already exist - if(n != m_clients.end()) return; + if (n != m_clients.end()) return; // Create client RemoteClient *client = new RemoteClient(); @@ -814,8 +797,7 @@ void ClientInterface::event(u16 peer_id, ClientStateEvent event) MutexAutoLock clientlock(m_clients_mutex); // Error check - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // No client to deliver event if (n == m_clients.end()) @@ -836,8 +818,7 @@ u16 ClientInterface::getProtocolVersion(u16 peer_id) MutexAutoLock conlock(m_clients_mutex); // Error check - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // No client to get version if (n == m_clients.end()) @@ -851,8 +832,7 @@ void ClientInterface::setClientVersion(u16 peer_id, u8 major, u8 minor, u8 patch MutexAutoLock conlock(m_clients_mutex); // Error check - std::map<u16, RemoteClient*>::iterator n; - n = m_clients.find(peer_id); + UNORDERED_MAP<u16, RemoteClient*>::iterator n = m_clients.find(peer_id); // No client to set versions if (n == m_clients.end()) diff --git a/src/clientiface.h b/src/clientiface.h index c09942909..551d71bbe 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -25,10 +25,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" // for SER_FMT_VER_INVALID #include "threading/mutex.h" #include "network/networkpacket.h" +#include "util/cpp11_container.h" #include <list> #include <vector> -#include <map> #include <set> class MapBlock; @@ -453,7 +453,7 @@ public: std::vector<u16> getClientIDs(ClientState min_state=CS_Active); /* get list of client player names */ - std::vector<std::string> getPlayerNames(); + const std::vector<std::string> &getPlayerNames() const { return m_clients_names; } /* send message to client */ void send(u16 peer_id, u8 channelnum, NetworkPacket* pkt, bool reliable); @@ -502,8 +502,7 @@ protected: void lock() { m_clients_mutex.lock(); } void unlock() { m_clients_mutex.unlock(); } - std::map<u16, RemoteClient*>& getClientList() - { return m_clients; } + UNORDERED_MAP<u16, RemoteClient*>& getClientList() { return m_clients; } private: /* update internal player list */ @@ -513,7 +512,7 @@ private: con::Connection* m_con; Mutex m_clients_mutex; // Connected clients (behind the con mutex) - std::map<u16, RemoteClient*> m_clients; + UNORDERED_MAP<u16, RemoteClient*> m_clients; std::vector<std::string> m_clients_names; //for announcing masterserver // Environment diff --git a/src/clientmap.cpp b/src/clientmap.cpp index a0a780250..faa1461f6 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -132,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){ - if(count == needed_count) - return true; count++; + if(count >= needed_count) + return true; } step *= stepfac; } @@ -293,13 +293,22 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) float step = BS * 1; float stepfac = 1.1; float startoff = BS * 1; - float endoff = -BS*MAP_BLOCKSIZE * 1.42 * 1.42; - v3s16 spn = cam_pos_nodes + v3s16(0, 0, 0); + // The occlusion search of 'isOccluded()' must stop short of the target + // point by distance 'endoff' (end offset) to not enter the target mapblock. + // For the 8 mapblock corners 'endoff' must therefore be the maximum diagonal + // of a mapblock, because we must consider all view angles. + // sqrt(1^2 + 1^2 + 1^2) = 1.732 + float endoff = -BS * MAP_BLOCKSIZE * 1.732050807569; + v3s16 spn = cam_pos_nodes; s16 bs2 = MAP_BLOCKSIZE / 2 + 1; - u32 needed_count = 1; + // to reduce the likelihood of falsely occluded blocks + // require at least two solid blocks + // this is a HACK, we should think of a more precise algorithm + u32 needed_count = 2; if (occlusion_culling_enabled && - isOccluded(this, spn, cpn + v3s16(0, 0, 0), - step, stepfac, startoff, endoff, needed_count, nodemgr) && + // For the central point of the mapblock 'endoff' can be halved + isOccluded(this, spn, cpn, + step, stepfac, startoff, endoff / 2.0f, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), step, stepfac, startoff, endoff, needed_count, nodemgr) && isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), @@ -521,6 +530,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, m_cache_trilinear_filter); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, m_cache_bilinear_filter); buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, m_cache_anistropic_filter); + buf->getMaterial().setFlag(video::EMF_WIREFRAME, m_control.show_wireframe); const video::SMaterial& material = buf->getMaterial(); video::IMaterialRenderer* rnd = diff --git a/src/clientmap.h b/src/clientmap.h index 396e68f64..cb686ff33 100644 --- a/src/clientmap.h +++ b/src/clientmap.h @@ -32,6 +32,7 @@ struct MapDrawControl range_all(false), wanted_range(0), wanted_max_blocks(0), + show_wireframe(false), blocks_drawn(0), blocks_would_have_drawn(0), farthest_drawn(0) @@ -43,6 +44,8 @@ struct MapDrawControl float wanted_range; // Maximum number of blocks to draw u32 wanted_max_blocks; + // show a wire frame for debugging + bool show_wireframe; // Number of blocks rendered is written here by the renderer u32 blocks_drawn; // Number of blocks that would have been drawn in wanted_range @@ -135,7 +138,9 @@ public: { return (m_last_drawn_sectors.find(p) != m_last_drawn_sectors.end()); } - + + const MapDrawControl & getControl() const { return m_control; } + f32 getCameraFov() const { return m_camera_fov; } private: Client *m_client; diff --git a/src/clientobject.cpp b/src/clientobject.cpp index a11757ea6..ff3f47187 100644 --- a/src/clientobject.cpp +++ b/src/clientobject.cpp @@ -43,12 +43,11 @@ ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, IGameDef *gamedef, ClientEnvironment *env) { // Find factory function - std::map<u16, Factory>::iterator n; - n = m_types.find(type); + UNORDERED_MAP<u16, Factory>::iterator n = m_types.find(type); if(n == m_types.end()) { // If factory is not found, just return. - warningstream<<"ClientActiveObject: No factory for type=" - <<(int)type<<std::endl; + warningstream << "ClientActiveObject: No factory for type=" + << (int)type << std::endl; return NULL; } @@ -59,8 +58,7 @@ ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, void ClientActiveObject::registerType(u16 type, Factory f) { - std::map<u16, Factory>::iterator n; - n = m_types.find(type); + UNORDERED_MAP<u16, Factory>::iterator n = m_types.find(type); if(n != m_types.end()) return; m_types[type] = f; diff --git a/src/clientobject.h b/src/clientobject.h index 3cc7c2391..83931e438 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "activeobject.h" #include <map> +#include "util/cpp11_container.h" /* @@ -60,6 +61,7 @@ public: virtual bool getCollisionBox(aabb3f *toset){return false;} virtual bool collideWithObjects(){return false;} virtual v3f getPosition(){return v3f(0,0,0);} + virtual float getYaw() const {return 0;} virtual scene::ISceneNode *getSceneNode(){return NULL;} virtual scene::IMeshSceneNode *getMeshSceneNode(){return NULL;} virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(){return NULL;} @@ -103,7 +105,7 @@ protected: ClientEnvironment *m_env; private: // Used for creating objects based on type - static std::map<u16, Factory> m_types; + static UNORDERED_MAP<u16, Factory> m_types; }; struct DistanceSortedActiveObject diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 018532d13..4b731020a 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -14,6 +14,7 @@ #define STATIC_SHAREDIR "@SHAREDIR@" #define STATIC_LOCALEDIR "@LOCALEDIR@" #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" +#define ICON_DIR "@ICONDIR@" #cmakedefine01 RUN_IN_PLACE #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL @@ -22,6 +23,7 @@ #cmakedefine01 USE_CURSES #cmakedefine01 USE_LEVELDB #cmakedefine01 USE_LUAJIT +#cmakedefine01 USE_POSTGRESQL #cmakedefine01 USE_SPATIAL #cmakedefine01 USE_SYSTEM_GMP #cmakedefine01 USE_REDIS diff --git a/src/collision.cpp b/src/collision.cpp index 74c0c25c3..8e5dbcc9b 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -34,6 +34,27 @@ with this program; if not, write to the Free Software Foundation, Inc., //#define COLL_ZERO 0.032 // broken unit tests #define COLL_ZERO 0 + +struct NearbyCollisionInfo { + NearbyCollisionInfo(bool is_ul, bool is_obj, int bouncy, + const v3s16 &pos, const aabb3f &box) : + is_unloaded(is_ul), + is_step_up(false), + is_object(is_obj), + bouncy(bouncy), + position(pos), + box(box) + {} + + bool is_unloaded; + bool is_step_up; + bool is_object; + int bouncy; + v3s16 position; + aabb3f box; +}; + + // 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 @@ -160,7 +181,7 @@ int axisAlignedCollision( // Helper function: // Checks if moving the movingbox up by the given distance would hit a ceiling. bool wouldCollideWithCeiling( - const std::vector<aabb3f> &staticboxes, + const std::vector<NearbyCollisionInfo> &cinfo, const aabb3f &movingbox, f32 y_increase, f32 d) { @@ -168,12 +189,10 @@ bool wouldCollideWithCeiling( assert(y_increase >= 0); // pre-condition - for(std::vector<aabb3f>::const_iterator - i = staticboxes.begin(); - i != staticboxes.end(); ++i) - { - const aabb3f& staticbox = *i; - if((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && + for (std::vector<NearbyCollisionInfo>::const_iterator it = cinfo.begin(); + it != cinfo.end(); ++it) { + const aabb3f &staticbox = it->box; + if ((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && (movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) && (movingbox.MinEdge.X < staticbox.MaxEdge.X) && (movingbox.MaxEdge.X > staticbox.MinEdge.X) && @@ -234,12 +253,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, /* Collect node boxes in movement range */ - std::vector<aabb3f> cboxes; - std::vector<bool> is_unloaded; - std::vector<bool> is_step_up; - std::vector<bool> is_object; - std::vector<int> bouncy_values; - std::vector<v3s16> node_positions; + std::vector<NearbyCollisionInfo> cinfo; { //TimeTaker tt2("collisionMoveSimple collect boxes"); ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); @@ -310,23 +324,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, aabb3f box = *i; box.MinEdge += v3f(x, y, z)*BS; box.MaxEdge += v3f(x, y, z)*BS; - cboxes.push_back(box); - is_unloaded.push_back(false); - is_step_up.push_back(false); - bouncy_values.push_back(n_bouncy_value); - node_positions.push_back(p); - is_object.push_back(false); + cinfo.push_back(NearbyCollisionInfo(false, + false, n_bouncy_value, p, box)); } - } - else { + } else { // Collide with unloaded nodes aabb3f box = getNodeBox(p, BS); - cboxes.push_back(box); - is_unloaded.push_back(true); - is_step_up.push_back(false); - bouncy_values.push_back(0); - node_positions.push_back(p); - is_object.push_back(false); + cinfo.push_back(NearbyCollisionInfo(true, false, 0, p, box)); } } @@ -344,7 +348,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, ScopeProfiler sp(g_profiler, "collisionMoveSimple objects avg", SPT_AVG); //TimeTaker tt3("collisionMoveSimple collect object boxes"); - /* add object boxes to cboxes */ + /* add object boxes to cinfo */ std::vector<ActiveObject*> objects; #ifndef SERVER @@ -363,7 +367,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, #endif { ServerEnvironment *s_env = dynamic_cast<ServerEnvironment*>(env); - if (s_env != 0) { + if (s_env != NULL) { f32 distance = speed_f->getLength(); std::vector<u16> s_objects; s_env->getObjectsInsideRadius(s_objects, *pos_f, distance * 1.5); @@ -384,23 +388,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, aabb3f object_collisionbox; if (object->getCollisionBox(&object_collisionbox) && object->collideWithObjects()) { - cboxes.push_back(object_collisionbox); - is_unloaded.push_back(false); - is_step_up.push_back(false); - bouncy_values.push_back(0); - node_positions.push_back(v3s16(0,0,0)); - is_object.push_back(true); + cinfo.push_back(NearbyCollisionInfo(false, true, 0, v3s16(), object_collisionbox)); } } } } //tt3 - 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 */ @@ -440,15 +433,16 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, /* Go through every nodebox, find nearest collision */ - for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { + for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) { + NearbyCollisionInfo box_info = cinfo[boxindex]; // Ignore if already stepped up this nodebox. - if(is_step_up[boxindex]) + if (box_info.is_step_up) continue; // Find nearest collision of the two boxes (raytracing-like) f32 dtime_tmp; - int collided = axisAlignedCollision( - cboxes[boxindex], movingbox, *speed_f, d, &dtime_tmp); + int collided = axisAlignedCollision(box_info.box, + movingbox, *speed_f, d, &dtime_tmp); if (collided == -1 || dtime_tmp >= nearest_dtime) continue; @@ -464,19 +458,19 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers } else { // Otherwise, a collision occurred. - - const aabb3f& cbox = cboxes[nearest_boxindex]; + NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex]; + const aabb3f& cbox = nearest_info.box; // Check for stairs. bool step_up = (nearest_collided != 1) && // must not be Y direction (movingbox.MinEdge.Y < cbox.MaxEdge.Y) && (movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) && - (!wouldCollideWithCeiling(cboxes, movingbox, + (!wouldCollideWithCeiling(cinfo, movingbox, cbox.MaxEdge.Y - movingbox.MinEdge.Y, d)); // Get bounce multiplier - bool bouncy = (bouncy_values[nearest_boxindex] >= 1); - float bounce = -(float)bouncy_values[nearest_boxindex] / 100.0; + bool bouncy = (nearest_info.bouncy >= 1); + float bounce = -(float)nearest_info.bouncy / 100.0; // Move to the point of collision and reduce dtime by nearest_dtime if (nearest_dtime < 0) { @@ -495,39 +489,38 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } bool is_collision = true; - if (is_unloaded[nearest_boxindex]) + if (nearest_info.is_unloaded) is_collision = false; CollisionInfo info; - if (is_object[nearest_boxindex]) + if (nearest_info.is_object) info.type = COLLISION_OBJECT; else info.type = COLLISION_NODE; - info.node_p = node_positions[nearest_boxindex]; + info.node_p = nearest_info.position; info.bouncy = bouncy; info.old_speed = *speed_f; // Set the speed component that caused the collision to zero if (step_up) { // Special case: Handle stairs - is_step_up[nearest_boxindex] = true; + nearest_info.is_step_up = true; is_collision = false; - } else if(nearest_collided == 0) { // X + } else if (nearest_collided == 0) { // X if (fabs(speed_f->X) > BS * 3) speed_f->X *= bounce; else speed_f->X = 0; result.collides = true; result.collides_xz = true; - } - else if(nearest_collided == 1) { // Y - if (fabs(speed_f->Y) > BS * 3) + } else if (nearest_collided == 1) { // Y + if(fabs(speed_f->Y) > BS * 3) speed_f->Y *= bounce; else speed_f->Y = 0; result.collides = true; - } else if(nearest_collided == 2) { // Z + } else if (nearest_collided == 2) { // Z if (fabs(speed_f->Z) > BS * 3) speed_f->Z *= bounce; else @@ -552,8 +545,9 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, aabb3f box = box_0; box.MinEdge += *pos_f; box.MaxEdge += *pos_f; - for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { - const aabb3f& cbox = cboxes[boxindex]; + for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) { + NearbyCollisionInfo &box_info = cinfo[boxindex]; + const aabb3f &cbox = box_info.box; /* See if the object is touching ground. @@ -567,8 +561,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X && cbox.MaxEdge.Z - d > box.MinEdge.Z && cbox.MinEdge.Z + d < box.MaxEdge.Z) { - if (is_step_up[boxindex]) { - pos_f->Y += (cbox.MaxEdge.Y - box.MinEdge.Y); + if (box_info.is_step_up) { + pos_f->Y += cbox.MaxEdge.Y - box.MinEdge.Y; box = box_0; box.MinEdge += *pos_f; box.MaxEdge += *pos_f; @@ -576,9 +570,9 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15 * BS) { result.touching_ground = true; - if (is_object[boxindex]) + if (box_info.is_object) result.standing_on_object = true; - if (is_unloaded[boxindex]) + if (box_info.is_unloaded) result.standing_on_unloaded = true; } } diff --git a/src/constants.h b/src/constants.h index b606fc4fa..55ae9daf3 100644 --- a/src/constants.h +++ b/src/constants.h @@ -112,7 +112,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #if defined(_WIN32) #define TTF_DEFAULT_FONT_SIZE (18) #else - #define TTF_DEFAULT_FONT_SIZE (15) + #define TTF_DEFAULT_FONT_SIZE (16) #endif #define DEFAULT_FONT_SIZE (10) diff --git a/src/content_cao.cpp b/src/content_cao.cpp index d701e4f72..6b35d5881 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -51,7 +51,7 @@ struct ToolCapabilities; #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" -std::map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types; +UNORDERED_MAP<u16, ClientActiveObject::Factory> ClientActiveObject::m_types; SmoothTranslator::SmoothTranslator(): vect_old(0,0,0), @@ -313,7 +313,8 @@ public: {return &m_selection_box;} v3f getPosition() {return m_position;} - + inline float getYaw() const + {return 0;} std::string infoText() {return m_infotext;} @@ -546,7 +547,6 @@ GenericCAO::GenericCAO(IGameDef *gamedef, ClientEnvironment *env): // m_smgr(NULL), m_irr(NULL), - m_camera(NULL), m_gamedef(NULL), m_selection_box(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.), m_meshnode(NULL), @@ -567,7 +567,7 @@ GenericCAO::GenericCAO(IGameDef *gamedef, ClientEnvironment *env): m_animation_speed(15), m_animation_blend(0), m_animation_loop(true), - m_bone_position(std::map<std::string, core::vector2d<v3f> >()), + m_bone_position(UNORDERED_MAP<std::string, core::vector2d<v3f> >()), m_attachment_bone(""), m_attachment_position(v3f(0,0,0)), m_attachment_rotation(v3f(0,0,0)), @@ -614,13 +614,28 @@ bool GenericCAO::collideWithObjects() void GenericCAO::initialize(const std::string &data) { infostream<<"GenericCAO: Got init data"<<std::endl; + processInitData(data); + + if (m_is_player) { + // Check if it's the current player + LocalPlayer *player = m_env->getLocalPlayer(); + if (player && strcmp(player->getName(), m_name.c_str()) == 0) { + m_is_local_player = true; + m_is_visible = false; + player->setCAO(this); + } + m_env->addPlayerName(m_name.c_str()); + } +} + +void GenericCAO::processInitData(const std::string &data) +{ std::istringstream is(data, std::ios::binary); int num_messages = 0; // version u8 version = readU8(is); // check version - if(version == 1) // In PROTOCOL_VERSION 14 - { + if (version == 1) { // In PROTOCOL_VERSION 14 m_name = deSerializeString(is); m_is_player = readU8(is); m_id = readS16(is); @@ -628,46 +643,26 @@ void GenericCAO::initialize(const std::string &data) m_yaw = readF1000(is); m_hp = readS16(is); num_messages = readU8(is); - } - else if(version == 0) // In PROTOCOL_VERSION 13 - { + } else if (version == 0) { // In PROTOCOL_VERSION 13 m_name = deSerializeString(is); m_is_player = readU8(is); m_position = readV3F1000(is); m_yaw = readF1000(is); m_hp = readS16(is); num_messages = readU8(is); - } - else - { + } else { errorstream<<"GenericCAO: Unsupported init data version" <<std::endl; return; } - for(int i=0; i<num_messages; i++) - { + for (int i = 0; i < num_messages; i++) { std::string message = deSerializeLongString(is); processMessage(message); } pos_translator.init(m_position); updateNodePos(); - - if(m_is_player) - { - Player *player = m_env->getPlayer(m_name.c_str()); - if(player && player->isLocal()) - { - m_is_local_player = true; - m_is_visible = false; - LocalPlayer* localplayer = dynamic_cast<LocalPlayer*>(player); - - assert( localplayer != NULL ); - localplayer->setCAO(this); - } - m_env->addPlayerName(m_name.c_str()); - } } GenericCAO::~GenericCAO() @@ -804,7 +799,7 @@ void GenericCAO::removeFromScene(bool permanent) } } -void GenericCAO::addToScene(scene::ISceneManager *smgr, +void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, IrrlichtDevice *irr) { m_smgr = smgr; @@ -840,54 +835,50 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, setBillboardTextureMatrix(m_spritenode, txs, tys, 0, 0); } - } - else if(m_prop.visual == "upright_sprite") { + } else if (m_prop.visual == "upright_sprite") { scene::SMesh *mesh = new scene::SMesh(); - double dx = BS*m_prop.visual_size.X/2; - double dy = BS*m_prop.visual_size.Y/2; - { // Front - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + double dx = BS * m_prop.visual_size.X / 2; + double dy = BS * m_prop.visual_size.Y / 2; u8 li = m_last_light; - video::SColor c(255,li,li,li); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-dx,-dy,0, 0,0,0, c, 0,1), - video::S3DVertex(dx,-dy,0, 0,0,0, c, 1,1), - video::S3DVertex(dx,dy,0, 0,0,0, c, 1,0), - video::S3DVertex(-dx,dy,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); + video::SColor c(255, li, li, li); + + { // Front + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::S3DVertex vertices[4] = { + video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1), + video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1), + video::S3DVertex( dx, dy, 0, 0,0,0, c, 0,0), + video::S3DVertex(-dx, dy, 0, 0,0,0, c, 1,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); } { // Back - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - u8 li = m_last_light; - video::SColor c(255,li,li,li); - video::S3DVertex vertices[4] = - { - video::S3DVertex(dx,-dy,0, 0,0,0, c, 1,1), - video::S3DVertex(-dx,-dy,0, 0,0,0, c, 0,1), - video::S3DVertex(-dx,dy,0, 0,0,0, c, 0,0), - video::S3DVertex(dx,dy,0, 0,0,0, c, 1,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::S3DVertex vertices[4] = { + video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1), + video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1), + video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0), + video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); } m_meshnode = smgr->addMeshSceneNode(mesh, NULL); m_meshnode->grab(); @@ -1057,7 +1048,9 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) PlayerControl controls = player->getPlayerControl(); bool walking = false; - if(controls.up || controls.down || controls.left || controls.right) + if (controls.up || controls.down || controls.left || controls.right || + controls.forw_move_joystick_axis != 0.f || + controls.sidew_move_joystick_axis != 0.f) walking = true; f32 new_speed = player->local_animation_speed; @@ -1508,10 +1501,8 @@ void GenericCAO::updateBonePosition() return; m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render - for(std::map<std::string, - core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); - ii != m_bone_position.end(); ++ii) - { + for(UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { std::string bone_name = (*ii).first; v3f bone_pos = (*ii).second.X; v3f bone_rot = (*ii).second.Y; @@ -1572,8 +1563,7 @@ void GenericCAO::processMessage(const std::string &data) std::istringstream is(data, std::ios::binary); // command u8 cmd = readU8(is); - if(cmd == GENERIC_CMD_SET_PROPERTIES) - { + if (cmd == GENERIC_CMD_SET_PROPERTIES) { m_prop = gob_read_set_properties(is); m_selection_box = m_prop.collisionbox; @@ -1592,9 +1582,7 @@ void GenericCAO::processMessage(const std::string &data) m_prop.nametag = m_name; expireVisuals(); - } - else if(cmd == GENERIC_CMD_UPDATE_POSITION) - { + } else if (cmd == GENERIC_CMD_UPDATE_POSITION) { // Not sent by the server if this object is an attachment. // We might however get here if the server notices the object being detached before the client. m_position = readV3F1000(is); @@ -1624,12 +1612,10 @@ void GenericCAO::processMessage(const std::string &data) pos_translator.init(m_position); } updateNodePos(); - } - else if(cmd == GENERIC_CMD_SET_TEXTURE_MOD) { + } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) { std::string mod = deSerializeString(is); updateTextures(mod); - } - else if(cmd == GENERIC_CMD_SET_SPRITE) { + } else if (cmd == GENERIC_CMD_SET_SPRITE) { v2s16 p = readV2S16(is); int num_frames = readU16(is); float framelength = readF1000(is); @@ -1641,8 +1627,7 @@ void GenericCAO::processMessage(const std::string &data) m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; updateTexturePos(); - } - else if(cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) { + } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) { float override_speed = readF1000(is); float override_jump = readF1000(is); float override_gravity = readF1000(is); @@ -1660,8 +1645,7 @@ void GenericCAO::processMessage(const std::string &data) player->physics_override_sneak = sneak; player->physics_override_sneak_glitch = sneak_glitch; } - } - else if(cmd == GENERIC_CMD_SET_ANIMATION) { + } else if (cmd == GENERIC_CMD_SET_ANIMATION) { // TODO: change frames send as v2s32 value v2f range = readV2F1000(is); if (!m_is_local_player) { @@ -1695,8 +1679,7 @@ void GenericCAO::processMessage(const std::string &data) updateAnimation(); } } - } - else if(cmd == GENERIC_CMD_SET_BONE_POSITION) { + } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) { std::string bone = deSerializeString(is); v3f position = readV3F1000(is); v3f rotation = readV3F1000(is); @@ -1729,8 +1712,7 @@ void GenericCAO::processMessage(const std::string &data) } updateAttachments(); - } - else if(cmd == GENERIC_CMD_PUNCHED) { + } else if (cmd == GENERIC_CMD_PUNCHED) { /*s16 damage =*/ readS16(is); s16 result_hp = readS16(is); @@ -1758,8 +1740,7 @@ void GenericCAO::processMessage(const std::string &data) updateTextures("^[brighten"); } } - } - else if(cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { + } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { m_armor_groups.clear(); int armor_groups_size = readU16(is); for(int i=0; i<armor_groups_size; i++) @@ -1775,6 +1756,19 @@ void GenericCAO::processMessage(const std::string &data) if (m_nametag != NULL) { m_nametag->nametag_color = m_prop.nametag_color; } + } else if (cmd == GENERIC_CMD_SPAWN_INFANT) { + u16 child_id = readU16(is); + u8 type = readU8(is); + + if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) { + childobj->processInitData(deSerializeLongString(is)); + } else { + m_env->addActiveObject(child_id, type, deSerializeLongString(is)); + } + } else { + warningstream << FUNCTION_NAME + << ": unknown command or outdated client \"" + << cmd << std::endl; } } diff --git a/src/content_cao.h b/src/content_cao.h index a166ff494..a158e8296 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -68,7 +68,6 @@ private: // scene::ISceneManager *m_smgr; IrrlichtDevice *m_irr; - Camera* m_camera; IGameDef *m_gamedef; aabb3f m_selection_box; scene::IMeshSceneNode *m_meshnode; @@ -91,7 +90,7 @@ private: 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 + UNORDERED_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; v3f m_attachment_rotation; @@ -126,6 +125,8 @@ public: void initialize(const std::string &data); + void processInitData(const std::string &data); + ClientActiveObject *getParent(); bool getCollisionBox(aabb3f *toset); @@ -135,6 +136,10 @@ public: aabb3f *getSelectionBox(); v3f getPosition(); + inline float getYaw() const + { + return m_yaw; + } scene::ISceneNode *getSceneNode(); @@ -206,7 +211,7 @@ public: float time_from_last_punch=1000000); std::string debugInfoText(); - + std::string infoText() { return m_prop.infotext; diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 6a83bd8f3..8ce0f1e0a 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IMeshManipulator.h> #include "gamedef.h" #include "log.h" +#include "noise.h" // Create a cuboid. @@ -171,6 +172,15 @@ static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef, *neighbors |= v; } +// For use in mapblock_mesh_generate_special +// X,Y,Z of position must be -1,0,1 +// This expression is a simplification of +// 3 * 3 * (pos.X + 1) + 3 * (pos.Y + 1) + (pos.Z + 1) +static inline int NeighborToIndex(const v3s16 &pos) +{ + return 9 * pos.X + 3 * pos.Y + pos.Z + 13; +} + /* TODO: Fix alpha blending for special nodes Currently only the last element rendered is blended correct @@ -400,9 +410,14 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Neighbor liquid levels (key = relative position) // Includes current node - std::map<v3s16, f32> neighbor_levels; - std::map<v3s16, content_t> neighbor_contents; - std::map<v3s16, u8> neighbor_flags; + + struct NeighborData { + f32 level; + content_t content; + u8 flags; + }; + NeighborData neighbor_data_matrix[27]; + const u8 neighborflag_top_is_same_liquid = 0x01; v3s16 neighbor_dirs[9] = { v3s16(0,0,0), @@ -448,9 +463,12 @@ void mapblock_mesh_generate_special(MeshMakeData *data, flags |= neighborflag_top_is_same_liquid; } - neighbor_levels[neighbor_dirs[i]] = level; - neighbor_contents[neighbor_dirs[i]] = content; - neighbor_flags[neighbor_dirs[i]] = flags; + NeighborData &neighbor_data = + neighbor_data_matrix[NeighborToIndex(neighbor_dirs[i])]; + + neighbor_data.level = level; + neighbor_data.content = content; + neighbor_data.flags = flags; } // Corner heights (average between four liquids) @@ -471,10 +489,12 @@ void mapblock_mesh_generate_special(MeshMakeData *data, for(u32 j=0; j<4; j++) { v3s16 neighbordir = cornerdir - halfdirs[j]; - content_t content = neighbor_contents[neighbordir]; + + NeighborData &neighbor_data = + neighbor_data_matrix[NeighborToIndex(neighbordir)]; + content_t content = neighbor_data.content; // If top is liquid, draw starting from top of node - if(neighbor_flags[neighbordir] & - neighborflag_top_is_same_liquid) + if (neighbor_data.flags & neighborflag_top_is_same_liquid) { cornerlevel = 0.5*BS; valid_count = 1; @@ -490,7 +510,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, // Flowing liquid has level information else if(content == c_flowing) { - cornerlevel += neighbor_levels[neighbordir]; + cornerlevel += neighbor_data.level; valid_count++; } else if(content == CONTENT_AIR) @@ -525,15 +545,17 @@ void mapblock_mesh_generate_special(MeshMakeData *data, { v3s16 dir = side_dirs[i]; + NeighborData& neighbor_data = + neighbor_data_matrix[NeighborToIndex(dir)]; /* If our topside is liquid and neighbor's topside is liquid, don't draw side face */ - if(top_is_same_liquid && - neighbor_flags[dir] & neighborflag_top_is_same_liquid) + if (top_is_same_liquid && + neighbor_data.flags & neighborflag_top_is_same_liquid) continue; - content_t neighbor_content = neighbor_contents[dir]; + content_t neighbor_content = neighbor_data.content; const ContentFeatures &n_feat = nodedef->get(neighbor_content); // Don't draw face if neighbor is blocking the view @@ -1104,6 +1126,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, break;} case NDT_PLANTLIKE: { + PseudoRandom rng(x<<8 | z | y<<16); + TileSpec tile = getNodeTileN(n, p, 0, data); tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; @@ -1111,9 +1135,18 @@ void mapblock_mesh_generate_special(MeshMakeData *data, video::SColor c = MapBlock_LightColor(255, l, f.light_source); float s = BS / 2 * f.visual_scale; + // add sqrt(2) visual scale + if ((f.param_type_2 == CPT2_MESHOPTIONS) && ((n.param2 & 0x10) != 0)) + s *= 1.41421; + + float random_offset_X = .0; + float random_offset_Z = .0; + if ((f.param_type_2 == CPT2_MESHOPTIONS) && ((n.param2 & 0x8) != 0)) { + random_offset_X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); + random_offset_Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); + } - for (int j = 0; j < 2; j++) - { + for (int j = 0; j < 4; j++) { video::S3DVertex vertices[4] = { video::S3DVertex(-s,-BS/2, 0, 0,0,0, c, 0,1), @@ -1121,28 +1154,146 @@ void mapblock_mesh_generate_special(MeshMakeData *data, video::S3DVertex( s,-BS/2 + s*2,0, 0,0,0, c, 1,0), video::S3DVertex(-s,-BS/2 + s*2,0, 0,0,0, c, 0,0), }; + float rotate_degree = 0; + u8 p2mesh = 0; if (f.param_type_2 == CPT2_DEGROTATE) rotate_degree = n.param2 * 2; - - if (j == 0) { - for(u16 i = 0; i < 4; i++) - vertices[i].Pos.rotateXZBy(46 + rotate_degree); - } else if (j == 1) { - for(u16 i = 0; i < 4; i++) - vertices[i].Pos.rotateXZBy(-44 + rotate_degree); + if (f.param_type_2 != CPT2_MESHOPTIONS) { + if (j == 0) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(46 + rotate_degree); + } else if (j == 1) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(-44 + rotate_degree); + } + } else { + p2mesh = n.param2 & 0x7; + switch (p2mesh) { + case 0: + // x + if (j == 0) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(46); + } else if (j == 1) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(-44); + } + break; + case 1: + // + + if (j == 0) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(91); + } else if (j == 1) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(1); + } + break; + case 2: + // * + if (j == 0) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(121); + } else if (j == 1) { + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(241); + } else { // (j == 2) + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(1); + } + break; + case 3: + // # + switch (j) { + case 0: + for (u16 i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(1); + vertices[i].Pos.Z += BS / 4; + } + break; + case 1: + for (u16 i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(91); + vertices[i].Pos.X += BS / 4; + } + break; + case 2: + for (u16 i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(181); + vertices[i].Pos.Z -= BS / 4; + } + break; + case 3: + for (u16 i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(271); + vertices[i].Pos.X -= BS / 4; + } + break; + } + break; + case 4: + // outward leaning #-like + switch (j) { + case 0: + for (u16 i = 2; i < 4; i++) + vertices[i].Pos.Z -= BS / 2; + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(1); + break; + case 1: + for (u16 i = 2; i < 4; i++) + vertices[i].Pos.Z -= BS / 2; + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(91); + break; + case 2: + for (u16 i = 2; i < 4; i++) + vertices[i].Pos.Z -= BS / 2; + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(181); + break; + case 3: + for (u16 i = 2; i < 4; i++) + vertices[i].Pos.Z -= BS / 2; + for (u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(271); + break; + } + break; + } } - for (int i = 0; i < 4; i++) - { + for (int i = 0; i < 4; i++) { vertices[i].Pos *= f.visual_scale; vertices[i].Pos.Y += BS/2 * (f.visual_scale - 1); vertices[i].Pos += intToFloat(p, BS); + // move to a random spot to avoid moire + if ((f.param_type_2 == CPT2_MESHOPTIONS) && ((n.param2 & 0x8) != 0)) { + vertices[i].Pos.X += random_offset_X; + vertices[i].Pos.Z += random_offset_Z; + } + // randomly move each face up/down + if ((f.param_type_2 == CPT2_MESHOPTIONS) && ((n.param2 & 0x20) != 0)) { + PseudoRandom yrng(j | x<<16 | z<<8 | y<<24 ); + vertices[i].Pos.Y -= BS * ((yrng.next() % 16 / 16.0) * 0.125); + } } u16 indices[] = {0, 1, 2, 2, 3, 0}; // Add to mesh collector collector.append(tile, vertices, 4, indices, 6); + + // stop adding faces for meshes with less than 4 faces + if (f.param_type_2 == CPT2_MESHOPTIONS) { + if (((p2mesh == 0) || (p2mesh == 1)) && (j == 1)) + break; + else if ((p2mesh == 2) && (j == 2)) + break; + } else if (j == 1) { + break; + } + } break;} case NDT_FIRELIKE: @@ -1376,8 +1527,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, 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); + const ContentFeatures &def_xy = nodedef->get(n_xy); + const 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 diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp index 7f4264d8e..79a32b6bf 100644 --- a/src/content_nodemeta.cpp +++ b/src/content_nodemeta.cpp @@ -186,7 +186,7 @@ void content_nodemeta_deserialize_legacy(std::istream &is, meta->set(p, data); if(need_timer) - timers->set(p, NodeTimer(1., 0.)); + timers->set(NodeTimer(1., 0., p)); } } diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 53bf3154f..77ab51a02 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" // For compressZlib #include "tool.h" // For ToolCapabilities #include "gamedef.h" -#include "player.h" +#include "remoteplayer.h" #include "server.h" #include "scripting_game.h" #include "genericobject.h" @@ -118,14 +118,12 @@ LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", ""); LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, const std::string &state): - ServerActiveObject(env, pos), + UnitSAO(env, pos), m_init_name(name), m_init_state(state), m_registered(false), - m_hp(-1), m_velocity(0,0,0), m_acceleration(0,0,0), - m_yaw(0), m_properties_sent(true), m_last_sent_yaw(0), m_last_sent_position(0,0,0), @@ -156,6 +154,11 @@ LuaEntitySAO::~LuaEntitySAO() if(m_registered){ m_env->getScriptIface()->luaentity_Remove(m_id); } + + for (UNORDERED_SET<u32>::iterator it = m_attached_particle_spawners.begin(); + it != m_attached_particle_spawners.end(); ++it) { + m_env->deleteParticleSpawner(*it, false); + } } void LuaEntitySAO::addedToEnvironment(u32 dtime_s) @@ -175,6 +178,8 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) // Activate entity, supplying serialized state m_env->getScriptIface()-> luaentity_Activate(m_id, m_init_state.c_str(), dtime_s); + } else { + m_prop.infotext = m_init_name; } } @@ -345,8 +350,10 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) if(m_bone_position_sent == false){ m_bone_position_sent = true; - for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); @@ -376,15 +383,30 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) writeF1000(os, m_yaw); writeS16(os, m_hp); - 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( + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + msg_os << serializeLongString(gob_cmd_update_animation( m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 - for(std::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 + for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y)); // m_bone_position.size + } + msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, + m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 + int message_count = 4 + m_bone_position.size(); + for (UNORDERED_SET<int>::const_iterator ii = m_attachment_child_ids.begin(); + (ii != m_attachment_child_ids.end()); ++ii) { + if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { + message_count++; + msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(), + obj->getClientInitializationData(protocol_version))); + } } - os<<serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 + + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); } else { @@ -492,7 +514,7 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker) m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); } -void LuaEntitySAO::setPos(v3f pos) +void LuaEntitySAO::setPos(const v3f &pos) { if(isAttached()) return; @@ -612,7 +634,7 @@ void LuaEntitySAO::removeAttachmentChild(int child_id) m_attachment_child_ids.erase(child_id); } -std::set<int> LuaEntitySAO::getAttachmentChildIds() +UNORDERED_SET<int> LuaEntitySAO::getAttachmentChildIds() { return m_attachment_child_ids; } @@ -647,16 +669,6 @@ v3f LuaEntitySAO::getAcceleration() return m_acceleration; } -void LuaEntitySAO::setYaw(float yaw) -{ - m_yaw = yaw; -} - -float LuaEntitySAO::getYaw() -{ - return m_yaw; -} - void LuaEntitySAO::setTextureMod(const std::string &mod) { std::string str = gob_cmd_set_texture_mod(mod); @@ -745,10 +757,9 @@ bool LuaEntitySAO::collideWithObjects(){ // No prototype, PlayerSAO does not need to be deserialized -PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, - const std::set<std::string> &privs, bool is_singleplayer): - ServerActiveObject(env_, v3f(0,0,0)), - m_player(player_), +PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer): + UnitSAO(env_, v3f(0,0,0)), + m_player(NULL), m_peer_id(peer_id_), m_inventory(NULL), m_damage(0), @@ -760,7 +771,6 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_position_not_sent(false), m_armor_groups_sent(false), m_properties_sent(true), - m_privs(privs), m_is_singleplayer(is_singleplayer), m_animation_speed(0), m_animation_blend(0), @@ -769,6 +779,10 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_bone_position_sent(false), m_attachment_parent_id(0), m_attachment_sent(false), + m_breath(PLAYER_MAX_BREATH), + m_pitch(0), + m_fov(0), + m_wanted_range(0), // public m_physics_override_speed(1), m_physics_override_jump(1), @@ -777,10 +791,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_physics_override_sneak_glitch(true), m_physics_override_sent(false) { - 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; m_prop.hp_max = PLAYER_MAX_HP; @@ -799,13 +810,21 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, // end of default appearance m_prop.is_visible = true; m_prop.makes_footstep_sound = true; + m_hp = PLAYER_MAX_HP; } PlayerSAO::~PlayerSAO() { if(m_inventory != &m_player->inventory) delete m_inventory; +} +void PlayerSAO::initialize(RemotePlayer *player, const std::set<std::string> &privs) +{ + assert(player); + m_player = player; + m_privs = privs; + m_inventory = &m_player->inventory; } std::string PlayerSAO::getDescription() @@ -817,22 +836,22 @@ std::string PlayerSAO::getDescription() void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_player->getPosition()); + ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); m_player->peer_id = m_peer_id; - m_last_good_position = m_player->getPosition(); + m_last_good_position = m_base_position; } // Called before removing from environment void PlayerSAO::removingFromEnvironment() { ServerActiveObject::removingFromEnvironment(); - if(m_player->getPlayerSAO() == this) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; - m_env->savePlayer((RemotePlayer*)m_player); - m_env->removePlayer(m_player); + if (m_player->getPlayerSAO() == this) { + unlinkPlayerSessionAndSave(); + for (UNORDERED_SET<u32>::iterator it = m_attached_particle_spawners.begin(); + it != m_attached_particle_spawners.end(); ++it) { + m_env->deleteParticleSpawner(*it, false); + } } } @@ -851,31 +870,47 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os<<serializeString(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); //id - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); + writeV3F1000(os, m_base_position + v3f(0,BS*1,0)); + writeF1000(os, m_yaw); writeS16(os, getHP()); - 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( + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + msg_os << serializeLongString(gob_cmd_update_animation( m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 - for(std::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 + for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } - os<<serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 - os<<serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, + msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, + m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 + msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak, m_physics_override_sneak_glitch)); // 5 - os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6 (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. + // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. + msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6 + int message_count = 6 + m_bone_position.size(); + for (UNORDERED_SET<int>::const_iterator ii = m_attachment_child_ids.begin(); + ii != m_attachment_child_ids.end(); ++ii) { + if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { + message_count++; + msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(), + obj->getClientInitializationData(protocol_version))); + } + } + + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); } else { writeU8(os, 0); // version os<<serializeString(m_player->getName()); // name writeU8(os, 1); // is_player - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); + writeV3F1000(os, m_base_position + v3f(0,BS*1,0)); + writeF1000(os, m_yaw); writeS16(os, getHP()); writeU8(os, 2); // number of messages stuffed in here os<<serializeLongString(getPropertyPacket()); // message 1 @@ -921,7 +956,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_attachment_bone = ""; m_attachment_position = v3f(0,0,0); m_attachment_rotation = v3f(0,0,0); - m_player->setPosition(m_last_good_position); + setBasePosition(m_last_good_position); ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } @@ -943,14 +978,13 @@ void PlayerSAO::step(float dtime, bool send_recommended) // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally // If the object gets detached this comes into effect automatically from the last known origin - if(isAttached()) - { + if (isAttached()) { v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); m_last_good_position = pos; - m_player->setPosition(pos); + setBasePosition(pos); } - if(send_recommended == false) + if (!send_recommended) return; // If the object is attached client-side, don't waste bandwidth sending its position to clients @@ -962,12 +996,12 @@ void PlayerSAO::step(float dtime, bool send_recommended) if(isAttached()) // Just in case we ever do send attachment position too pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); else - pos = m_player->getPosition() + v3f(0,BS*1,0); + pos = m_base_position + v3f(0,BS*1,0); std::string str = gob_cmd_update_position( pos, v3f(0,0,0), v3f(0,0,0), - m_player->getYaw(), + m_yaw, true, false, update_interval @@ -977,7 +1011,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_armor_groups_sent == false) { + if (!m_armor_groups_sent) { m_armor_groups_sent = true; std::string str = gob_cmd_update_armor_groups( m_armor_groups); @@ -986,7 +1020,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_physics_override_sent == false){ + if (!m_physics_override_sent) { m_physics_override_sent = true; std::string str = gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, @@ -996,7 +1030,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_animation_sent == false){ + if (!m_animation_sent) { m_animation_sent = true; std::string str = gob_cmd_update_animation( m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); @@ -1005,19 +1039,22 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_bone_position_sent == false){ + if (!m_bone_position_sent) { m_bone_position_sent = true; - for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + for (UNORDERED_MAP<std::string, core::vector2d<v3f> >::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + std::string str = gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } - if(m_attachment_sent == false){ + if (!m_attachment_sent){ m_attachment_sent = true; - std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, + m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); @@ -1026,16 +1063,20 @@ void PlayerSAO::step(float dtime, bool send_recommended) void PlayerSAO::setBasePosition(const v3f &position) { + if (m_player && position != m_base_position) + m_player->setDirty(true); + // This needs to be ran for attachments too ServerActiveObject::setBasePosition(position); m_position_not_sent = true; } -void PlayerSAO::setPos(v3f pos) +void PlayerSAO::setPos(const v3f &pos) { if(isAttached()) return; - m_player->setPosition(pos); + + setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); @@ -1045,21 +1086,54 @@ void PlayerSAO::moveTo(v3f pos, bool continuous) { if(isAttached()) return; - m_player->setPosition(pos); + + setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } -void PlayerSAO::setYaw(float yaw) +void PlayerSAO::setYaw(const float yaw) +{ + if (m_player && yaw != m_yaw) + m_player->setDirty(true); + + UnitSAO::setYaw(yaw); +} + +void PlayerSAO::setFov(const float fov) +{ + if (m_player && fov != m_fov) + m_player->setDirty(true); + + m_fov = fov; +} + +void PlayerSAO::setWantedRange(const s16 range) +{ + if (m_player && range != m_wanted_range) + m_player->setDirty(true); + + m_wanted_range = range; +} + +void PlayerSAO::setYawAndSend(const float yaw) { - m_player->setYaw(yaw); + setYaw(yaw); ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } -void PlayerSAO::setPitch(float pitch) +void PlayerSAO::setPitch(const float pitch) +{ + if (m_player && pitch != m_pitch) + m_player->setDirty(true); + + m_pitch = pitch; +} + +void PlayerSAO::setPitchAndSend(const float pitch) { - m_player->setPitch(pitch); + setPitch(pitch); ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } @@ -1124,15 +1198,10 @@ int PlayerSAO::punch(v3f dir, return hitparams.wear; } -void PlayerSAO::rightClick(ServerActiveObject *clicker) +void PlayerSAO::rightClick(ServerActiveObject *) { } -s16 PlayerSAO::getHP() const -{ - return m_player->hp; -} - s16 PlayerSAO::readDamage() { s16 damage = m_damage; @@ -1142,10 +1211,9 @@ s16 PlayerSAO::readDamage() void PlayerSAO::setHP(s16 hp) { - s16 oldhp = m_player->hp; + s16 oldhp = m_hp; - s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, - hp - oldhp); + s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp); if (hp_change == 0) return; hp = oldhp + hp_change; @@ -1155,11 +1223,11 @@ void PlayerSAO::setHP(s16 hp) else if (hp > PLAYER_MAX_HP) hp = PLAYER_MAX_HP; - if(hp < oldhp && g_settings->getBool("enable_damage") == false) { + if (hp < oldhp && !g_settings->getBool("enable_damage")) { return; } - m_player->hp = hp; + m_hp = hp; if (oldhp > hp) m_damage += (oldhp - hp); @@ -1169,14 +1237,12 @@ void PlayerSAO::setHP(s16 hp) m_properties_sent = false; } -u16 PlayerSAO::getBreath() const +void PlayerSAO::setBreath(const u16 breath) { - return m_player->getBreath(); -} + if (m_player && breath != m_breath) + m_player->setDirty(true); -void PlayerSAO::setBreath(u16 breath) -{ - m_player->setBreath(breath); + m_breath = breath; } void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) @@ -1257,7 +1323,7 @@ void PlayerSAO::removeAttachmentChild(int child_id) m_attachment_child_ids.erase(child_id); } -std::set<int> PlayerSAO::getAttachmentChildIds() +UNORDERED_SET<int> PlayerSAO::getAttachmentChildIds() { return m_attachment_child_ids; } @@ -1293,6 +1359,42 @@ std::string PlayerSAO::getWieldList() const return "main"; } +ItemStack PlayerSAO::getWieldedItem() const +{ + const Inventory *inv = getInventory(); + ItemStack ret; + const InventoryList *mlist = inv->getList(getWieldList()); + if (mlist && getWieldIndex() < (s32)mlist->getSize()) + ret = mlist->getItem(getWieldIndex()); + if (ret.name.empty()) { + const InventoryList *hlist = inv->getList("hand"); + if (hlist) + ret = hlist->getItem(0); + } + return ret; +} + +bool PlayerSAO::setWieldedItem(const ItemStack &item) +{ + Inventory *inv = getInventory(); + if (inv) { + InventoryList *mlist = inv->getList(getWieldList()); + if (mlist) { + ItemStack olditem = mlist->getItem(getWieldIndex()); + if (olditem.name.empty()) { + InventoryList *hlist = inv->getList("hand"); + if (hlist) { + hlist->changeItem(0, item); + return true; + } + } + mlist->changeItem(getWieldIndex(), item); + return true; + } + } + return false; +} + int PlayerSAO::getWieldIndex() const { return m_wield_index; @@ -1305,15 +1407,20 @@ void PlayerSAO::setWieldIndex(int i) } } +// Erase the peer id and make the object for removal void PlayerSAO::disconnected() { m_peer_id = 0; m_removed = true; - if(m_player->getPlayerSAO() == this) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; - } +} + +void PlayerSAO::unlinkPlayerSessionAndSave() +{ + assert(m_player->getPlayerSAO() == this); + m_player->peer_id = 0; + m_env->savePlayer(m_player); + m_player->setPlayerSAO(NULL); + m_env->removePlayer(m_player); } std::string PlayerSAO::getPropertyPacket() @@ -1324,66 +1431,65 @@ std::string PlayerSAO::getPropertyPacket() bool PlayerSAO::checkMovementCheat() { + if (isAttached() || m_is_singleplayer || + g_settings->getBool("disable_anticheat")) { + m_last_good_position = m_base_position; + return false; + } + bool cheated = false; - if(isAttached() || m_is_singleplayer || - g_settings->getBool("disable_anticheat")) - { - m_last_good_position = m_player->getPosition(); + /* + Check player movements + + NOTE: Actually the server should handle player physics like the + client does and compare player's position to what is calculated + on our side. This is required when eg. players fly due to an + explosion. Altough a node-based alternative might be possible + too, and much more lightweight. + */ + + float player_max_speed = 0; + + if (m_privs.count("fast") != 0) { + // Fast speed + player_max_speed = m_player->movement_speed_fast * m_physics_override_speed; + } else { + // Normal speed + player_max_speed = m_player->movement_speed_walk * m_physics_override_speed; } - else - { - /* - Check player movements - - NOTE: Actually the server should handle player physics like the - client does and compare player's position to what is calculated - on our side. This is required when eg. players fly due to an - explosion. Altough a node-based alternative might be possible - too, and much more lightweight. - */ - - float player_max_speed = 0; - if(m_privs.count("fast") != 0){ - // Fast speed - player_max_speed = m_player->movement_speed_fast; - } else { - // Normal speed - player_max_speed = m_player->movement_speed_walk; - } - // Tolerance. With the lag pool we shouldn't need it. - //player_max_speed *= 2.5; - //player_max_speed_up *= 2.5; - - v3f diff = (m_player->getPosition() - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - float required_time = d_horiz/player_max_speed; - if(d_vert > 0 && d_vert/player_max_speed > required_time) - required_time = d_vert/player_max_speed; - if(m_move_pool.grab(required_time)){ - m_last_good_position = m_player->getPosition(); - } else { - actionstream<<"Player "<<m_player->getName() - <<" moved too fast; resetting position" - <<std::endl; - m_player->setPosition(m_last_good_position); - cheated = true; - } + // Tolerance. The lag pool does this a bit. + //player_max_speed *= 2.5; + + v3f diff = (m_base_position - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + float required_time = d_horiz / player_max_speed; + + if (d_vert > 0 && d_vert / player_max_speed > required_time) + required_time = d_vert / player_max_speed; // Moving upwards + + if (m_move_pool.grab(required_time)) { + m_last_good_position = m_base_position; + } else { + actionstream << "Player " << m_player->getName() + << " moved too fast; resetting position" + << std::endl; + setBasePosition(m_last_good_position); + cheated = true; } return cheated; } -bool PlayerSAO::getCollisionBox(aabb3f *toset) { - //update collision box - *toset = m_player->getCollisionbox(); - +bool PlayerSAO::getCollisionBox(aabb3f *toset) +{ + *toset = aabb3f(-BS * 0.30, 0.0, -BS * 0.30, BS * 0.30, BS * 1.75, BS * 0.30); toset->MinEdge += m_base_position; toset->MaxEdge += m_base_position; - return true; } -bool PlayerSAO::collideWithObjects(){ +bool PlayerSAO::collideWithObjects() +{ return true; } diff --git a/src/content_sao.h b/src/content_sao.h index 44d40d332..86255183d 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -22,14 +22,36 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverobject.h" #include "itemgroup.h" -#include "player.h" #include "object_properties.h" +#include "constants.h" + +class UnitSAO: public ServerActiveObject +{ +public: + UnitSAO(ServerEnvironment *env, v3f pos): + ServerActiveObject(env, pos), + m_hp(-1), m_yaw(0) {} + virtual ~UnitSAO() {} + + virtual void setYaw(const float yaw) { m_yaw = yaw; } + float getYaw() const { return m_yaw; }; + f32 getRadYaw() const { return m_yaw * core::DEGTORAD; } + // Deprecated + f32 getRadYawDep() const { return (m_yaw + 90.) * core::DEGTORAD; } + + s16 getHP() const { return m_hp; } + // Use a function, if isDead can be defined by other conditions + bool isDead() const { return m_hp == 0; } +protected: + s16 m_hp; + float m_yaw; +}; /* LuaEntitySAO needs some internals exposed. */ -class LuaEntitySAO : public ServerActiveObject +class LuaEntitySAO : public UnitSAO { public: LuaEntitySAO(ServerEnvironment *env, v3f pos, @@ -51,7 +73,7 @@ public: ServerActiveObject *puncher=NULL, float time_from_last_punch=1000000); void rightClick(ServerActiveObject *clicker); - void setPos(v3f pos); + void setPos(const v3f &pos); void moveTo(v3f pos, bool continuous); float getMinimumSavedMovement(); std::string getDescription(); @@ -67,7 +89,7 @@ public: 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(); + UNORDERED_SET<int> getAttachmentChildIds(); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); /* LuaEntitySAO-specific */ @@ -75,8 +97,7 @@ public: v3f getVelocity(); void setAcceleration(v3f acceleration); v3f getAcceleration(); - void setYaw(float yaw); - float getYaw(); + void setTextureMod(const std::string &mod); void setSprite(v2s16 p, int num_frames, float framelength, bool select_horiz_by_yawpitch); @@ -92,10 +113,9 @@ private: bool m_registered; struct ObjectProperties m_prop; - s16 m_hp; v3f m_velocity; v3f m_acceleration; - float m_yaw; + ItemGroupList m_armor_groups; bool m_properties_sent; @@ -112,11 +132,11 @@ private: bool m_animation_loop; bool m_animation_sent; - std::map<std::string, core::vector2d<v3f> > m_bone_position; + UNORDERED_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; + UNORDERED_SET<int> m_attachment_child_ids; std::string m_attachment_bone; v3f m_attachment_position; v3f m_attachment_rotation; @@ -157,11 +177,12 @@ public: } }; -class PlayerSAO : public ServerActiveObject +class RemotePlayer; + +class PlayerSAO : public UnitSAO { public: - PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, - const std::set<std::string> &privs, bool is_singleplayer); + PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer); ~PlayerSAO(); ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; } @@ -181,10 +202,22 @@ public: bool isAttached(); void step(float dtime, bool send_recommended); void setBasePosition(const v3f &position); - void setPos(v3f pos); + void setPos(const v3f &pos); void moveTo(v3f pos, bool continuous); - void setYaw(float); - void setPitch(float); + void setYaw(const float yaw); + // Data should not be sent at player initialization + void setYawAndSend(const float yaw); + void setPitch(const float pitch); + // Data should not be sent at player initialization + void setPitchAndSend(const float pitch); + f32 getPitch() const { return m_pitch; } + f32 getRadPitch() const { return m_pitch * core::DEGTORAD; } + // Deprecated + f32 getRadPitchDep() const { return -1.0 * m_pitch * core::DEGTORAD; } + void setFov(const float pitch); + f32 getFov() const { return m_fov; } + void setWantedRange(const s16 range); + s16 getWantedRange() const { return m_wanted_range; } /* Interaction interface @@ -195,11 +228,11 @@ public: ServerActiveObject *puncher, float time_from_last_punch); void rightClick(ServerActiveObject *clicker); - s16 getHP() const; void setHP(s16 hp); + void setHPRaw(s16 hp) { m_hp = hp; } s16 readDamage(); - u16 getBreath() const; - void setBreath(u16 breath); + u16 getBreath() const { return m_breath; } + void setBreath(const u16 breath); void setArmorGroups(const ItemGroupList &armor_groups); ItemGroupList getArmorGroups(); void setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop); @@ -210,7 +243,7 @@ public: 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(); + UNORDERED_SET<int> getAttachmentChildIds(); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); @@ -222,6 +255,8 @@ public: const Inventory* getInventory() const; InventoryLocation getInventoryLocation() const; std::string getWieldList() const; + ItemStack getWieldedItem() const; + bool setWieldedItem(const ItemStack &item); int getWieldIndex() const; void setWieldIndex(int i); @@ -231,14 +266,8 @@ public: void disconnected(); - Player* getPlayer() - { - return m_player; - } - u16 getPeerID() const - { - return m_peer_id; - } + RemotePlayer *getPlayer() { return m_player; } + u16 getPeerID() const { return m_peer_id; } // Cheat prevention @@ -288,10 +317,16 @@ public: bool getCollisionBox(aabb3f *toset); bool collideWithObjects(); + void initialize(RemotePlayer *player, const std::set<std::string> &privs); + + v3f getEyePosition() const { return m_base_position + getEyeOffset(); } + v3f getEyeOffset() const { return v3f(0, BS * 1.625f, 0); } + private: std::string getPropertyPacket(); + void unlinkPlayerSessionAndSave(); - Player *m_player; + RemotePlayer *m_player; u16 m_peer_id; Inventory *m_inventory; s16 m_damage; @@ -321,17 +356,20 @@ private: 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 + // Stores position and rotation for each bone name + UNORDERED_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; + UNORDERED_SET<int> m_attachment_child_ids; std::string m_attachment_bone; v3f m_attachment_position; v3f m_attachment_rotation; bool m_attachment_sent; - - + u16 m_breath; + f32 m_pitch; + f32 m_fov; + s16 m_wanted_range; public: float m_physics_override_speed; float m_physics_override_jump; diff --git a/src/convert_json.h b/src/convert_json.h index 6732fcfa3..55321af5f 100644 --- a/src/convert_json.h +++ b/src/convert_json.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef __CONVERT_JSON_H__ #define __CONVERT_JSON_H__ -#include "json/json.h" +#include <json/json.h> struct ModStoreMod; struct ModStoreModDetails; diff --git a/src/craftdef.cpp b/src/craftdef.cpp index d3f1edaf9..45d3018a7 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -960,6 +960,96 @@ public: return recipes; } + + virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef) + { + std::map<std::string, std::vector<CraftDefinition*> >::iterator vec_iter = + m_output_craft_definitions.find(output.item); + + if (vec_iter == m_output_craft_definitions.end()) + return false; + + std::vector<CraftDefinition*> &vec = vec_iter->second; + for (std::vector<CraftDefinition*>::iterator i = vec.begin(); + i != vec.end(); ++i) { + CraftDefinition *def = *i; + // Recipes are not yet hashed at this point + std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]; + std::vector<CraftDefinition*> new_vec_by_input; + /* We will preallocate necessary memory addresses, so we don't need to reallocate them later. + This would save us some performance. */ + new_vec_by_input.reserve(unhashed_inputs_vec.size()); + for (std::vector<CraftDefinition*>::iterator i2 = unhashed_inputs_vec.begin(); + i2 != unhashed_inputs_vec.end(); ++i2) { + if (def != *i2) { + new_vec_by_input.push_back(*i2); + } + } + m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input); + } + m_output_craft_definitions.erase(output.item); + return true; + } + + virtual bool clearCraftRecipesByInput(CraftMethod craft_method, unsigned int craft_grid_width, + const std::vector<std::string> &recipe, IGameDef *gamedef) + { + bool all_empty = true; + for (std::vector<std::string>::size_type i = 0; + i < recipe.size(); i++) { + if (!recipe[i].empty()) { + all_empty = false; + break; + } + } + if (all_empty) + return false; + + CraftInput input(craft_method, craft_grid_width, craftGetItems(recipe, gamedef)); + // Recipes are not yet hashed at this point + std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]; + std::vector<CraftDefinition*> new_vec_by_input; + bool got_hit = false; + for (std::vector<CraftDefinition*>::size_type + i = unhashed_inputs_vec.size(); i > 0; i--) { + CraftDefinition *def = unhashed_inputs_vec[i - 1]; + /* If the input doesn't match the recipe definition, this recipe definition later + will be added back in source map. */ + if (!def->check(input, gamedef)) { + new_vec_by_input.push_back(def); + continue; + } + CraftOutput output = def->getOutput(input, gamedef); + got_hit = true; + std::map<std::string, std::vector<CraftDefinition*> >::iterator + vec_iter = m_output_craft_definitions.find(output.item); + if (vec_iter == m_output_craft_definitions.end()) + continue; + std::vector<CraftDefinition*> &vec = vec_iter->second; + std::vector<CraftDefinition*> new_vec_by_output; + /* We will preallocate necessary memory addresses, so we don't need + to reallocate them later. This would save us some performance. */ + new_vec_by_output.reserve(vec.size()); + for (std::vector<CraftDefinition*>::iterator i = vec.begin(); + i != vec.end(); ++i) { + /* If pointers from map by input and output are not same, + we will add 'CraftDefinition*' to a new vector. */ + if (def != *i) { + /* Adding dereferenced iterator value (which are + 'CraftDefinition' reference) to a new vector. */ + new_vec_by_output.push_back(*i); + } + } + // Swaps assigned to current key value with new vector for output map. + m_output_craft_definitions[output.item].swap(new_vec_by_output); + } + if (got_hit) + // Swaps value with new vector for input map. + m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input); + + return got_hit; + } + virtual std::string dump() const { std::ostringstream os(std::ios::binary); diff --git a/src/craftdef.h b/src/craftdef.h index cebb2d7ae..695ee0c2c 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -426,6 +426,10 @@ public: virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, IGameDef *gamedef, unsigned limit=0) const=0; + virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef) = 0; + virtual bool clearCraftRecipesByInput(CraftMethod craft_method, + unsigned int craft_grid_width, const std::vector<std::string> &recipe, IGameDef *gamedef) = 0; + // Print crafting recipes for debugging virtual std::string dump() const=0; diff --git a/src/database-dummy.cpp b/src/database-dummy.cpp index b38db1fb9..ef2148f70 100644 --- a/src/database-dummy.cpp +++ b/src/database-dummy.cpp @@ -30,13 +30,16 @@ bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) return true; } -std::string Database_Dummy::loadBlock(const v3s16 &pos) +void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block) { s64 i = getBlockAsInteger(pos); std::map<s64, std::string>::iterator it = m_database.find(i); - if (it == m_database.end()) - return ""; - return it->second; + if (it == m_database.end()) { + *block = ""; + return; + } + + *block = it->second; } bool Database_Dummy::deleteBlock(const v3s16 &pos) diff --git a/src/database-dummy.h b/src/database-dummy.h index 0cf56928e..72100edd0 100644 --- a/src/database-dummy.h +++ b/src/database-dummy.h @@ -28,10 +28,10 @@ with this program; if not, write to the Free Software Foundation, Inc., class Database_Dummy : public Database { public: - 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); + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector<v3s16> &dst); private: std::map<s64, std::string> m_database; diff --git a/src/database-leveldb.cpp b/src/database-leveldb.cpp index acd0fd1eb..4a4904c6a 100644 --- a/src/database-leveldb.cpp +++ b/src/database-leveldb.cpp @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define ENSURE_STATUS_OK(s) \ if (!(s).ok()) { \ - throw FileNotGoodException(std::string("LevelDB error: ") + \ + throw DatabaseException(std::string("LevelDB error: ") + \ (s).ToString()); \ } @@ -65,16 +65,13 @@ bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) return true; } -std::string Database_LevelDB::loadBlock(const v3s16 &pos) +void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) { std::string datastr; leveldb::Status status = m_database->Get(leveldb::ReadOptions(), i64tos(getBlockAsInteger(pos)), &datastr); - if(status.ok()) - return datastr; - else - return ""; + *block = (status.ok()) ? datastr : ""; } bool Database_LevelDB::deleteBlock(const v3s16 &pos) diff --git a/src/database-leveldb.h b/src/database-leveldb.h index 4afe2fdc7..3993db0c3 100644 --- a/src/database-leveldb.h +++ b/src/database-leveldb.h @@ -34,10 +34,10 @@ public: 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); + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector<v3s16> &dst); private: leveldb::DB *m_database; diff --git a/src/database-postgresql.cpp b/src/database-postgresql.cpp new file mode 100644 index 000000000..3b6b42aea --- /dev/null +++ b/src/database-postgresql.cpp @@ -0,0 +1,286 @@ +/* +Copyright (C) 2016 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 "config.h" + +#if USE_POSTGRESQL + +#include "database-postgresql.h" + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + // Without this some of the network functions are not found on mingw + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #include <windows.h> + #include <winsock2.h> +#else +#include <netinet/in.h> +#endif + +#include "log.h" +#include "exceptions.h" +#include "settings.h" + +Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) : + m_connect_string(""), + m_conn(NULL), + m_pgversion(0) +{ + if (!conf.getNoEx("pgsql_connection", m_connect_string)) { + throw SettingNotFoundException( + "Set pgsql_connection string in world.mt to " + "use the postgresql backend\n" + "Notes:\n" + "pgsql_connection has the following form: \n" + "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user " + "password=mt_password dbname=minetest_world\n" + "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and " + "DELETE rights on the database.\n" + "Don't create mt_user as a SUPERUSER!"); + } + + connectToDatabase(); +} + +Database_PostgreSQL::~Database_PostgreSQL() +{ + PQfinish(m_conn); +} + +void Database_PostgreSQL::connectToDatabase() +{ + m_conn = PQconnectdb(m_connect_string.c_str()); + + if (PQstatus(m_conn) != CONNECTION_OK) { + throw DatabaseException(std::string( + "PostgreSQL database error: ") + + PQerrorMessage(m_conn)); + } + + m_pgversion = PQserverVersion(m_conn); + + /* + * We are using UPSERT feature from PostgreSQL 9.5 + * to have the better performance, + * set the minimum version to 90500 + */ + if (m_pgversion < 90500) { + throw DatabaseException("PostgreSQL database error: " + "Server version 9.5 or greater required."); + } + + infostream << "PostgreSQL Database: Version " << m_pgversion + << " Connection made." << std::endl; + + createDatabase(); + initStatements(); +} + +void Database_PostgreSQL::verifyDatabase() +{ + if (PQstatus(m_conn) == CONNECTION_OK) + return; + + PQreset(m_conn); + ping(); +} + +void Database_PostgreSQL::ping() +{ + if (PQping(m_connect_string.c_str()) != PQPING_OK) { + throw DatabaseException(std::string( + "PostgreSQL database error: ") + + PQerrorMessage(m_conn)); + } +} + +bool Database_PostgreSQL::initialized() const +{ + return (PQstatus(m_conn) == CONNECTION_OK); +} + +void Database_PostgreSQL::initStatements() +{ + prepareStatement("read_block", + "SELECT data FROM blocks " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); + + prepareStatement("write_block", + "INSERT INTO blocks (posX, posY, posZ, data) VALUES " + "($1::int4, $2::int4, $3::int4, $4::bytea) " + "ON CONFLICT ON CONSTRAINT blocks_pkey DO " + "UPDATE SET data = $4::bytea"); + + prepareStatement("delete_block", "DELETE FROM blocks WHERE " + "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); + + prepareStatement("list_all_loadable_blocks", + "SELECT posX, posY, posZ FROM blocks"); +} + +PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) +{ + ExecStatusType statusType = PQresultStatus(result); + + switch (statusType) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + break; + case PGRES_FATAL_ERROR: + default: + throw DatabaseException( + std::string("PostgreSQL database error: ") + + PQresultErrorMessage(result)); + } + + if (clear) + PQclear(result); + + return result; +} + +void Database_PostgreSQL::createDatabase() +{ + PGresult *result = checkResults(PQexec(m_conn, + "SELECT relname FROM pg_class WHERE relname='blocks';"), + false); + + // If table doesn't exist, create it + if (!PQntuples(result)) { + static const char* dbcreate_sql = "CREATE TABLE blocks (" + "posX INT NOT NULL," + "posY INT NOT NULL," + "posZ INT NOT NULL," + "data BYTEA," + "PRIMARY KEY (posX,posY,posZ)" + ");"; + checkResults(PQexec(m_conn, dbcreate_sql)); + } + + PQclear(result); + + infostream << "PostgreSQL: Game Database was inited." << std::endl; +} + + +void Database_PostgreSQL::beginSave() +{ + verifyDatabase(); + checkResults(PQexec(m_conn, "BEGIN;")); +} + +void Database_PostgreSQL::endSave() +{ + checkResults(PQexec(m_conn, "COMMIT;")); +} + +bool Database_PostgreSQL::saveBlock(const v3s16 &pos, + const std::string &data) +{ + // Verify if we don't overflow the platform integer with the mapblock size + if (data.size() > INT_MAX) { + errorstream << "Database_PostgreSQL::saveBlock: Data truncation! " + << "data.size() over 0xFFFF (== " << data.size() + << ")" << std::endl; + return false; + } + + verifyDatabase(); + + s32 x, y, z; + x = htonl(pos.X); + y = htonl(pos.Y); + z = htonl(pos.Z); + + const void *args[] = { &x, &y, &z, data.c_str() }; + const int argLen[] = { + sizeof(x), sizeof(y), sizeof(z), (int)data.size() + }; + const int argFmt[] = { 1, 1, 1, 1 }; + + execPrepared("write_block", ARRLEN(args), args, argLen, argFmt); + return true; +} + +void Database_PostgreSQL::loadBlock(const v3s16 &pos, + std::string *block) +{ + verifyDatabase(); + + s32 x, y, z; + x = htonl(pos.X); + y = htonl(pos.Y); + z = htonl(pos.Z); + + const void *args[] = { &x, &y, &z }; + const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) }; + const int argFmt[] = { 1, 1, 1 }; + + PGresult *results = execPrepared("read_block", ARRLEN(args), args, + argLen, argFmt, false); + + *block = ""; + + if (PQntuples(results)) { + *block = std::string(PQgetvalue(results, 0, 0), + PQgetlength(results, 0, 0)); + } + + PQclear(results); +} + +bool Database_PostgreSQL::deleteBlock(const v3s16 &pos) +{ + verifyDatabase(); + + s32 x, y, z; + x = htonl(pos.X); + y = htonl(pos.Y); + z = htonl(pos.Z); + + const void *args[] = { &x, &y, &z }; + const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) }; + const int argFmt[] = { 1, 1, 1 }; + + execPrepared("read_block", ARRLEN(args), args, argLen, argFmt); + + return true; +} + +void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst) +{ + verifyDatabase(); + + PGresult *results = execPrepared("list_all_loadable_blocks", 0, + NULL, NULL, NULL, false, false); + + int numrows = PQntuples(results); + + for (int row = 0; row < numrows; ++row) { + dst.push_back(pg_to_v3s16(results, 0, 0)); + } + + PQclear(results); +} + +#endif // USE_POSTGRESQL diff --git a/src/database-postgresql.h b/src/database-postgresql.h new file mode 100644 index 000000000..1cfa544e3 --- /dev/null +++ b/src/database-postgresql.h @@ -0,0 +1,95 @@ +/* +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 DATABASE_POSTGRESQL_HEADER +#define DATABASE_POSTGRESQL_HEADER + +#include <string> +#include <libpq-fe.h> +#include "database.h" +#include "util/basic_macros.h" + +class Settings; + +class Database_PostgreSQL : public Database +{ +public: + Database_PostgreSQL(const Settings &conf); + ~Database_PostgreSQL(); + + void beginSave(); + void endSave(); + + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector<v3s16> &dst); + bool initialized() const; + +private: + // Database initialization + void connectToDatabase(); + void initStatements(); + void createDatabase(); + + inline void prepareStatement(const std::string &name, const std::string &sql) + { + checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); + } + + // Database connectivity checks + void ping(); + void verifyDatabase(); + + // Database usage + PGresult *checkResults(PGresult *res, bool clear = true); + + inline PGresult *execPrepared(const char *stmtName, const int paramsNumber, + const void **params, + const int *paramsLengths = NULL, const int *paramsFormats = NULL, + bool clear = true, bool nobinary = true) + { + return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber, + (const char* const*) params, paramsLengths, paramsFormats, + nobinary ? 1 : 0), clear); + } + + // Conversion helpers + inline int pg_to_int(PGresult *res, int row, int col) + { + return atoi(PQgetvalue(res, row, col)); + } + + inline v3s16 pg_to_v3s16(PGresult *res, int row, int col) + { + return v3s16( + pg_to_int(res, row, col), + pg_to_int(res, row, col + 1), + pg_to_int(res, row, col + 2) + ); + } + + // Attributes + std::string m_connect_string; + PGconn *m_conn; + int m_pgversion; +}; + +#endif + diff --git a/src/database-redis.cpp b/src/database-redis.cpp index 498e9d39a..3bcedad9b 100644 --- a/src/database-redis.cpp +++ b/src/database-redis.cpp @@ -46,11 +46,11 @@ Database_Redis::Database_Redis(Settings &conf) int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379; ctx = redisConnect(addr, port); if (!ctx) { - throw FileNotGoodException("Cannot allocate redis context"); + throw DatabaseException("Cannot allocate redis context"); } else if (ctx->err) { std::string err = std::string("Connection error: ") + ctx->errstr; redisFree(ctx); - throw FileNotGoodException(err); + throw DatabaseException(err); } } @@ -62,7 +62,7 @@ Database_Redis::~Database_Redis() void Database_Redis::beginSave() { redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI")); if (!reply) { - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'MULTI' failed: ") + ctx->errstr); } freeReplyObject(reply); @@ -71,7 +71,7 @@ void Database_Redis::beginSave() { void Database_Redis::endSave() { redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC")); if (!reply) { - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'EXEC' failed: ") + ctx->errstr); } freeReplyObject(reply); @@ -101,42 +101,45 @@ bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) return true; } -std::string Database_Redis::loadBlock(const v3s16 &pos) +void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) { std::string tmp = i64tos(getBlockAsInteger(pos)); redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str())); if (!reply) { - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'HGET %s %s' failed: ") + ctx->errstr); } + switch (reply->type) { case REDIS_REPLY_STRING: { - std::string str(reply->str, reply->len); + *block = std::string(reply->str, reply->len); // std::string copies the memory so this won't cause any problems freeReplyObject(reply); - return str; + return; } case REDIS_REPLY_ERROR: { std::string errstr(reply->str, reply->len); freeReplyObject(reply); errorstream << "loadBlock: loading block " << PP(pos) << " failed: " << errstr << std::endl; - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'HGET %s %s' errored: ") + errstr); } case REDIS_REPLY_NIL: { + *block = ""; // block not found in database freeReplyObject(reply); - return ""; + return; } } + errorstream << "loadBlock: loading block " << PP(pos) << " returned invalid reply type " << reply->type << ": " << std::string(reply->str, reply->len) << std::endl; freeReplyObject(reply); - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'HGET %s %s' gave invalid reply.")); } @@ -147,7 +150,7 @@ bool Database_Redis::deleteBlock(const v3s16 &pos) redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HDEL %s %s", hash.c_str(), tmp.c_str())); if (!reply) { - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'HDEL %s %s' failed: ") + ctx->errstr); } else if (reply->type == REDIS_REPLY_ERROR) { warningstream << "deleteBlock: deleting block " << PP(pos) @@ -164,7 +167,7 @@ void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst) { redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str())); if (!reply) { - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Redis command 'HKEYS %s' failed: ") + ctx->errstr); } switch (reply->type) { @@ -176,7 +179,7 @@ void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst) } break; case REDIS_REPLY_ERROR: - throw FileNotGoodException(std::string( + throw DatabaseException(std::string( "Failed to get keys from database: ") + std::string(reply->str, reply->len)); } diff --git a/src/database-redis.h b/src/database-redis.h index 45e702c83..3addaa20a 100644 --- a/src/database-redis.h +++ b/src/database-redis.h @@ -36,13 +36,13 @@ public: Database_Redis(Settings &conf); ~Database_Redis(); - virtual void beginSave(); - virtual void endSave(); + void beginSave(); + void endSave(); - 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); + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector<v3s16> &dst); private: redisContext *ctx; diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp index 56f937bf2..095d485c0 100644 --- a/src/database-sqlite3.cpp +++ b/src/database-sqlite3.cpp @@ -47,7 +47,7 @@ SQLite format specification: #define SQLRES(s, r, m) \ if ((s) != (r)) { \ - throw FileNotGoodException(std::string(m) + ": " +\ + throw DatabaseException(std::string(m) + ": " +\ sqlite3_errmsg(m_database)); \ } #define SQLOK(s, m) SQLRES(s, SQLITE_OK, m) @@ -56,8 +56,14 @@ SQLite format specification: SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\ "Failed to prepare query '" query "'") -#define FINALIZE_STATEMENT(statement) \ - SQLOK(sqlite3_finalize(statement), "Failed to finalize " #statement) +#define SQLOK_ERRSTREAM(s, m) \ + if ((s) != SQLITE_OK) { \ + errorstream << (m) << ": " \ + << sqlite3_errmsg(m_database) << std::endl; \ + } + +#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \ + "Failed to finalize " #statement) int Database_SQLite3::busyHandler(void *data, int count) { @@ -237,7 +243,7 @@ bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data) return true; } -std::string Database_SQLite3::loadBlock(const v3s16 &pos) +void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block) { verifyDatabase(); @@ -245,20 +251,17 @@ std::string Database_SQLite3::loadBlock(const v3s16 &pos) if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { sqlite3_reset(m_stmt_read); - return ""; + return; } + const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); - std::string s; - if (data) - s = std::string(data, len); + *block = (data) ? std::string(data, len) : ""; sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset sqlite3_reset(m_stmt_read); - - return s; } void Database_SQLite3::createDatabase() @@ -292,6 +295,6 @@ Database_SQLite3::~Database_SQLite3() FINALIZE_STATEMENT(m_stmt_end) FINALIZE_STATEMENT(m_stmt_delete) - SQLOK(sqlite3_close(m_database), "Failed to close database"); + SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database"); } diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h index 04a1825d9..debbc9d8b 100644 --- a/src/database-sqlite3.h +++ b/src/database-sqlite3.h @@ -31,16 +31,16 @@ class Database_SQLite3 : public Database { public: Database_SQLite3(const std::string &savedir); + ~Database_SQLite3(); - virtual void beginSave(); - virtual void endSave(); + void beginSave(); + void endSave(); - 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(); + bool saveBlock(const v3s16 &pos, const std::string &data); + void loadBlock(const v3s16 &pos, std::string *block); + bool deleteBlock(const v3s16 &pos); + void listAllLoadableBlocks(std::vector<v3s16> &dst); + bool initialized() const { return m_initialized; } private: // Open the database diff --git a/src/database.h b/src/database.h index cee7b6fd9..0cf75232f 100644 --- a/src/database.h +++ b/src/database.h @@ -38,7 +38,7 @@ public: virtual void endSave() {} virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0; - virtual std::string loadBlock(const v3s16 &pos) = 0; + virtual void loadBlock(const v3s16 &pos, std::string *block) = 0; virtual bool deleteBlock(const v3s16 &pos) = 0; static s64 getBlockAsInteger(const v3s16 &pos); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index e8b652c17..0b4be6322 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -40,6 +40,7 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_jump", "KEY_SPACE"); settings->setDefault("keymap_sneak", "KEY_LSHIFT"); settings->setDefault("keymap_drop", "KEY_KEY_Q"); + settings->setDefault("keymap_zoom", "KEY_KEY_Z"); settings->setDefault("keymap_inventory", "KEY_KEY_I"); settings->setDefault("keymap_special1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); @@ -50,7 +51,7 @@ void set_default_settings(Settings *settings) 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_cinematic", ""); settings->setDefault("keymap_screenshot", "KEY_F12"); settings->setDefault("keymap_toggle_hud", "KEY_F1"); settings->setDefault("keymap_toggle_chat", "KEY_F2"); @@ -74,6 +75,7 @@ void set_default_settings(Settings *settings) settings->setDefault("always_fly_fast", "true"); settings->setDefault("directional_colored_fog", "true"); settings->setDefault("tooltip_show_delay", "400"); + settings->setDefault("zoom_fov", "15"); // Some (temporary) keys for debugging settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P"); @@ -104,6 +106,7 @@ void set_default_settings(Settings *settings) settings->setDefault("client_unload_unused_data_timeout", "600"); settings->setDefault("client_mapblock_limit", "5000"); settings->setDefault("enable_fog", "true"); + settings->setDefault("fog_start", "0.4"); settings->setDefault("fov", "72"); settings->setDefault("view_bobbing", "true"); settings->setDefault("leaves_style", "fancy"); @@ -116,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("enable_joysticks", "false"); + settings->setDefault("repeat_joystick_button_time", "0.17"); + settings->setDefault("joystick_frustum_sensitivity", "170"); settings->setDefault("cinematic", "false"); settings->setDefault("camera_smoothing", "0"); settings->setDefault("cinematic_camera_smoothing", "0.7"); @@ -185,7 +191,7 @@ void set_default_settings(Settings *settings) settings->setDefault("minimap_shape_round", "true"); settings->setDefault("minimap_double_scan_height", "true"); - settings->setDefault("send_pre_v25_init", "true"); + settings->setDefault("send_pre_v25_init", "false"); settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); @@ -202,11 +208,13 @@ void set_default_settings(Settings *settings) settings->setDefault("server_name", ""); settings->setDefault("server_description", ""); + settings->setDefault("disable_escape_sequences", "false"); + #if USE_FREETYPE settings->setDefault("freetype", "true"); settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf")); settings->setDefault("font_shadow", "1"); - settings->setDefault("font_shadow_alpha", "128"); + settings->setDefault("font_shadow_alpha", "127"); settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "liberationmono.ttf")); settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); @@ -272,12 +280,16 @@ void set_default_settings(Settings *settings) settings->setDefault("max_simultaneous_block_sends_server_total", "40"); settings->setDefault("max_block_send_distance", "9"); settings->setDefault("max_block_generate_distance", "7"); + settings->setDefault("block_send_optimize_distance", "4"); settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096"); settings->setDefault("time_send_interval", "5"); settings->setDefault("time_speed", "72"); settings->setDefault("server_unload_unused_data_timeout", "29"); - settings->setDefault("max_objects_per_block", "49"); + settings->setDefault("max_objects_per_block", "64"); settings->setDefault("server_map_save_interval", "5.3"); + settings->setDefault("chat_message_max_size", "500"); + settings->setDefault("chat_message_limit_per_10sec", "8.0"); + settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("sqlite_synchronous", "2"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); settings->setDefault("dedicated_server_step", "0.1"); @@ -315,7 +327,7 @@ void set_default_settings(Settings *settings) settings->setDefault("liquid_update", "1.0"); //mapgen stuff - settings->setDefault("mg_name", "v6"); + settings->setDefault("mg_name", "v7"); settings->setDefault("water_level", "1"); settings->setDefault("chunksize", "5"); settings->setDefault("mg_flags", "dungeons"); diff --git a/src/dungeongen.cpp b/src/dungeongen.cpp index 5bfc320c9..78573da04 100644 --- a/src/dungeongen.cpp +++ b/src/dungeongen.cpp @@ -30,55 +30,67 @@ with this program; if not, write to the Free Software Foundation, Inc., //#define DGEN_USE_TORCHES -NoiseParams nparams_dungeon_rarity(0.0, 1.0, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); -NoiseParams nparams_dungeon_wetness(0.0, 1.0, v3f(40.0, 40.0, 40.0), 32474, 4, 1.1, 2.0); -NoiseParams nparams_dungeon_density(0.0, 1.0, v3f(2.5, 2.5, 2.5), 0, 2, 1.4, 2.0); +NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); +NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); /////////////////////////////////////////////////////////////////////////////// -DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) +DungeonGen::DungeonGen(INodeDefManager *ndef, + GenerateNotifier *gennotify, DungeonParams *dparams) { - this->mg = mapgen; - this->vm = mapgen->vm; + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; #ifdef DGEN_USE_TORCHES - c_torch = mg->ndef->getId("default:torch"); + c_torch = ndef->getId("default:torch"); #endif if (dparams) { memcpy(&dp, dparams, sizeof(dp)); } else { - dp.c_water = mg->ndef->getId("mapgen_water_source"); - dp.c_cobble = mg->ndef->getId("mapgen_cobble"); - dp.c_moss = mg->ndef->getId("mapgen_mossycobble"); - dp.c_stair = mg->ndef->getId("mapgen_stair_cobble"); + dp.seed = 0; + + dp.c_water = ndef->getId("mapgen_water_source"); + dp.c_river_water = ndef->getId("mapgen_river_water_source"); + dp.c_wall = ndef->getId("mapgen_cobble"); + dp.c_alt_wall = ndef->getId("mapgen_mossycobble"); + dp.c_stair = ndef->getId("mapgen_stair_cobble"); + + if (dp.c_river_water == CONTENT_IGNORE) + dp.c_river_water = ndef->getId("mapgen_water_source"); dp.diagonal_dirs = false; - dp.mossratio = 3.0; dp.holesize = v3s16(1, 2, 1); dp.roomsize = v3s16(0, 0, 0); + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; dp.notifytype = GENNOTIFY_DUNGEON; - dp.np_rarity = nparams_dungeon_rarity; - dp.np_wetness = nparams_dungeon_wetness; - dp.np_density = nparams_dungeon_density; + dp.np_density = nparams_dungeon_density; + dp.np_alt_wall = nparams_dungeon_alt_wall; } - - // For mapgens using river water - dp.c_river_water = mg->ndef->getId("mapgen_river_water_source"); - if (dp.c_river_water == CONTENT_IGNORE) - dp.c_river_water = mg->ndef->getId("mapgen_water_source"); } -void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) +void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) { + assert(vm); + //TimeTaker t("gen dungeons"); - if (NoisePerlin3D(&dp.np_rarity, nmin.X, nmin.Y, nmin.Z, mg->seed) < 0.2) + if (nmin.Y < dp.y_min || nmax.Y > dp.y_max) return; + float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed); + if (nval_density < 1.0f) + return; + + this->vm = vm; this->blockseed = bseed; random.seed(bseed + 2); @@ -99,23 +111,23 @@ void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) } } - // Add it - makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE); + // Add them + for (u32 i = 0; i < floor(nval_density); i++) + makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE); - // Convert some cobble to mossy cobble - if (dp.mossratio != 0.0) { - 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++) { - if (vm->m_data[i].getContent() == dp.c_cobble) { - float wetness = NoisePerlin3D(&dp.np_wetness, x, y, z, mg->seed); - float density = NoisePerlin3D(&dp.np_density, x, y, z, blockseed); - if (density < wetness / dp.mossratio) - vm->m_data[i].setContent(dp.c_moss); - } - i++; + // Optionally convert some structure to alternative structure + if (dp.c_alt_wall == CONTENT_IGNORE) + return; + + 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++) { + if (vm->m_data[i].getContent() == dp.c_wall) { + if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f) + vm->m_data[i].setContent(dp.c_alt_wall); } + i++; } } @@ -135,17 +147,23 @@ void DungeonGen::makeDungeon(v3s16 start_padding) bool fits = false; 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)); + if (is_large_room) { + roomsize.Z = random.range(8, 16); + roomsize.Y = random.range(8, 16); + roomsize.X = random.range(8, 16); + } else { + roomsize.Z = random.range(4, 8); + roomsize.Y = random.range(4, 6); + roomsize.X = 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 - start_padding.X), - random.range(0, areasize.Y - roomsize.Y - start_padding.Y), - random.range(0, areasize.Z - roomsize.Z - start_padding.Z)); + roomplace = vm->m_area.MinEdge + start_padding; + roomplace.Z += random.range(0, areasize.Z - roomsize.Z - start_padding.Z); + roomplace.Y += random.range(0, areasize.Y - roomsize.Y - start_padding.Y); + roomplace.X += random.range(0, areasize.X - roomsize.X - start_padding.X); /* Check that we're not putting the room to an unknown place, @@ -175,13 +193,14 @@ 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); + u32 room_count = random.range(dp.rooms_min, dp.rooms_max); for (u32 i = 0; i < room_count; i++) { // Make a room to the determined place makeRoom(roomsize, roomplace); v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2); - mg->gennotify.addEvent(dp.notifytype, room_center); + if (gennotify) + gennotify->addEvent(dp.notifytype, room_center); #ifdef DGEN_USE_TORCHES // Place torch at room center (for testing) @@ -227,7 +246,9 @@ 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.Z = random.range(4, 8); + roomsize.Y = random.range(4, 6); + roomsize.X = random.range(4, 8); roomsize += dp.roomsize; m_pos = corridor_end; @@ -248,7 +269,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding) void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) { - MapNode n_cobble(dp.c_cobble); + MapNode n_wall(dp.c_wall); MapNode n_air(CONTENT_AIR); // Make +-X walls @@ -261,7 +282,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } { v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); @@ -270,7 +291,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } } @@ -284,7 +305,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } { v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); @@ -293,7 +314,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } } @@ -307,7 +328,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } { v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z); @@ -316,7 +337,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) continue; - vm->m_data[vi] = n_cobble; + vm->m_data[vi] = n_wall; } } @@ -400,7 +421,7 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 3, 2), VMANIP_FLAG_DUNGEON_UNTOUCHABLE, - MapNode(dp.c_cobble), + MapNode(dp.c_wall), 0); makeHole(p); makeHole(p - dir); @@ -418,18 +439,18 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, 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); - if (vm->m_data[vi].getContent() == dp.c_cobble) + if (vm->m_data[vi].getContent() == dp.c_wall) vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); vi = vm->m_area.index(p.X, p.Y, p.Z); - if (vm->m_data[vi].getContent() == dp.c_cobble) + if (vm->m_data[vi].getContent() == dp.c_wall) 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), + MapNode(dp.c_wall), 0); makeHole(p); } @@ -471,8 +492,8 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) randomizeDir(); continue; } - if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_cobble && - vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble) { + if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_wall && + vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_wall) { // Found wall, this is a good place! result_place = p; result_dir = m_dir; @@ -485,7 +506,7 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) */ // Jump one up if the actual space is there if (vm->getNodeNoExNoEmerge(p + - v3s16(0, 0, 0)).getContent() == dp.c_cobble && + v3s16(0, 0, 0)).getContent() == dp.c_wall && vm->getNodeNoExNoEmerge(p + v3s16(0, 1, 0)).getContent() == CONTENT_AIR && vm->getNodeNoExNoEmerge(p + @@ -493,7 +514,7 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) 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 && + v3s16(0, 1, 0)).getContent() == dp.c_wall && vm->getNodeNoExNoEmerge(p + v3s16(0, 0, 0)).getContent() == CONTENT_AIR && vm->getNodeNoExNoEmerge(p + @@ -587,7 +608,10 @@ v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) do { trycount++; - dir = v3s16(random.next() % 3 - 1, 0, random.next() % 3 - 1); + + dir.Z = random.next() % 3 - 1; + dir.Y = 0; + dir.X = random.next() % 3 - 1; } while ((dir.X == 0 && dir.Z == 0) && trycount < 10); return dir; diff --git a/src/dungeongen.h b/src/dungeongen.h index d209dd4bf..30786b9f3 100644 --- a/src/dungeongen.h +++ b/src/dungeongen.h @@ -39,27 +39,33 @@ int dir_to_facedir(v3s16 d); struct DungeonParams { + s32 seed; + content_t c_water; content_t c_river_water; - content_t c_cobble; - content_t c_moss; + content_t c_wall; + content_t c_alt_wall; content_t c_stair; - GenNotifyType notifytype; bool diagonal_dirs; - float mossratio; v3s16 holesize; v3s16 roomsize; + u16 rooms_min; + u16 rooms_max; + s16 y_min; + s16 y_max; + GenNotifyType notifytype; - NoiseParams np_rarity; - NoiseParams np_wetness; NoiseParams np_density; + NoiseParams np_alt_wall; }; class DungeonGen { public: MMVManip *vm; - Mapgen *mg; + INodeDefManager *ndef; + GenerateNotifier *gennotify; + u32 blockseed; PseudoRandom random; v3s16 csize; @@ -67,17 +73,20 @@ public: content_t c_torch; DungeonParams dp; - //RoomWalker + // RoomWalker v3s16 m_pos; v3s16 m_dir; - DungeonGen(Mapgen *mg, DungeonParams *dparams); - void generate(u32 bseed, v3s16 full_node_min, v3s16 full_node_max); + DungeonGen(INodeDefManager *ndef, + GenerateNotifier *gennotify, DungeonParams *dparams); + + void generate(MMVManip *vm, u32 bseed, + v3s16 full_node_min, v3s16 full_node_max); void makeDungeon(v3s16 start_padding); void makeRoom(v3s16 roomsize, v3s16 roomplace); void makeCorridor(v3s16 doorplace, v3s16 doordir, - v3s16 &result_place, v3s16 &result_dir); + v3s16 &result_place, v3s16 &result_dir); void makeDoor(v3s16 doorplace, v3s16 doordir); void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags); void makeHole(v3s16 place); @@ -86,14 +95,13 @@ public: bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, v3s16 &result_doordir, v3s16 &result_roomplace); - void randomizeDir() + inline void randomizeDir() { m_dir = rand_ortho_dir(random, dp.diagonal_dirs); } }; -extern NoiseParams nparams_dungeon_rarity; -extern NoiseParams nparams_dungeon_wetness; extern NoiseParams nparams_dungeon_density; +extern NoiseParams nparams_dungeon_alt_wall; #endif diff --git a/src/emerge.cpp b/src/emerge.cpp index 93e8f2b30..25b2e924b 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -34,13 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "map.h" #include "mapblock.h" -#include "mapgen_flat.h" -#include "mapgen_fractal.h" -#include "mapgen_v5.h" -#include "mapgen_v6.h" -#include "mapgen_v7.h" -#include "mapgen_valleys.h" -#include "mapgen_singlenode.h" #include "mg_biome.h" #include "mg_ore.h" #include "mg_decoration.h" @@ -53,13 +46,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "voxel.h" - -struct MapgenDesc { - const char *name; - MapgenFactory *factory; - bool is_user_visible; -}; - class EmergeThread : public Thread { public: bool enable_mapgen_debug_info; @@ -100,20 +86,6 @@ private: }; //// -//// Built-in mapgens -//// - -MapgenDesc g_reg_mapgens[] = { - {"v5", new MapgenFactoryV5, true}, - {"v6", new MapgenFactoryV6, true}, - {"v7", new MapgenFactoryV7, true}, - {"flat", new MapgenFactoryFlat, true}, - {"fractal", new MapgenFactoryFractal, true}, - {"valleys", new MapgenFactoryValleys, true}, - {"singlenode", new MapgenFactorySinglenode, false}, -}; - -//// //// EmergeManager //// @@ -181,42 +153,22 @@ EmergeManager::~EmergeManager() delete oremgr; delete decomgr; delete schemmgr; - - delete params.sparams; } -void EmergeManager::loadMapgenParams() -{ - params.load(*g_settings); -} - - -void EmergeManager::initMapgens() +bool EmergeManager::initMapgens(MapgenParams *params) { if (m_mapgens.size()) - return; - - MapgenFactory *mgfactory = getMapgenFactory(params.mg_name); - if (!mgfactory) { - errorstream << "EmergeManager: mapgen " << params.mg_name << - " not registered; falling back to " << DEFAULT_MAPGEN << std::endl; - - params.mg_name = DEFAULT_MAPGEN; + return false; - mgfactory = getMapgenFactory(params.mg_name); - FATAL_ERROR_IF(mgfactory == NULL, "Couldn't use any mapgen!"); - } - - if (!params.sparams) { - params.sparams = mgfactory->createMapgenParams(); - params.sparams->readParams(g_settings); - } + this->mgparams = params; for (u32 i = 0; i != m_threads.size(); i++) { - Mapgen *mg = mgfactory->createMapgen(i, ¶ms, this); + Mapgen *mg = Mapgen::createMapgen(params->mgtype, i, params, this); m_mapgens.push_back(mg); } + + return true; } @@ -318,12 +270,14 @@ bool EmergeManager::enqueueBlockEmergeEx( // Mapgen-related helper functions // + +// TODO(hmmmm): Move this to ServerMap v3s16 EmergeManager::getContainingChunk(v3s16 blockpos) { - return getContainingChunk(blockpos, params.chunksize); + return getContainingChunk(blockpos, mgparams->chunksize); } - +// TODO(hmmmm): Move this to ServerMap v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize) { s16 coff = -chunksize / 2; @@ -357,7 +311,7 @@ int EmergeManager::getGroundLevelAtPoint(v2s16 p) return m_mapgens[0]->getGroundLevelAtPoint(p); } - +// TODO(hmmmm): Move this to ServerMap bool EmergeManager::isBlockUnderground(v3s16 blockpos) { #if 0 @@ -368,31 +322,9 @@ bool EmergeManager::isBlockUnderground(v3s16 blockpos) #endif // Use a simple heuristic; the above method is wildly inaccurate anyway. - return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params.water_level; + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= mgparams->water_level; } - -void EmergeManager::getMapgenNames( - std::vector<const char *> *mgnames, bool include_hidden) -{ - for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) { - if (include_hidden || g_reg_mapgens[i].is_user_visible) - mgnames->push_back(g_reg_mapgens[i].name); - } -} - - -MapgenFactory *EmergeManager::getMapgenFactory(const std::string &mgname) -{ - for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) { - if (mgname == g_reg_mapgens[i].name) - return g_reg_mapgens[i].factory; - } - - return NULL; -} - - bool EmergeManager::pushBlockEmergeData( v3s16 pos, u16 peer_requested, @@ -437,12 +369,10 @@ bool EmergeManager::pushBlockEmergeData( } -bool EmergeManager::popBlockEmergeData( - v3s16 pos, - BlockEmergeData *bedata) +bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) { std::map<v3s16, BlockEmergeData>::iterator it; - std::map<u16, u16>::iterator it2; + UNORDERED_MAP<u16, u16>::iterator it2; it = m_blocks_enqueued.find(pos); if (it == m_blocks_enqueued.end()) @@ -578,13 +508,15 @@ EmergeAction EmergeThread::getBlockOrStartGen( // 1). Attempt to fetch block from memory *block = m_map->getBlockNoCreateNoEx(pos); - if (*block && !(*block)->isDummy() && (*block)->isGenerated()) - return EMERGE_FROM_MEMORY; - - // 2). Attempt to load block from disk - *block = m_map->loadBlock(pos); - if (*block && (*block)->isGenerated()) - return EMERGE_FROM_DISK; + if (*block && !(*block)->isDummy()) { + if ((*block)->isGenerated()) + return EMERGE_FROM_MEMORY; + } else { + // 2). Attempt to load block from disk if it was not in the memory + *block = m_map->loadBlock(pos); + if (*block && (*block)->isGenerated()) + return EMERGE_FROM_DISK; + } // 3). Attempt to start generation if (allow_gen && m_map->initBlockMake(pos, bmdata)) diff --git a/src/emerge.h b/src/emerge.h index 825ac1c0f..71ad97da3 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -97,8 +97,16 @@ public: u32 gen_notify_on; std::set<u32> gen_notify_on_deco_ids; - // Map generation parameters - MapgenParams params; + // Parameters passed to mapgens owned by ServerMap + // TODO(hmmmm): Remove this after mapgen helper methods using them + // are moved to ServerMap + MapgenParams *mgparams; + + // Hackish workaround: + // For now, EmergeManager must hold onto a ptr to the Map's setting manager + // since the Map can only be accessed through the Environment, and the + // Environment is not created until after script initialization. + MapSettingsManager *map_settings_mgr; // Managers of various map generation-related components BiomeManager *biomemgr; @@ -110,8 +118,7 @@ public: EmergeManager(IGameDef *gamedef); ~EmergeManager(); - void loadMapgenParams(); - void initMapgens(); + bool initMapgens(MapgenParams *mgparams); void startThreads(); void stopThreads(); @@ -140,9 +147,6 @@ public: int getGroundLevelAtPoint(v2s16 p); bool isBlockUnderground(v3s16 blockpos); - static MapgenFactory *getMapgenFactory(const std::string &mgname); - static void getMapgenNames( - std::vector<const char *> *mgnames, bool include_hidden); static v3s16 getContainingChunk(v3s16 blockpos, s16 chunksize); private: @@ -152,7 +156,7 @@ private: Mutex m_queue_mutex; std::map<v3s16, BlockEmergeData> m_blocks_enqueued; - std::map<u16, u16> m_peer_queue_count; + UNORDERED_MAP<u16, u16> m_peer_queue_count; u16 m_qlimit_total; u16 m_qlimit_diskonly; diff --git a/src/environment.cpp b/src/environment.cpp index 413bc7ff1..13c64b37c 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -69,121 +69,6 @@ Environment::Environment(): Environment::~Environment() { - // Deallocate players - for(std::vector<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) { - delete (*i); - } -} - -void Environment::addPlayer(Player *player) -{ - DSTACK(FUNCTION_NAME); - /* - Check that peer_ids are unique. - Also check that names are unique. - Exception: there can be multiple players with peer_id=0 - */ - // If peer id is non-zero, it has to be unique. - if(player->peer_id != 0) - FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique"); - // Name has to be unique. - FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique"); - // Add. - m_players.push_back(player); -} - -void Environment::removePlayer(Player* player) -{ - for (std::vector<Player*>::iterator it = m_players.begin(); - it != m_players.end(); ++it) { - if ((*it) == player) { - delete *it; - m_players.erase(it); - return; - } - } -} - -Player * Environment::getPlayer(u16 peer_id) -{ - 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; - } - return NULL; -} - -Player * Environment::getPlayer(const char *name) -{ - 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; - } - return NULL; -} - -Player * Environment::getRandomConnectedPlayer() -{ - std::vector<Player*> connected_players = getPlayers(true); - u32 chosen_one = myrand() % connected_players.size(); - u32 j = 0; - for(std::vector<Player*>::iterator - i = connected_players.begin(); - i != connected_players.end(); ++i) { - if(j == chosen_one) { - Player *player = *i; - return player; - } - j++; - } - return NULL; -} - -Player * Environment::getNearestConnectedPlayer(v3f pos) -{ - std::vector<Player*> connected_players = getPlayers(true); - f32 nearest_d = 0; - Player *nearest_player = NULL; - for(std::vector<Player*>::iterator - i = connected_players.begin(); - i != connected_players.end(); ++i) { - Player *player = *i; - f32 d = player->getPosition().getDistanceFrom(pos); - if(d < nearest_d || nearest_player == NULL) { - nearest_d = d; - nearest_player = player; - } - } - return nearest_player; -} - -std::vector<Player*> Environment::getPlayers() -{ - return m_players; -} - -std::vector<Player*> Environment::getPlayers(bool ignore_disconnected) -{ - std::vector<Player*> newlist; - for(std::vector<Player*>::iterator - i = m_players.begin(); - i != m_players.end(); ++i) { - Player *player = *i; - - if(ignore_disconnected) { - // Ignore disconnected players - if(player->peer_id == 0) - continue; - } - - newlist.push_back(player); - } - return newlist; } u32 Environment::getDayNightRatio() @@ -199,11 +84,6 @@ void Environment::setTimeOfDaySpeed(float speed) m_time_of_day_speed = speed; } -float Environment::getTimeOfDaySpeed() -{ - return m_time_of_day_speed; -} - void Environment::setDayNightRatioOverride(bool enable, u32 value) { MutexAutoLock lock(this->m_time_lock); @@ -609,10 +489,16 @@ ServerEnvironment::~ServerEnvironment() m_map->drop(); // Delete ActiveBlockModifiers - for(std::vector<ABMWithState>::iterator + for (std::vector<ABMWithState>::iterator i = m_abms.begin(); i != m_abms.end(); ++i){ delete i->abm; } + + // Deallocate players + for (std::vector<RemotePlayer *>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + delete (*i); + } } Map & ServerEnvironment::getMap() @@ -625,6 +511,57 @@ ServerMap & ServerEnvironment::getServerMap() return *m_map; } +RemotePlayer *ServerEnvironment::getPlayer(const u16 peer_id) +{ + for (std::vector<RemotePlayer *>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + RemotePlayer *player = *i; + if (player->peer_id == peer_id) + return player; + } + return NULL; +} + +RemotePlayer *ServerEnvironment::getPlayer(const char* name) +{ + for (std::vector<RemotePlayer *>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + RemotePlayer *player = *i; + if (strcmp(player->getName(), name) == 0) + return player; + } + return NULL; +} + +void ServerEnvironment::addPlayer(RemotePlayer *player) +{ + DSTACK(FUNCTION_NAME); + /* + Check that peer_ids are unique. + Also check that names are unique. + Exception: there can be multiple players with peer_id=0 + */ + // If peer id is non-zero, it has to be unique. + if (player->peer_id != 0) + FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique"); + // Name has to be unique. + FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique"); + // Add. + m_players.push_back(player); +} + +void ServerEnvironment::removePlayer(RemotePlayer *player) +{ + for (std::vector<RemotePlayer *>::iterator it = m_players.begin(); + it != m_players.end(); ++it) { + if ((*it) == player) { + delete *it; + m_players.erase(it); + return; + } + } +} + bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p) { float distance = pos1.getDistanceFrom(pos2); @@ -655,12 +592,11 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 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); + for (std::vector<RemotePlayer *>::iterator it = m_players.begin(); + it != m_players.end(); ++it) { + RemotePlayer *player = dynamic_cast<RemotePlayer *>(*it); + ((Server*)m_gamedef)->DenyAccessVerCompliant(player->peer_id, + player->protocol_version, reason, str_reason, reconnect); } } @@ -669,12 +605,11 @@ void ServerEnvironment::saveLoadedPlayers() std::string players_path = m_path_world + DIR_DELIM "players"; fs::CreateDir(players_path); - for (std::vector<Player*>::iterator it = m_players.begin(); + for (std::vector<RemotePlayer *>::iterator it = m_players.begin(); it != m_players.end(); ++it) { - RemotePlayer *player = static_cast<RemotePlayer*>(*it); - if (player->checkModified()) { - player->save(players_path); + if ((*it)->checkModified()) { + (*it)->save(players_path, m_gamedef); } } } @@ -684,19 +619,19 @@ void ServerEnvironment::savePlayer(RemotePlayer *player) std::string players_path = m_path_world + DIR_DELIM "players"; fs::CreateDir(players_path); - player->save(players_path); + player->save(players_path, m_gamedef); } -Player *ServerEnvironment::loadPlayer(const std::string &playername) +RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao) { 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())); + RemotePlayer *player = getPlayer(playername.c_str()); if (!player) { - player = new RemotePlayer(m_gamedef, ""); + player = new RemotePlayer("", m_gamedef->idef()); newplayer = true; } @@ -705,7 +640,8 @@ Player *ServerEnvironment::loadPlayer(const std::string &playername) std::ifstream is(path.c_str(), std::ios_base::binary); if (!is.good()) continue; - player->deSerialize(is, path); + + player->deSerialize(is, path, sao); is.close(); if (player->getName() == playername) { @@ -721,11 +657,13 @@ Player *ServerEnvironment::loadPlayer(const std::string &playername) << " not found" << std::endl; if (newplayer) delete player; + return NULL; } - if (newplayer) + if (newplayer) { addPlayer(player); + } player->setModified(false); return player; } @@ -1030,17 +968,17 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) m_lbm_mgr.applyLBMs(this, block, stamp); // Run node timers - std::map<v3s16, NodeTimer> elapsed_timers = + std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step((float)dtime_s); - if(!elapsed_timers.empty()){ + if (!elapsed_timers.empty()) { MapNode n; - for(std::map<v3s16, NodeTimer>::iterator + for (std::vector<NodeTimer>::iterator i = elapsed_timers.begin(); i != elapsed_timers.end(); ++i){ - n = block->getNodeNoEx(i->first); - v3s16 p = i->first + block->getPosRelative(); - if(m_script->node_on_timer(p,n,i->second.elapsed)) - block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + n = block->getNodeNoEx(i->position); + v3s16 p = i->position + block->getPosRelative(); + if (m_script->node_on_timer(p, n, i->elapsed)) + block->setNodeTimer(NodeTimer(i->timeout, 0, i->position)); } } @@ -1124,14 +1062,12 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius) { - for(std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + for (ActiveObjectMap::iterator i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; u16 id = i->first; v3f objectpos = obj->getBasePosition(); - if(objectpos.getDistanceFrom(pos) > radius) + if (objectpos.getDistanceFrom(pos) > radius) continue; objects.push_back(id); } @@ -1142,8 +1078,7 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) infostream << "ServerEnvironment::clearObjects(): " << "Removing all active objects" << std::endl; std::vector<u16> objects_to_remove; - for (std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); + for (ActiveObjectMap::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) @@ -1311,10 +1246,10 @@ void ServerEnvironment::step(float dtime) */ { ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG); - for(std::vector<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { - Player *player = *i; + for (std::vector<RemotePlayer *>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i); + assert(player); // Ignore disconnected players if(player->peer_id == 0) @@ -1334,16 +1269,20 @@ void ServerEnvironment::step(float dtime) Get player block positions */ std::vector<v3s16> players_blockpos; - for(std::vector<Player*>::iterator - i = m_players.begin(); + for (std::vector<RemotePlayer *>::iterator i = m_players.begin(); i != m_players.end(); ++i) { - Player *player = *i; + RemotePlayer *player = dynamic_cast<RemotePlayer *>(*i); + assert(player); + // Ignore disconnected players - if(player->peer_id == 0) + if (player->peer_id == 0) continue; + PlayerSAO *playersao = player->getPlayerSAO(); + assert(playersao); + v3s16 blockpos = getNodeBlockPos( - floatToInt(player->getPosition(), BS)); + floatToInt(playersao->getBasePosition(), BS)); players_blockpos.push_back(blockpos); } @@ -1434,17 +1373,18 @@ void ServerEnvironment::step(float dtime) MOD_REASON_BLOCK_EXPIRED); // Run node timers - std::map<v3s16, NodeTimer> elapsed_timers = + std::vector<NodeTimer> elapsed_timers = block->m_node_timers.step((float)dtime); - if(!elapsed_timers.empty()){ + if (!elapsed_timers.empty()) { MapNode n; - for(std::map<v3s16, NodeTimer>::iterator - i = elapsed_timers.begin(); - i != elapsed_timers.end(); ++i){ - n = block->getNodeNoEx(i->first); - p = i->first + block->getPosRelative(); - if(m_script->node_on_timer(p,n,i->second.elapsed)) - block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + for (std::vector<NodeTimer>::iterator i = elapsed_timers.begin(); + i != elapsed_timers.end(); ++i) { + n = block->getNodeNoEx(i->position); + p = i->position + block->getPosRelative(); + if (m_script->node_on_timer(p, n, i->elapsed)) { + block->setNodeTimer(NodeTimer( + i->timeout, 0, i->position)); + } } } } @@ -1516,10 +1456,8 @@ void ServerEnvironment::step(float dtime) send_recommended = true; } - for(std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + for(ActiveObjectMap::iterator i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; // Don't step if is to be removed or stored statically if(obj->m_removed || obj->m_pending_deactivation) @@ -1552,7 +1490,7 @@ void ServerEnvironment::step(float dtime) Manage particle spawner expiration */ if (m_particle_management_interval.step(dtime, 1.0)) { - for (std::map<u32, float>::iterator i = m_particle_spawners.begin(); + for (UNORDERED_MAP<u32, float>::iterator i = m_particle_spawners.begin(); i != m_particle_spawners.end(); ) { //non expiring spawners if (i->second == PARTICLE_SPAWNER_NO_EXPIRY) { @@ -1577,8 +1515,7 @@ u32 ServerEnvironment::addParticleSpawner(float exptime) u32 id = 0; for (;;) { // look for unused particlespawner id id++; - std::map<u32, float>::iterator f; - f = m_particle_spawners.find(id); + UNORDERED_MAP<u32, float>::iterator f = m_particle_spawners.find(id); if (f == m_particle_spawners.end()) { m_particle_spawners[id] = time; break; @@ -1587,31 +1524,45 @@ u32 ServerEnvironment::addParticleSpawner(float exptime) return id; } -void ServerEnvironment::deleteParticleSpawner(u32 id) +u32 ServerEnvironment::addParticleSpawner(float exptime, u16 attached_id) +{ + u32 id = addParticleSpawner(exptime); + m_particle_spawner_attachments[id] = attached_id; + if (ServerActiveObject *obj = getActiveObject(attached_id)) { + obj->attachParticleSpawner(id); + } + return id; +} + +void ServerEnvironment::deleteParticleSpawner(u32 id, bool remove_from_object) { m_particle_spawners.erase(id); + UNORDERED_MAP<u32, u16>::iterator it = m_particle_spawner_attachments.find(id); + if (it != m_particle_spawner_attachments.end()) { + u16 obj_id = (*it).second; + ServerActiveObject *sao = getActiveObject(obj_id); + if (sao != NULL && remove_from_object) { + sao->detachParticleSpawner(id); + } + m_particle_spawner_attachments.erase(id); + } } ServerActiveObject* ServerEnvironment::getActiveObject(u16 id) { - std::map<u16, ServerActiveObject*>::iterator n; - n = m_active_objects.find(id); - if(n == m_active_objects.end()) - return NULL; - return n->second; + ActiveObjectMap::iterator n = m_active_objects.find(id); + return (n != m_active_objects.end() ? n->second : NULL); } -bool isFreeServerActiveObjectId(u16 id, - std::map<u16, ServerActiveObject*> &objects) +bool isFreeServerActiveObjectId(u16 id, ActiveObjectMap &objects) { - if(id == 0) + if (id == 0) return false; return objects.find(id) == objects.end(); } -u16 getFreeServerActiveObjectId( - std::map<u16, ServerActiveObject*> &objects) +u16 getFreeServerActiveObjectId(ActiveObjectMap &objects) { //try to reuse id's as late as possible static u16 last_used_id = 0; @@ -1639,7 +1590,7 @@ u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) Finds out what new objects have been added to inside a radius around a position */ -void ServerEnvironment::getAddedActiveObjects(Player *player, s16 radius, +void ServerEnvironment::getAddedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, std::set<u16> ¤t_objects, std::queue<u16> &added_objects) @@ -1649,7 +1600,6 @@ void ServerEnvironment::getAddedActiveObjects(Player *player, s16 radius, if (player_radius_f < 0) player_radius_f = 0; - /* Go through the object list, - discard m_removed objects, @@ -1657,21 +1607,21 @@ void ServerEnvironment::getAddedActiveObjects(Player *player, s16 radius, - discard objects that are found in current_objects. - add remaining objects to added_objects */ - for(std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); + for (ActiveObjectMap::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) { u16 id = i->first; // Get object ServerActiveObject *object = i->second; - if(object == NULL) + if (object == NULL) continue; // Discard if removed or deactivating if(object->m_removed || object->m_pending_deactivation) continue; - f32 distance_f = object->getBasePosition().getDistanceFrom(player->getPosition()); + f32 distance_f = object->getBasePosition(). + getDistanceFrom(playersao->getBasePosition()); if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { // Discard if too far if (distance_f > player_radius_f && player_radius_f != 0) @@ -1693,7 +1643,7 @@ void ServerEnvironment::getAddedActiveObjects(Player *player, s16 radius, Finds out what objects have been removed from inside a radius around a position */ -void ServerEnvironment::getRemovedActiveObjects(Player *player, s16 radius, +void ServerEnvironment::getRemovedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, std::set<u16> ¤t_objects, std::queue<u16> &removed_objects) @@ -1703,7 +1653,6 @@ void ServerEnvironment::getRemovedActiveObjects(Player *player, s16 radius, if (player_radius_f < 0) player_radius_f = 0; - /* Go through current_objects; object is removed if: - object is not found in m_active_objects (this is actually an @@ -1731,7 +1680,7 @@ void ServerEnvironment::getRemovedActiveObjects(Player *player, s16 radius, continue; } - f32 distance_f = object->getBasePosition().getDistanceFrom(player->getPosition()); + f32 distance_f = object->getBasePosition().getDistanceFrom(playersao->getBasePosition()); if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { if (distance_f <= player_radius_f || player_radius_f == 0) continue; @@ -1754,8 +1703,7 @@ void ServerEnvironment::setStaticForActiveObjectsInBlock( 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); + ActiveObjectMap::iterator 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(): " @@ -1804,8 +1752,8 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " <<"supplied with id "<<object->getId()<<std::endl; } - if(isFreeServerActiveObjectId(object->getId(), m_active_objects) == false) - { + + if(!isFreeServerActiveObjectId(object->getId(), m_active_objects)) { errorstream<<"ServerEnvironment::addActiveObjectRaw(): " <<"id is not free ("<<object->getId()<<")"<<std::endl; if(object->environmentDeletes()) @@ -1873,8 +1821,7 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, void ServerEnvironment::removeRemovedObjects() { std::vector<u16> objects_to_remove; - for(std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); + for(ActiveObjectMap::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) { u16 id = i->first; ServerActiveObject* obj = i->second; @@ -1892,7 +1839,7 @@ void ServerEnvironment::removeRemovedObjects() We will delete objects that are marked as removed or thatare waiting for deletion after deactivation */ - if(obj->m_removed == false && obj->m_pending_deactivation == false) + if (!obj->m_removed && !obj->m_pending_deactivation) continue; /* @@ -2092,8 +2039,7 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) void ServerEnvironment::deactivateFarObjects(bool force_delete) { std::vector<u16> objects_to_remove; - for(std::map<u16, ServerActiveObject*>::iterator - i = m_active_objects.begin(); + for(ActiveObjectMap::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; assert(obj); @@ -2234,13 +2180,13 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) if(block) { - if(block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")){ - errorstream<<"ServerEnv: Trying to store id="<<obj->getId() - <<" statically but block "<<PP(blockpos) - <<" already contains " - <<block->m_static_objects.m_stored.size() - <<" objects." - <<" Forcing delete."<<std::endl; + if (block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")) { + warningstream << "ServerEnv: Trying to store id = " << obj->getId() + << " statically but block " << PP(blockpos) + << " already contains " + << block->m_static_objects.m_stored.size() + << " objects." + << " Forcing delete." << std::endl; force_delete = true; } else { // If static counterpart already exists in target block, @@ -2329,6 +2275,7 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr, ITextureSource *texturesource, IGameDef *gamedef, IrrlichtDevice *irr): m_map(map), + m_local_player(NULL), m_smgr(smgr), m_texturesource(texturesource), m_gamedef(gamedef), @@ -2341,10 +2288,8 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr, ClientEnvironment::~ClientEnvironment() { // delete active objects - for(std::map<u16, ClientActiveObject*>::iterator - i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + for (UNORDERED_MAP<u16, ClientActiveObject*>::iterator i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { delete i->second; } @@ -2367,28 +2312,16 @@ ClientMap & ClientEnvironment::getClientMap() return *m_map; } -void ClientEnvironment::addPlayer(Player *player) +void ClientEnvironment::setLocalPlayer(LocalPlayer *player) { DSTACK(FUNCTION_NAME); /* - It is a failure if player is local and there already is a local - player + It is a failure if already is a local player */ - FATAL_ERROR_IF(player->isLocal() == true && getLocalPlayer() != NULL, - "Player is local but there is already a local player"); + FATAL_ERROR_IF(m_local_player != NULL, + "Local player already allocated"); - Environment::addPlayer(player); -} - -LocalPlayer * ClientEnvironment::getLocalPlayer() -{ - for(std::vector<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) { - Player *player = *i; - if(player->isLocal()) - return (LocalPlayer*)player; - } - return NULL; + m_local_player = player; } void ClientEnvironment::step(float dtime) @@ -2469,11 +2402,11 @@ void ClientEnvironment::step(float dtime) { // Apply physics - if(free_move == false && is_climbing == false) + if(!free_move && !is_climbing) { // Gravity v3f speed = lplayer->getSpeed(); - if(lplayer->in_liquid == false) + if(!lplayer->in_liquid) speed.Y -= lplayer->movement_gravity * lplayer->physics_override_gravity * dtime_part * 2; // Liquid floating / sinking @@ -2625,23 +2558,6 @@ void ClientEnvironment::step(float dtime) } } - /* - Stuff that can be done in an arbitarily large dtime - */ - 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) { - // Move - player->move(dtime, this, 100*BS); - - } - } - // Update lighting on local player (used for wield item) u32 day_night_ratio = getDayNightRatio(); { @@ -2666,10 +2582,8 @@ void ClientEnvironment::step(float dtime) 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 - i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + for (UNORDERED_MAP<u16, ClientActiveObject*>::iterator i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { ClientActiveObject* obj = i->second; // Step object obj->step(dtime, this); @@ -2728,15 +2642,14 @@ GenericCAO* ClientEnvironment::getGenericCAO(u16 id) ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) { - std::map<u16, ClientActiveObject*>::iterator n; - n = m_active_objects.find(id); - if(n == m_active_objects.end()) + UNORDERED_MAP<u16, ClientActiveObject*>::iterator n = m_active_objects.find(id); + if (n == m_active_objects.end()) return NULL; return n->second; } -bool isFreeClientActiveObjectId(u16 id, - std::map<u16, ClientActiveObject*> &objects) +bool isFreeClientActiveObjectId(const u16 id, + UNORDERED_MAP<u16, ClientActiveObject*> &objects) { if(id == 0) return false; @@ -2744,19 +2657,17 @@ bool isFreeClientActiveObjectId(u16 id, return objects.find(id) == objects.end(); } -u16 getFreeClientActiveObjectId( - std::map<u16, ClientActiveObject*> &objects) +u16 getFreeClientActiveObjectId(UNORDERED_MAP<u16, ClientActiveObject*> &objects) { //try to reuse id's as late as possible static u16 last_used_id = 0; u16 startid = last_used_id; - for(;;) - { + for(;;) { last_used_id ++; - if(isFreeClientActiveObjectId(last_used_id, objects)) + if (isFreeClientActiveObjectId(last_used_id, objects)) return last_used_id; - if(last_used_id == startid) + if (last_used_id == startid) return 0; } } @@ -2776,8 +2687,7 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) } object->setId(new_id); } - if(isFreeClientActiveObjectId(object->getId(), m_active_objects) == false) - { + if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) { infostream<<"ClientEnvironment::addActiveObject(): " <<"id is not free ("<<object->getId()<<")"<<std::endl; delete object; @@ -2841,8 +2751,7 @@ void ClientEnvironment::removeActiveObject(u16 id) verbosestream<<"ClientEnvironment::removeActiveObject(): " <<"id="<<id<<std::endl; ClientActiveObject* obj = getActiveObject(id); - if(obj == NULL) - { + if (obj == NULL) { infostream<<"ClientEnvironment::removeActiveObject(): " <<"id="<<id<<" not found"<<std::endl; return; @@ -2910,10 +2819,8 @@ void ClientEnvironment::updateLocalPlayerBreath(u16 breath) void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d, std::vector<DistanceSortedActiveObject> &dest) { - for(std::map<u16, ClientActiveObject*>::iterator - i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + for (UNORDERED_MAP<u16, ClientActiveObject*>::iterator i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { ClientActiveObject* obj = i->second; f32 d = (obj->getPosition() - origin).getLength(); diff --git a/src/environment.h b/src/environment.h index c6786faed..4bee40e57 100644 --- a/src/environment.h +++ b/src/environment.h @@ -54,6 +54,7 @@ class ClientMap; class GameScripting; class Player; class RemotePlayer; +class PlayerSAO; class Environment { @@ -72,15 +73,6 @@ public: virtual Map & getMap() = 0; - virtual void addPlayer(Player *player); - void removePlayer(Player *player); - Player * getPlayer(u16 peer_id); - Player * getPlayer(const char *name); - Player * getRandomConnectedPlayer(); - Player * getNearestConnectedPlayer(v3f pos); - std::vector<Player*> getPlayers(); - std::vector<Player*> getPlayers(bool ignore_disconnected); - u32 getDayNightRatio(); // 0-23999 @@ -91,7 +83,6 @@ public: void stepTimeOfDay(float dtime); void setTimeOfDaySpeed(float speed); - float getTimeOfDaySpeed(); void setDayNightRatioOverride(bool enable, u32 value); @@ -101,9 +92,6 @@ public: u32 m_added_objects; protected: - // peer_ids in here should be unique, except that there may be many 0s - std::vector<Player*> m_players; - GenericAtomic<float> m_time_of_day_speed; /* @@ -300,6 +288,8 @@ enum ClearObjectsMode { This is not thread-safe. Server uses an environment mutex. */ +typedef UNORDERED_MAP<u16, ServerActiveObject *> ActiveObjectMap; + class ServerEnvironment : public Environment { public: @@ -326,7 +316,9 @@ public: // Save players void saveLoadedPlayers(); void savePlayer(RemotePlayer *player); - Player *loadPlayer(const std::string &playername); + RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao); + void addPlayer(RemotePlayer *player); + void removePlayer(RemotePlayer *player); /* Save and load time of day and game timer @@ -338,7 +330,8 @@ public: void loadDefaultMeta(); u32 addParticleSpawner(float exptime); - void deleteParticleSpawner(u32 id); + u32 addParticleSpawner(float exptime, u16 attached_id); + void deleteParticleSpawner(u32 id, bool remove_from_object = true); /* External ActiveObject interface @@ -370,7 +363,7 @@ public: Find out what new objects have been added to inside a radius around a position */ - void getAddedActiveObjects(Player *player, s16 radius, + void getAddedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, std::set<u16> ¤t_objects, std::queue<u16> &added_objects); @@ -379,7 +372,7 @@ public: Find out what new objects have been removed from inside a radius around a position */ - void getRemovedActiveObjects(Player* player, s16 radius, + void getRemovedActiveObjects(PlayerSAO *playersao, s16 radius, s16 player_radius, std::set<u16> ¤t_objects, std::queue<u16> &removed_objects); @@ -438,6 +431,8 @@ public: void setStaticForActiveObjectsInBlock(v3s16 blockpos, bool static_exists, v3s16 static_block=v3s16(0,0,0)); + RemotePlayer *getPlayer(const u16 peer_id); + RemotePlayer *getPlayer(const char* name); private: /* @@ -491,7 +486,7 @@ private: // World path const std::string m_path_world; // Active object list - std::map<u16, ServerActiveObject*> m_active_objects; + ActiveObjectMap m_active_objects; // Outgoing network message buffer for active objects std::queue<ActiveObjectMessage> m_active_object_messages; // Some timers @@ -520,9 +515,13 @@ private: // Can raise to high values like 15s with eg. map generation mods. float m_max_lag_estimate; + // peer_ids in here should be unique, except that there may be many 0s + std::vector<RemotePlayer*> m_players; + // Particles IntervalLimiter m_particle_management_interval; - std::map<u32, float> m_particle_spawners; + UNORDERED_MAP<u32, float> m_particle_spawners; + UNORDERED_MAP<u32, u16> m_particle_spawner_attachments; }; #ifndef SERVER @@ -579,8 +578,8 @@ public: void step(f32 dtime); - virtual void addPlayer(Player *player); - LocalPlayer * getLocalPlayer(); + virtual void setLocalPlayer(LocalPlayer *player); + LocalPlayer *getLocalPlayer() { return m_local_player; } /* ClientSimpleObjects @@ -630,24 +629,20 @@ public: u16 attachement_parent_ids[USHRT_MAX + 1]; - std::list<std::string> getPlayerNames() - { return m_player_names; } - void addPlayerName(std::string name) - { m_player_names.push_back(name); } - void removePlayerName(std::string name) - { m_player_names.remove(name); } + const std::list<std::string> &getPlayerNames() { return m_player_names; } + void addPlayerName(const std::string &name) { m_player_names.push_back(name); } + void removePlayerName(const std::string &name) { m_player_names.remove(name); } void updateCameraOffset(v3s16 camera_offset) { m_camera_offset = camera_offset; } - v3s16 getCameraOffset() - { return m_camera_offset; } - + v3s16 getCameraOffset() const { return m_camera_offset; } private: ClientMap *m_map; + LocalPlayer *m_local_player; scene::ISceneManager *m_smgr; ITextureSource *m_texturesource; IGameDef *m_gamedef; IrrlichtDevice *m_irr; - std::map<u16, ClientActiveObject*> m_active_objects; + UNORDERED_MAP<u16, ClientActiveObject*> m_active_objects; std::vector<ClientSimpleObject*> m_simple_objects; std::queue<ClientEnvEvent> m_client_event_queue; IntervalLimiter m_active_object_light_update_interval; diff --git a/src/exceptions.h b/src/exceptions.h index 4f18f70d9..67a2d0df6 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -65,6 +65,11 @@ public: FileNotGoodException(const std::string &s): BaseException(s) {} }; +class DatabaseException : public BaseException { +public: + DatabaseException(const std::string &s): BaseException(s) {} +}; + class SerializationError : public BaseException { public: SerializationError(const std::string &s): BaseException(s) {} diff --git a/src/filesys.cpp b/src/filesys.cpp index b4c52ab79..bd8b94aff 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -617,48 +617,51 @@ std::string RemoveRelativePathComponents(std::string path) { size_t pos = path.size(); size_t dotdot_count = 0; - while(pos != 0){ + while (pos != 0) { size_t component_with_delim_end = pos; // skip a dir delimiter - while(pos != 0 && IsDirDelimiter(path[pos-1])) + while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; // strip a path component size_t component_end = pos; - while(pos != 0 && !IsDirDelimiter(path[pos-1])) + while (pos != 0 && !IsDirDelimiter(path[pos-1])) pos--; size_t component_start = pos; std::string component = path.substr(component_start, component_end - component_start); bool remove_this_component = false; - if(component == "."){ + if (component == ".") { remove_this_component = true; - } - else if(component == ".."){ + } else if (component == "..") { remove_this_component = true; dotdot_count += 1; - } - else if(dotdot_count != 0){ + } else if (dotdot_count != 0) { remove_this_component = true; dotdot_count -= 1; } - if(remove_this_component){ - while(pos != 0 && IsDirDelimiter(path[pos-1])) + if (remove_this_component) { + while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; - path = path.substr(0, pos) + DIR_DELIM + - path.substr(component_with_delim_end, - std::string::npos); - pos++; + if (component_start == 0) { + // We need to remove the delemiter too + path = path.substr(component_with_delim_end, std::string::npos); + } else { + path = path.substr(0, pos) + DIR_DELIM + + path.substr(component_with_delim_end, std::string::npos); + } + if (pos > 0) + pos++; } } - if(dotdot_count > 0) + if (dotdot_count > 0) return ""; // remove trailing dir delimiters pos = path.size(); - while(pos != 0 && IsDirDelimiter(path[pos-1])) + while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; return path.substr(0, pos); } diff --git a/src/game.cpp b/src/game.cpp index 23f261cfd..966c23073 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -23,6 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "client.h" #include "client/tile.h" // For TextureSource +#include "client/keys.h" +#include "client/joystick_controller.h" #include "clientmap.h" #include "clouds.h" #include "config.h" @@ -55,6 +57,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tool.h" #include "util/directiontables.h" #include "util/pointedthing.h" +#include "irrlicht_changes/static_text.h" #include "version.h" #include "minimap.h" #include "mapblock_mesh.h" @@ -359,6 +362,7 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio min_distance = (selected_object->getPosition() - camera_position).getLength(); + hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0)); result.type = POINTEDTHING_OBJECT; result.object_id = selected_object->getId(); } @@ -470,6 +474,7 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio if (!facebox.intersectsWithLine(shootline)) continue; result.node_abovesurface = pointed_pos + facedir; + hud->setSelectedFaceNormal(v3f(facedir.X, facedir.Y, facedir.Z)); face_min_distance = distance; } } @@ -537,7 +542,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 = utf8_to_wide(os.str()); - guitext_profiler->setText(text.c_str()); + setStaticText(guitext_profiler, text.c_str()); guitext_profiler->setVisible(true); s32 w = fe->getTextWidth(text.c_str()); @@ -602,6 +607,8 @@ public: void draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, gui::IGUIFont *font) const { + // Do *not* use UNORDERED_MAP here as the order needs + // to be the same for each call to prevent flickering std::map<std::string, Meta> m_meta; for (std::deque<Piece>::const_iterator k = m_log.begin(); @@ -612,8 +619,7 @@ public: i != piece.values.end(); ++i) { const std::string &id = i->first; const float &value = i->second; - std::map<std::string, Meta>::iterator j = - m_meta.find(id); + std::map<std::string, Meta>::iterator j = m_meta.find(id); if (j == m_meta.end()) { m_meta[id] = Meta(value); @@ -867,7 +873,7 @@ public: return; m_fetched.insert(name); - std::string base = porting::path_share + DIR_DELIM + "testsounds"; + std::string base = porting::path_share + DIR_DELIM + "sounds"; dst_paths.insert(base + DIR_DELIM + name + ".ogg"); dst_paths.insert(base + DIR_DELIM + name + ".0.ogg"); dst_paths.insert(base + DIR_DELIM + name + ".1.ogg"); @@ -882,40 +888,73 @@ public: } }; + +// before 1.8 there isn't a "integer interface", only float +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) +typedef f32 SamplerLayer_t; +#else +typedef s32 SamplerLayer_t; +#endif + + class GameGlobalShaderConstantSetter : public IShaderConstantSetter { Sky *m_sky; bool *m_force_fog_off; f32 *m_fog_range; + bool m_fog_enabled; + CachedPixelShaderSetting<float, 4> m_sky_bg_color; + CachedPixelShaderSetting<float> m_fog_distance; + CachedVertexShaderSetting<float> m_animation_timer_vertex; + CachedPixelShaderSetting<float> m_animation_timer_pixel; + CachedPixelShaderSetting<float> m_day_night_ratio; + CachedPixelShaderSetting<float, 3> m_eye_position_pixel; + CachedVertexShaderSetting<float, 3> m_eye_position_vertex; + CachedPixelShaderSetting<float, 3> m_minimap_yaw; + CachedPixelShaderSetting<SamplerLayer_t> m_base_texture; + CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture; + CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags; Client *m_client; - bool m_fogEnabled; public: void onSettingsChange(const std::string &name) { if (name == "enable_fog") - m_fogEnabled = g_settings->getBool("enable_fog"); + m_fog_enabled = 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); } + void setSky(Sky *sky) { m_sky = sky; } + GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, f32 *fog_range, Client *client) : m_sky(sky), m_force_fog_off(force_fog_off), m_fog_range(fog_range), + m_sky_bg_color("skyBgColor"), + m_fog_distance("fogDistance"), + m_animation_timer_vertex("animationTimer"), + m_animation_timer_pixel("animationTimer"), + m_day_night_ratio("dayNightRatio"), + m_eye_position_pixel("eyePosition"), + m_eye_position_vertex("eyePosition"), + m_minimap_yaw("yawVec"), + m_base_texture("baseTexture"), + m_normal_texture("normalTexture"), + m_texture_flags("textureFlags"), m_client(client) { - g_settings->registerChangedCallback("enable_fog", SettingsCallback, this); - m_fogEnabled = g_settings->getBool("enable_fog"); + g_settings->registerChangedCallback("enable_fog", settingsCallback, this); + m_fog_enabled = g_settings->getBool("enable_fog"); } ~GameGlobalShaderConstantSetter() { - g_settings->deregisterChangedCallback("enable_fog", SettingsCallback, this); + g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); } virtual void onSetConstants(video::IMaterialRendererServices *services, @@ -933,54 +972,92 @@ public: bgcolorf.b, bgcolorf.a, }; - services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4); + m_sky_bg_color.set(bgcolorfa, services); // Fog distance float fog_distance = 10000 * BS; - if (m_fogEnabled && !*m_force_fog_off) + if (m_fog_enabled && !*m_force_fog_off) fog_distance = *m_fog_range; - services->setPixelShaderConstant("fogDistance", &fog_distance, 1); + m_fog_distance.set(&fog_distance, services); - // Day-night ratio - u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); - float daynight_ratio_f = (float)daynight_ratio / 1000.0; - services->setPixelShaderConstant("dayNightRatio", &daynight_ratio_f, 1); + float daynight_ratio = (float)m_client->getEnv().getDayNightRatio() / 1000.f; + m_day_night_ratio.set(&daynight_ratio, services); u32 animation_timer = porting::getTimeMs() % 100000; - float animation_timer_f = (float)animation_timer / 100000.0; - services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1); - services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1); + float animation_timer_f = (float)animation_timer / 100000.f; + m_animation_timer_vertex.set(&animation_timer_f, services); + m_animation_timer_pixel.set(&animation_timer_f, services); - LocalPlayer *player = m_client->getEnv().getLocalPlayer(); - v3f eye_position = player->getEyePosition(); - 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); + float eye_position_array[3]; + v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + eye_position_array[0] = epos.X; + eye_position_array[1] = epos.Y; + eye_position_array[2] = epos.Z; +#else + epos.getAs3Values(eye_position_array); +#endif + m_eye_position_pixel.set(eye_position_array, services); + m_eye_position_vertex.set(eye_position_array, services); - // Uniform sampler layers - // before 1.8 there isn't a "integer interface", only float + float minimap_yaw_array[3]; + v3f minimap_yaw = m_client->getMapper()->getYawVec(); #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - f32 layer0 = 0; - f32 layer1 = 1; - f32 layer2 = 2; - services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1); - services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1); - services->setPixelShaderConstant("textureFlags" , (irr::f32 *)&layer2, 1); + minimap_yaw_array[0] = minimap_yaw.X; + minimap_yaw_array[1] = minimap_yaw.Y; + minimap_yaw_array[2] = minimap_yaw.Z; #else - s32 layer0 = 0; - s32 layer1 = 1; - s32 layer2 = 2; - services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1); - services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1); - services->setPixelShaderConstant("textureFlags" , (irr::s32 *)&layer2, 1); + minimap_yaw.getAs3Values(minimap_yaw_array); #endif + m_minimap_yaw.set(minimap_yaw_array, services); + + SamplerLayer_t base_tex = 0, + normal_tex = 1, + flags_tex = 2; + m_base_texture.set(&base_tex, services); + m_normal_texture.set(&normal_tex, services); + m_texture_flags.set(&flags_tex, services); + } +}; + + +class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + Client *m_client; + std::vector<GameGlobalShaderConstantSetter *> created_nosky; +public: + GameGlobalShaderConstantSetterFactory(bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(NULL), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_client(client) + {} + + void setSky(Sky *sky) { + m_sky = sky; + for (size_t i = 0; i < created_nosky.size(); ++i) { + created_nosky[i]->setSky(m_sky); + } + created_nosky.clear(); + } + + virtual IShaderConstantSetter* create() + { + GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter( + m_sky, m_force_fog_off, m_fog_range, m_client); + if (!m_sky) + created_nosky.push_back(scs); + return scs; } }; + bool nodePlacementPrediction(Client &client, const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos) { @@ -1106,12 +1183,14 @@ bool nodePlacementPrediction(Client &client, static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, IWritableTextureSource *tsrc, IrrlichtDevice *device, + JoystickController *joystick, IFormSource *fs_src, TextDest *txt_dest, Client *client) { if (*cur_formspec == 0) { - *cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, - invmgr, gamedef, tsrc, fs_src, txt_dest, client); + *cur_formspec = new GUIFormSpecMenu(device, joystick, + guiroot, -1, &g_menumgr, invmgr, gamedef, tsrc, + fs_src, txt_dest, client); (*cur_formspec)->doPause = false; /* @@ -1126,6 +1205,7 @@ static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, (*cur_formspec)->setFormSource(fs_src); (*cur_formspec)->setTextDest(txt_dest); } + } #ifdef __ANDROID__ @@ -1136,7 +1216,8 @@ static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, static void show_deathscreen(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client) + IWritableTextureSource *tsrc, IrrlichtDevice *device, + JoystickController *joystick, Client *client) { std::string formspec = std::string(FORMSPEC_VERSION_STRING) + @@ -1152,14 +1233,15 @@ static void show_deathscreen(GUIFormSpecMenu **cur_formspec, FormspecFormSource *fs_src = new FormspecFormSource(formspec); LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); - create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, + joystick, fs_src, txt_dst, NULL); } /******************************************************************************/ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, IWritableTextureSource *tsrc, IrrlichtDevice *device, - bool singleplayermode) + JoystickController *joystick, bool singleplayermode) { #ifdef __ANDROID__ std::string control_text = strgettext("Default Controls:\n" @@ -1224,7 +1306,8 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, FormspecFormSource *fs_src = new FormspecFormSource(os.str()); LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, + joystick, fs_src, txt_dst, NULL); std::string con("btn_continue"); (*cur_formspec)->setFocus(con); (*cur_formspec)->doPause = true; @@ -1240,7 +1323,11 @@ 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"", utf8_to_wide(chat_log_error_buf.get())); + std::wstring error_message = utf8_to_wide(chat_log_error_buf.get()); + if (!g_settings->getBool("disable_escape_sequences")) { + error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)"; + } + chat_backend.addMessage(L"", error_message); } // Get new messages from client @@ -1255,19 +1342,19 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, // Display all messages in a static text element unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount(); - std::wstring recent_chat = chat_backend.getRecentChat(); + EnrichedString recent_chat = chat_backend.getRecentChat(); unsigned int line_height = g_fontengine->getLineHeight(); - guitext_chat->setText(recent_chat.c_str()); + setStaticText(guitext_chat, recent_chat); // Update gui element size and position - s32 chat_y = 5 + line_height; + s32 chat_y = 5; if (show_debug) - chat_y += line_height; + chat_y += 2 * line_height; // first pass to calculate height of text to be set - s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10, + s32 width = std::min(g_fontengine->getTextWidth(recent_chat.c_str()) + 10, porting::getWindowSize().X - 20); core::rect<s32> rect(10, chat_y, width, chat_y + porting::getWindowSize().Y); guitext_chat->setRelativePosition(rect); @@ -1297,108 +1384,89 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, */ struct KeyCache { - KeyCache() { populate(); } - - enum { - // Player movement - KEYMAP_ID_FORWARD, - KEYMAP_ID_BACKWARD, - KEYMAP_ID_LEFT, - KEYMAP_ID_RIGHT, - KEYMAP_ID_JUMP, - KEYMAP_ID_SPECIAL1, - KEYMAP_ID_SNEAK, - KEYMAP_ID_AUTORUN, - - // Other - KEYMAP_ID_DROP, - KEYMAP_ID_INVENTORY, - 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, - KEYMAP_ID_TOGGLE_FORCE_FOG_OFF, - KEYMAP_ID_TOGGLE_UPDATE_CAMERA, - KEYMAP_ID_TOGGLE_DEBUG, - KEYMAP_ID_TOGGLE_PROFILER, - KEYMAP_ID_CAMERA_MODE, - KEYMAP_ID_INCREASE_VIEWING_RANGE, - KEYMAP_ID_DECREASE_VIEWING_RANGE, - KEYMAP_ID_RANGESELECT, - - KEYMAP_ID_QUICKTUNE_NEXT, - KEYMAP_ID_QUICKTUNE_PREV, - KEYMAP_ID_QUICKTUNE_INC, - KEYMAP_ID_QUICKTUNE_DEC, - - KEYMAP_ID_DEBUG_STACKS, - - // Fake keycode for array size and internal checks - KEYMAP_INTERNAL_ENUM_COUNT - - - }; + KeyCache() + { + handler = NULL; + populate(); + populate_nonchanging(); + } void populate(); - KeyPress key[KEYMAP_INTERNAL_ENUM_COUNT]; + // Keys that are not settings dependent + void populate_nonchanging(); + + KeyPress key[KeyType::INTERNAL_ENUM_COUNT]; + InputHandler *handler; }; +void KeyCache::populate_nonchanging() +{ + key[KeyType::ESC] = EscapeKey; +} + void KeyCache::populate() { - key[KEYMAP_ID_FORWARD] = getKeySetting("keymap_forward"); - key[KEYMAP_ID_BACKWARD] = getKeySetting("keymap_backward"); - key[KEYMAP_ID_LEFT] = getKeySetting("keymap_left"); - key[KEYMAP_ID_RIGHT] = getKeySetting("keymap_right"); - key[KEYMAP_ID_JUMP] = getKeySetting("keymap_jump"); - key[KEYMAP_ID_SPECIAL1] = getKeySetting("keymap_special1"); - key[KEYMAP_ID_SNEAK] = getKeySetting("keymap_sneak"); - - key[KEYMAP_ID_AUTORUN] = getKeySetting("keymap_autorun"); - - key[KEYMAP_ID_DROP] = getKeySetting("keymap_drop"); - key[KEYMAP_ID_INVENTORY] = getKeySetting("keymap_inventory"); - 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"); - key[KEYMAP_ID_TOGGLE_FORCE_FOG_OFF] + key[KeyType::FORWARD] = getKeySetting("keymap_forward"); + key[KeyType::BACKWARD] = getKeySetting("keymap_backward"); + key[KeyType::LEFT] = getKeySetting("keymap_left"); + key[KeyType::RIGHT] = getKeySetting("keymap_right"); + key[KeyType::JUMP] = getKeySetting("keymap_jump"); + key[KeyType::SPECIAL1] = getKeySetting("keymap_special1"); + key[KeyType::SNEAK] = getKeySetting("keymap_sneak"); + + key[KeyType::AUTORUN] = getKeySetting("keymap_autorun"); + + key[KeyType::DROP] = getKeySetting("keymap_drop"); + key[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); + key[KeyType::CHAT] = getKeySetting("keymap_chat"); + key[KeyType::CMD] = getKeySetting("keymap_cmd"); + key[KeyType::CONSOLE] = getKeySetting("keymap_console"); + key[KeyType::MINIMAP] = getKeySetting("keymap_minimap"); + key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove"); + key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove"); + key[KeyType::NOCLIP] = getKeySetting("keymap_noclip"); + key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic"); + key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot"); + key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); + key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); + key[KeyType::TOGGLE_FORCE_FOG_OFF] = getKeySetting("keymap_toggle_force_fog_off"); - key[KEYMAP_ID_TOGGLE_UPDATE_CAMERA] + key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera"); - key[KEYMAP_ID_TOGGLE_DEBUG] + key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug"); - key[KEYMAP_ID_TOGGLE_PROFILER] + key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler"); - key[KEYMAP_ID_CAMERA_MODE] + key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode"); - key[KEYMAP_ID_INCREASE_VIEWING_RANGE] + key[KeyType::INCREASE_VIEWING_RANGE] = getKeySetting("keymap_increase_viewing_range_min"); - key[KEYMAP_ID_DECREASE_VIEWING_RANGE] + key[KeyType::DECREASE_VIEWING_RANGE] = getKeySetting("keymap_decrease_viewing_range_min"); - key[KEYMAP_ID_RANGESELECT] + key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); + key[KeyType::ZOOM] = getKeySetting("keymap_zoom"); - key[KEYMAP_ID_QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); - key[KEYMAP_ID_QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); - key[KEYMAP_ID_QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); - key[KEYMAP_ID_QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); + key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); + key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); + key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); + key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); - key[KEYMAP_ID_DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks"); + key[KeyType::DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks"); + + if (handler) { + // First clear all keys, then re-add the ones we listen for + handler->dontListenForKeys(); + for (size_t i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + handler->listenForKey(key[i]); + } + handler->listenForKey(EscapeKey); + handler->listenForKey(CancelKey); + for (size_t i = 0; i < 10; i++) { + handler->listenForKey(NumberKey[i]); + } + } } @@ -1552,9 +1620,10 @@ protected: f32 dtime); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + // Input related void processUserInput(VolatileRunFlags *flags, GameRunData *runData, f32 dtime); - void processKeyboardInput(VolatileRunFlags *flags, + void processKeyInput(VolatileRunFlags *flags, float *statustext_time, float *jump_timer, bool *reset_jump_timer, @@ -1578,7 +1647,7 @@ protected: bool shift_pressed); void toggleFog(float *statustext_time, bool *flag); void toggleDebug(float *statustext_time, bool *show_debug, - bool *show_profiler_graph); + bool *show_profiler_graph, bool *show_wireframe); void toggleUpdateCamera(float *statustext_time, bool *flag); void toggleProfiler(float *statustext_time, u32 *profiler_current_page, u32 profiler_max_page); @@ -1587,9 +1656,10 @@ protected: void decreaseViewRange(float *statustext_time); void toggleFullViewRange(float *statustext_time); - void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags); + void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags, + float dtime); void updateCameraOrientation(CameraOrientation *cam, - const VolatileRunFlags &flags); + const VolatileRunFlags &flags, float dtime); void updatePlayerControl(const CameraOrientation &cam); void step(f32 *dtime); void processClientEvents(CameraOrientation *cam, float *damage_flash); @@ -1624,6 +1694,41 @@ protected: static void settingChangedCallback(const std::string &setting_name, void *data); void readSettings(); + inline bool getLeftClicked() + { + return input->getLeftClicked() || + input->joystick.getWasKeyDown(KeyType::MOUSE_L); + } + inline bool getRightClicked() + { + return input->getRightClicked() || + input->joystick.getWasKeyDown(KeyType::MOUSE_R); + } + inline bool isLeftPressed() + { + return input->getLeftState() || + input->joystick.isKeyDown(KeyType::MOUSE_L); + } + inline bool isRightPressed() + { + return input->getRightState() || + input->joystick.isKeyDown(KeyType::MOUSE_R); + } + inline bool getLeftReleased() + { + return input->getLeftReleased() || + input->joystick.wasKeyReleased(KeyType::MOUSE_L); + } + + inline bool isKeyDown(GameKeyType k) + { + return input->isKeyDown(keycache.key[k]) || input->joystick.isKeyDown(k); + } + inline bool wasKeyDown(GameKeyType k) + { + return input->wasKeyDown(keycache.key[k]) || input->joystick.wasKeyDown(k); + } + #ifdef __ANDROID__ void handleAndroidChatInput(); #endif @@ -1649,6 +1754,8 @@ private: ChatBackend *chat_backend; GUIFormSpecMenu *current_formspec; + //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches + std::string cur_formname; EventManager *eventmgr; QuicktuneShortcutter *quicktune; @@ -1662,6 +1769,9 @@ private: Hud *hud; Mapper *mapper; + GameRunData runData; + VolatileRunFlags flags; + /* 'cache' This class does take ownership/responsibily for cleaning up etc of any of these items (e.g. device) @@ -1707,10 +1817,16 @@ private: */ bool m_cache_doubletap_jump; bool m_cache_enable_clouds; + bool m_cache_enable_joysticks; bool m_cache_enable_particles; bool m_cache_enable_fog; + bool m_cache_enable_noclip; + bool m_cache_enable_free_move; f32 m_cache_mouse_sensitivity; + f32 m_cache_joystick_frustum_sensitivity; f32 m_repeat_right_click_time; + f32 m_cache_cam_smoothing; + f32 m_cache_fog_start; #ifdef __ANDROID__ bool m_cache_hold_aux1; @@ -1730,6 +1846,7 @@ Game::Game() : soundmaker(NULL), chat_backend(NULL), current_formspec(NULL), + cur_formname(""), eventmgr(NULL), quicktune(NULL), gui_chat_console(NULL), @@ -1745,14 +1862,28 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("enable_clouds", &settingChangedCallback, this); + g_settings->registerChangedCallback("doubletap_joysticks", + &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("joystick_frustum_sensitivity", + &settingChangedCallback, this); g_settings->registerChangedCallback("repeat_rightclick_time", &settingChangedCallback, this); + g_settings->registerChangedCallback("noclip", + &settingChangedCallback, this); + g_settings->registerChangedCallback("free_move", + &settingChangedCallback, this); + g_settings->registerChangedCallback("cinematic", + &settingChangedCallback, this); + g_settings->registerChangedCallback("cinematic_camera_smoothing", + &settingChangedCallback, this); + g_settings->registerChangedCallback("camera_smoothing", + &settingChangedCallback, this); readSettings(); @@ -1802,6 +1933,16 @@ Game::~Game() &settingChangedCallback, this); g_settings->deregisterChangedCallback("repeat_rightclick_time", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("noclip", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("free_move", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("cinematic", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("cinematic_camera_smoothing", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("camera_smoothing", + &settingChangedCallback, this); } bool Game::startup(bool *kill, @@ -1829,11 +1970,26 @@ bool Game::startup(bool *kill, this->chat_backend = chat_backend; this->simple_singleplayer_mode = simple_singleplayer_mode; + keycache.handler = input; + keycache.populate(); + driver = device->getVideoDriver(); smgr = device->getSceneManager(); smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); + memset(&runData, 0, sizeof(runData)); + runData.time_from_last_punch = 10.0; + runData.profiler_max_page = 3; + runData.update_wielded_item_trigger = true; + + memset(&flags, 0, sizeof(flags)); + flags.show_chat = true; + flags.show_hud = true; + flags.show_debug = g_settings->getBool("show_debug"); + flags.invert_mouse = g_settings->getBool("invert_mouse"); + flags.first_loop_after_window_activation = true; + if (!init(map_dir, address, port, gamespec)) return false; @@ -1850,34 +2006,15 @@ void Game::run() RunStats stats = { 0 }; CameraOrientation cam_view_target = { 0 }; CameraOrientation cam_view = { 0 }; - GameRunData runData = { 0 }; FpsControl draw_times = { 0 }; - VolatileRunFlags flags = { 0 }; f32 dtime; // in seconds - runData.time_from_last_punch = 10.0; - runData.profiler_max_page = 3; - runData.update_wielded_item_trigger = true; - - 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; - /* Clear the profiler */ Profiler::GraphValues dummyvalues; g_profiler->graphGet(dummyvalues); draw_times.last_time = device->getTimer()->getTime(); - shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( - sky, - &flags.force_fog_off, - &runData.fog_range, - client)); - set_light_table(g_settings->getFloat("display_gamma")); #ifdef __ANDROID__ @@ -1910,17 +2047,11 @@ 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_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); + updateCameraDirection(&cam_view_target, &flags, dtime); cam_view.camera_yaw += (cam_view_target.camera_yaw - - cam_view.camera_yaw) * cam_smoothing; + cam_view.camera_yaw) * m_cache_cam_smoothing; cam_view.camera_pitch += (cam_view_target.camera_pitch - - cam_view.camera_pitch) * cam_smoothing; + cam_view.camera_pitch) * m_cache_cam_smoothing; updatePlayerControl(cam_view); step(&dtime); processClientEvents(&cam_view_target, &runData.damage_flash); @@ -2117,6 +2248,10 @@ bool Game::createClient(const std::string &playername, return false; } + GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory( + &flags.force_fog_off, &runData.fog_range, client); + shader_src->addShaderConstantSetterFactory(scsf); + // Update cached textures, meshes and materials client->afterContentReceived(device); @@ -2141,6 +2276,7 @@ bool Game::createClient(const std::string &playername, /* Skybox */ sky = new Sky(smgr->getRootSceneNode(), smgr, -1, texture_src); + scsf->setSky(sky); skybox = NULL; // This is used/set later on in the main run loop local_inventory = new Inventory(itemdef_manager); @@ -2167,6 +2303,8 @@ bool Game::createClient(const std::string &playername, /* Set window caption */ std::wstring str = utf8_to_wide(PROJECT_NAME_C); + str += L" "; + str += utf8_to_wide(g_version_hash); str += L" ["; str += driver->getName(); str += L"]"; @@ -2193,37 +2331,39 @@ bool Game::createClient(const std::string &playername, bool Game::initGui() { // First line of debug text - guitext = guienv->addStaticText( + guitext = addStaticText(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), core::rect<s32>(0, 0, 0, 0), false, false, guiroot); // Second line of debug text - guitext2 = guienv->addStaticText( + guitext2 = addStaticText(guienv, L"", core::rect<s32>(0, 0, 0, 0), false, false, guiroot); // At the middle of the screen // Object infos are shown in this - guitext_info = guienv->addStaticText( + guitext_info = addStaticText(guienv, L"", core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200), false, true, guiroot); // Status text (displays info when showing and hiding GUI stuff, etc.) - guitext_status = guienv->addStaticText( + guitext_status = addStaticText(guienv, L"<Status>", core::rect<s32>(0, 0, 0, 0), false, false, guiroot); guitext_status->setVisible(false); // Chat text - guitext_chat = guienv->addStaticText( + guitext_chat = addStaticText( + guienv, L"", core::rect<s32>(0, 0, 0, 0), //false, false); // Disable word wrap as of now false, true, guiroot); + // Remove stale "recent" chat messages from previous connections chat_backend->clearRecentChat(); @@ -2237,7 +2377,7 @@ bool Game::initGui() } // Profiler text (size is updated when text is updated) - guitext_profiler = guienv->addStaticText( + guitext_profiler = addStaticText(guienv, L"<Profiler>", core::rect<s32>(0, 0, 0, 0), false, false, guiroot); @@ -2351,7 +2491,7 @@ bool Game::connectToServer(const std::string &playername, break; } - if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { *aborted = true; infostream << "Connect aborted [Escape]" << std::endl; break; @@ -2360,7 +2500,26 @@ bool Game::connectToServer(const std::string &playername, 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."; + bool sent_old_init = g_settings->getFlag("send_pre_v25_init"); + // If no pre v25 init was sent, and no answer was received, + // but the low level connection could be established + // (meaning that we have a peer id), then we probably wanted + // to connect to a legacy server. In this case, tell the user + // to enable the option to be able to connect. + if (!sent_old_init && + (client->getProtoVersion() == 0) && + client->connectedToServer()) { + *error_message = "Connection failure: init packet not " + "recognized by server.\n" + "Most likely the server uses an old protocol version (<v25).\n" + "Please ask the server owner to update to 0.4.13 or later.\n" + "To still connect to the server in the meantime,\n" + "you can enable the 'send_pre_v25_init' setting by editing minetest.conf,\n" + "or by enabling the 'Client -> Network -> Support older Servers'\n" + "entry in the advanced settings menu."; + } else { + *error_message = "Connection timed out."; + } errorstream << *error_message << std::endl; break; } @@ -2412,7 +2571,7 @@ bool Game::getServerContent(bool *aborted) return false; } - if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { *aborted = true; infostream << "Connect aborted [Escape]" << std::endl; return false; @@ -2510,7 +2669,7 @@ inline bool Game::handleCallbacks() if (g_gamecallback->changevolume_requested) { (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, client))->drop(); + &g_menumgr))->drop(); g_gamecallback->changevolume_requested = false; } @@ -2674,7 +2833,7 @@ void Game::processUserInput(VolatileRunFlags *flags, if (m_cache_doubletap_jump && runData->jump_timer <= 0.2) runData->jump_timer += dtime; - processKeyboardInput( + processKeyInput( flags, &runData->statustext_time, &runData->jump_timer, @@ -2686,7 +2845,7 @@ void Game::processUserInput(VolatileRunFlags *flags, } -void Game::processKeyboardInput(VolatileRunFlags *flags, +void Game::processKeyInput(VolatileRunFlags *flags, float *statustext_time, float *jump_timer, bool *reset_jump_timer, @@ -2696,66 +2855,68 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, //TimeTaker tt("process kybd input", NULL, PRECISION_NANO); - if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) { + if (wasKeyDown(KeyType::DROP)) { dropSelectedItem(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_AUTORUN])) { + } else if (wasKeyDown(KeyType::AUTORUN)) { toggleAutorun(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) { + } else if (wasKeyDown(KeyType::INVENTORY)) { openInventory(); - } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + } else if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { if (!gui_chat_console->isOpenInhibited()) { show_pause_menu(¤t_formspec, client, gamedef, - texture_src, device, simple_singleplayer_mode); + texture_src, device, &input->joystick, + simple_singleplayer_mode); } - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CHAT])) { + } else if (wasKeyDown(KeyType::CHAT)) { openConsole(0.2, L""); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CMD])) { + } else if (wasKeyDown(KeyType::CMD)) { openConsole(0.2, L"/"); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CONSOLE])) { + } else if (wasKeyDown(KeyType::CONSOLE)) { openConsole(1); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FREEMOVE])) { + } else if (wasKeyDown(KeyType::FREEMOVE)) { toggleFreeMove(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])) { + } else if (wasKeyDown(KeyType::JUMP)) { toggleFreeMoveAlt(statustext_time, jump_timer); *reset_jump_timer = true; - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FASTMOVE])) { + } else if (wasKeyDown(KeyType::FASTMOVE)) { toggleFast(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) { + } else if (wasKeyDown(KeyType::NOCLIP)) { toggleNoClip(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) { + } else if (wasKeyDown(KeyType::CINEMATIC)) { toggleCinematic(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) { + } else if (wasKeyDown(KeyType::SCREENSHOT)) { client->makeScreenshot(device); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) { + } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { toggleHud(statustext_time, &flags->show_hud); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) { + } else if (wasKeyDown(KeyType::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])) { + isKeyDown(KeyType::SNEAK)); + } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) { toggleChat(statustext_time, &flags->show_chat); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) { + } else if (wasKeyDown(KeyType::TOGGLE_FORCE_FOG_OFF)) { toggleFog(statustext_time, &flags->force_fog_off); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_UPDATE_CAMERA])) { + } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { toggleUpdateCamera(statustext_time, &flags->disable_camera_update); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_DEBUG])) { - toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_PROFILER])) { + } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) { + toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph, + &draw_control->show_wireframe); + } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) { toggleProfiler(statustext_time, profiler_current_page, profiler_max_page); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INCREASE_VIEWING_RANGE])) { + } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) { increaseViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DECREASE_VIEWING_RANGE])) { + } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) { decreaseViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_RANGESELECT])) { + } else if (wasKeyDown(KeyType::RANGESELECT)) { toggleFullViewRange(statustext_time); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_NEXT])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) { quicktune->next(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_PREV])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) { quicktune->prev(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_INC])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) { quicktune->inc(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_DEC])) { + } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) { quicktune->dec(); - } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DEBUG_STACKS])) { + } else if (wasKeyDown(KeyType::DEBUG_STACKS)) { // Print debug stacks dstream << "-----------------------------------------" << std::endl; @@ -2765,7 +2926,7 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, debug_stacks_print(); } - if (!input->isKeyDown(getKeySetting("keymap_jump")) && *reset_jump_timer) { + if (!isKeyDown(KeyType::JUMP) && *reset_jump_timer) { *reset_jump_timer = false; *jump_timer = 0.0; } @@ -2779,7 +2940,6 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, } } - void Game::processItemSelection(u16 *new_playeritem) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2790,14 +2950,23 @@ void Game::processItemSelection(u16 *new_playeritem) s32 wheel = input->getMouseWheel(); u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, - player->hud_hotbar_itemcount - 1); + player->hud_hotbar_itemcount - 1); - if (wheel < 0) + s32 dir = wheel; + + if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN)) { + dir = -1; + } + + if (input->joystick.wasKeyDown(KeyType::SCROLL_UP)) { + dir = 1; + } + + if (dir < 0) *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; - else if (wheel > 0) + else if (dir > 0) *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; - // else wheel == 0 - + // else dir == 0 /* Item selection using keyboard */ @@ -2847,7 +3016,8 @@ void Game::openInventory() TextDest *txt_dst = new TextDestPlayerInventory(client); create_formspec_menu(¤t_formspec, client, gamedef, texture_src, - device, fs_src, txt_dst, client); + device, &input->joystick, fs_src, txt_dst, client); + cur_formname = ""; InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); @@ -3037,22 +3207,33 @@ void Game::toggleFog(float *statustext_time, bool *flag) void Game::toggleDebug(float *statustext_time, bool *show_debug, - bool *show_profiler_graph) + bool *show_profiler_graph, bool *show_wireframe) { - // Initial / 3x toggle: Chat only + // Initial / 4x toggle: Chat only // 1x toggle: Debug text with chat // 2x toggle: Debug text with profiler graph + // 3x toggle: Debug text and wireframe if (!*show_debug) { *show_debug = true; *show_profiler_graph = false; + *show_wireframe = false; statustext = L"Debug info shown"; - } else if (*show_profiler_graph) { - *show_debug = false; - *show_profiler_graph = false; - statustext = L"Debug info and profiler graph hidden"; - } else { + } else if (!*show_profiler_graph && !*show_wireframe) { *show_profiler_graph = true; statustext = L"Profiler graph shown"; + } else if (!*show_wireframe && client->checkPrivilege("debug")) { + *show_profiler_graph = false; + *show_wireframe = true; + statustext = L"Wireframe shown"; + } else { + *show_debug = false; + *show_profiler_graph = false; + *show_wireframe = false; + if (client->checkPrivilege("debug")) { + statustext = L"Debug info, profiler graph, and wireframe hidden"; + } else { + statustext = L"Debug info and profiler graph hidden"; + } } *statustext_time = 0; } @@ -3096,9 +3277,16 @@ void Game::increaseViewRange(float *statustext_time) { s16 range = g_settings->getS16("viewing_range"); s16 range_new = range + 10; + + if (range_new > 4000) { + range_new = 4000; + statustext = utf8_to_wide("Viewing range is at maximum: " + + itos(range_new)); + } else { + statustext = utf8_to_wide("Viewing range changed to " + + itos(range_new)); + } g_settings->set("viewing_range", itos(range_new)); - statustext = utf8_to_wide("Viewing range changed to " - + itos(range_new)); *statustext_time = 0; } @@ -3108,12 +3296,15 @@ void Game::decreaseViewRange(float *statustext_time) s16 range = g_settings->getS16("viewing_range"); s16 range_new = range - 10; - if (range_new < 20) + if (range_new < 20) { range_new = 20; - + statustext = utf8_to_wide("Viewing range is at minimum: " + + itos(range_new)); + } else { + statustext = utf8_to_wide("Viewing range changed to " + + itos(range_new)); + } g_settings->set("viewing_range", itos(range_new)); - statustext = utf8_to_wide("Viewing range changed to " - + itos(range_new)); *statustext_time = 0; } @@ -3133,7 +3324,7 @@ void Game::toggleFullViewRange(float *statustext_time) void Game::updateCameraDirection(CameraOrientation *cam, - VolatileRunFlags *flags) + VolatileRunFlags *flags, float dtime) { if ((device->isWindowActive() && noMenuActive()) || random_input) { @@ -3148,7 +3339,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, if (flags->first_loop_after_window_activation) flags->first_loop_after_window_activation = false; else - updateCameraOrientation(cam, *flags); + updateCameraOrientation(cam, *flags, dtime); input->setMousePos((driver->getScreenSize().Width / 2), (driver->getScreenSize().Height / 2)); @@ -3166,16 +3357,16 @@ void Game::updateCameraDirection(CameraOrientation *cam, } } - void Game::updateCameraOrientation(CameraOrientation *cam, - const VolatileRunFlags &flags) + const VolatileRunFlags &flags, float dtime) { #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { - cam->camera_yaw = g_touchscreengui->getYaw(); - cam->camera_pitch = g_touchscreengui->getPitch(); + cam->camera_yaw += g_touchscreengui->getYawChange(); + cam->camera_pitch = g_touchscreengui->getPitch(); } else { #endif + s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2); s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2); @@ -3191,6 +3382,14 @@ void Game::updateCameraOrientation(CameraOrientation *cam, } #endif + if (m_cache_enable_joysticks) { + f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime; + cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * + c; + cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * + c; + } + cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); } @@ -3199,30 +3398,37 @@ void Game::updatePlayerControl(const CameraOrientation &cam) { //TimeTaker tt("update player control", NULL, PRECISION_NANO); + // DO NOT use the isKeyDown method for the forward, backward, left, right + // buttons, as the code that uses the controls needs to be able to + // distinguish between the two in order to know when to use joysticks. + PlayerControl control( - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]), - input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]), - input->getLeftState(), - input->getRightState(), + input->isKeyDown(keycache.key[KeyType::FORWARD]), + input->isKeyDown(keycache.key[KeyType::BACKWARD]), + input->isKeyDown(keycache.key[KeyType::LEFT]), + input->isKeyDown(keycache.key[KeyType::RIGHT]), + isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::SPECIAL1), + isKeyDown(KeyType::SNEAK), + isKeyDown(KeyType::ZOOM), + isLeftPressed(), + isRightPressed(), cam.camera_pitch, - cam.camera_yaw + cam.camera_yaw, + input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), + input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) ); 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 + ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | + ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | + ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | + ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | + ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | + ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) | + ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | + ( (u32)(isLeftPressed() & 0x1) << 7) | + ( (u32)(isRightPressed() & 0x1) << 8 ); #ifdef ANDROID @@ -3277,12 +3483,12 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) //u16 damage = event.player_damage.amount; //infostream<<"Player damage: "<<damage<<std::endl; - *damage_flash += 100.0; - *damage_flash += 8.0 * event.player_damage.amount; + *damage_flash += 95.0 + 3.2 * event.player_damage.amount; + *damage_flash = MYMIN(*damage_flash, 127.0); player->hurt_tilt_timer = 1.5; - player->hurt_tilt_strength = event.player_damage.amount / 4; - player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0); + player->hurt_tilt_strength = + rangelim(event.player_damage.amount / 4, 1.0, 4.0); MtEvent *e = new SimpleTriggerEvent("PlayerDamage"); gamedef->event()->put(e); @@ -3291,7 +3497,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) cam->camera_pitch = event.player_force_move.pitch; } else if (event.type == CE_DEATHSCREEN) { show_deathscreen(¤t_formspec, client, gamedef, texture_src, - device, client); + device, &input->joystick, client); chat_backend->addMessage(L"", L"You died."); @@ -3301,13 +3507,21 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) player->hurt_tilt_strength = 0; } else if (event.type == CE_SHOW_FORMSPEC) { - FormspecFormSource *fs_src = - new FormspecFormSource(*(event.show_formspec.formspec)); - TextDestPlayerInventory *txt_dst = - new TextDestPlayerInventory(client, *(event.show_formspec.formname)); - - create_formspec_menu(¤t_formspec, client, gamedef, - texture_src, device, fs_src, txt_dst, client); + if (*(event.show_formspec.formspec) == "") { + if (current_formspec && ( *(event.show_formspec.formname) == "" || *(event.show_formspec.formname) == cur_formname) ){ + current_formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = + new FormspecFormSource(*(event.show_formspec.formspec)); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(client, *(event.show_formspec.formname)); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, &input->joystick, + fs_src, txt_dst, client); + cur_formname = *(event.show_formspec.formname); + } delete(event.show_formspec.formspec); delete(event.show_formspec.formname); @@ -3488,13 +3702,19 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time, if (mlist && client->getPlayerItem() < mlist->getSize()) playeritem = mlist->getItem(client->getPlayerItem()); } + if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) + playeritem = hlist->getItem(0); + } + ToolCapabilities playeritem_toolcap = playeritem.getToolCapabilities(itemdef_manager); v3s16 old_camera_offset = camera->getOffset(); - if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) { + if (wasKeyDown(KeyType::CAMERA_MODE)) { GenericCAO *playercao = player->getCAO(); // If playercao not loaded, don't change camera @@ -3572,6 +3792,11 @@ void Game::processPlayerInteraction(GameRunData *runData, playeritem = mlist->getItem(client->getPlayerItem()); } + if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) + playeritem = hlist->getItem(0); + } const ItemDefinition &playeritem_def = playeritem.getDefinition(itemdef_manager); @@ -3637,7 +3862,7 @@ void Game::processPlayerInteraction(GameRunData *runData, - pointing away from node */ if (runData->digging) { - if (input->getLeftReleased()) { + if (getLeftReleased()) { infostream << "Left button released" << " (stopped digging)" << std::endl; runData->digging = false; @@ -3663,7 +3888,7 @@ void Game::processPlayerInteraction(GameRunData *runData, } } - if (!runData->digging && runData->ldown_for_dig && !input->getLeftState()) { + if (!runData->digging && runData->ldown_for_dig && !isLeftPressed()) { runData->ldown_for_dig = false; } @@ -3671,13 +3896,13 @@ void Game::processPlayerInteraction(GameRunData *runData, soundmaker->m_player_leftpunch_sound.name = ""; - if (input->getRightState()) + if (isRightPressed()) runData->repeat_rightclick_timer += dtime; else runData->repeat_rightclick_timer = 0; - if (playeritem_def.usable && input->getLeftState()) { - if (input->getLeftClicked()) + if (playeritem_def.usable && isLeftPressed()) { + if (getLeftClicked()) client->interact(4, pointed); } else if (pointed.type == POINTEDTHING_NODE) { ToolCapabilities playeritem_toolcap = @@ -3687,23 +3912,29 @@ void Game::processPlayerInteraction(GameRunData *runData, } else if (pointed.type == POINTEDTHING_OBJECT) { handlePointingAtObject(runData, pointed, playeritem, player_position, show_debug); - } else if (input->getLeftState()) { + } else if (isLeftPressed()) { // When button is held down in air, show continuous animation runData->left_punch = true; - } else if (input->getRightClicked()) { + } else if (getRightClicked()) { handlePointingAtNothing(runData, playeritem); } runData->pointed_old = pointed; - if (runData->left_punch || input->getLeftClicked()) + if (runData->left_punch || getLeftClicked()) camera->setDigging(0); // left click animation input->resetLeftClicked(); input->resetRightClicked(); + input->joystick.clearWasKeyDown(KeyType::MOUSE_L); + input->joystick.clearWasKeyDown(KeyType::MOUSE_R); + input->resetLeftReleased(); input->resetRightReleased(); + + input->joystick.clearWasKeyReleased(KeyType::MOUSE_L); + input->joystick.clearWasKeyReleased(KeyType::MOUSE_R); } @@ -3741,19 +3972,19 @@ void Game::handlePointingAtNode(GameRunData *runData, } } - if (runData->nodig_delay_timer <= 0.0 && input->getLeftState() + if (runData->nodig_delay_timer <= 0.0 && isLeftPressed() && client->checkPrivilege("interact")) { handleDigging(runData, pointed, nodepos, playeritem_toolcap, dtime); } - if ((input->getRightClicked() || + if ((getRightClicked() || runData->repeat_rightclick_timer >= m_repeat_right_click_time) && client->checkPrivilege("interact")) { runData->repeat_rightclick_timer = 0; infostream << "Ground right-clicked" << std::endl; if (meta && meta->getString("formspec") != "" && !random_input - && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + && !isKeyDown(KeyType::SNEAK)) { infostream << "Launching custom inventory view" << std::endl; InventoryLocation inventoryloc; @@ -3764,7 +3995,8 @@ void Game::handlePointingAtNode(GameRunData *runData, TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); create_formspec_menu(¤t_formspec, client, gamedef, - texture_src, device, fs_src, txt_dst, client); + texture_src, device, &input->joystick, fs_src, txt_dst, client); + cur_formname = ""; current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); } else { @@ -3818,7 +4050,7 @@ void Game::handlePointingAtObject(GameRunData *runData, runData->selected_object->debugInfoText())); } - if (input->getLeftState()) { + if (isLeftPressed()) { bool do_punch = false; bool do_punch_damage = false; @@ -3828,7 +4060,7 @@ void Game::handlePointingAtObject(GameRunData *runData, runData->object_hit_delay_timer = object_hit_delay; } - if (input->getLeftClicked()) + if (getLeftClicked()) do_punch = true; if (do_punch) { @@ -3848,7 +4080,7 @@ void Game::handlePointingAtObject(GameRunData *runData, if (!disable_send) client->interact(0, pointed); } - } else if (input->getRightClicked()) { + } else if (getRightClicked()) { infostream << "Right-clicked object" << std::endl; client->interact(3, pointed); // place } @@ -3992,7 +4224,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, if (draw_control->range_all) { runData->fog_range = 100000 * BS; } else { - runData->fog_range = 0.9 * draw_control->wanted_range * BS; + runData->fog_range = draw_control->wanted_range * BS; } /* @@ -4003,7 +4235,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, float direct_brightness; bool sunlight_seen; - if (g_settings->getBool("free_move")) { + if (m_cache_enable_noclip && m_cache_enable_free_move) { direct_brightness = time_brightness; sunlight_seen = true; } else { @@ -4070,11 +4302,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, driver->setFog( sky->getBgColor(), video::EFT_FOG_LINEAR, - runData->fog_range * 0.4, + runData->fog_range * m_cache_fog_start, runData->fog_range * 1.0, 0.01, false, // pixel fog - false // range fog + true // range fog ); } else { driver->setFog( @@ -4118,8 +4350,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, if (mlist && (client->getPlayerItem() < mlist->getSize())) { ItemStack item = mlist->getItem(client->getPlayerItem()); + if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand + InventoryList *hlist = local_inventory->getList("hand"); + if (hlist) + item = hlist->getItem(0); + } camera->wield(item); } + runData->update_wielded_item_trigger = false; } @@ -4181,10 +4419,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, Damage flash */ if (runData->damage_flash > 0.0) { - video::SColor color(std::min(runData->damage_flash, 180.0f), - 180, - 0, - 0); + video::SColor color(runData->damage_flash, 180, 0, 0); driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL); @@ -4226,23 +4461,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, inline static const char *yawToDirectionString(int yaw) { - // NOTE: TODO: This can be done mathematically without the else/else-if - // cascade. - - const char *player_direction; + static const char *direction[4] = {"North [+Z]", "West [-X]", "South [-Z]", "East [+X]"}; yaw = wrapDegrees_0_360(yaw); + yaw = (yaw + 45) % 360 / 90; - 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; + return direction[yaw]; } @@ -4275,12 +4499,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, << ", v_range = " << draw_control->wanted_range << std::setprecision(3) << ", RTT = " << client->getRTT(); - 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 << PROJECT_NAME_C " " << g_version_hash; - guitext->setText(utf8_to_wide(os.str()).c_str()); + setStaticText(guitext, utf8_to_wide(os.str()).c_str()); guitext->setVisible(true); } else { guitext->setVisible(false); @@ -4317,7 +4536,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, } } - guitext2->setText(utf8_to_wide(os.str()).c_str()); + setStaticText(guitext2, utf8_to_wide(os.str()).c_str()); guitext2->setVisible(true); core::rect<s32> rect( @@ -4329,7 +4548,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, guitext2->setVisible(false); } - guitext_info->setText(infotext.c_str()); + setStaticText(guitext_info, infotext.c_str()); guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0); float statustext_time_max = 1.5; @@ -4343,7 +4562,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, } } - guitext_status->setText(statustext.c_str()); + setStaticText(guitext_status, statustext.c_str()); guitext_status->setVisible(!statustext.empty()); if (!statustext.empty()) { @@ -4447,14 +4666,30 @@ void Game::settingChangedCallback(const std::string &setting_name, void *data) void Game::readSettings() { - m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); - 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_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); + 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_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity"); + m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + + m_cache_enable_noclip = g_settings->getBool("noclip"); + m_cache_enable_free_move = g_settings->getBool("free_move"); + + m_cache_fog_start = g_settings->getFloat("fog_start"); + + m_cache_cam_smoothing = 0; + if (g_settings->getBool("cinematic")) + m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); + else + m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); + m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f); + m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f); m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); + } /****************************************************************************/ diff --git a/src/game.h b/src/game.h index e1f4e9346..df32e3397 100644 --- a/src/game.h +++ b/src/game.h @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include <string> +#include "client/keys.h" +#include "client/joystick_controller.h" #include "keycode.h" #include <list> @@ -110,6 +112,9 @@ public: virtual bool isKeyDown(const KeyPress &keyCode) = 0; virtual bool wasKeyDown(const KeyPress &keyCode) = 0; + virtual void listenForKey(const KeyPress &keyCode) {} + virtual void dontListenForKeys() {} + virtual v2s32 getMousePos() = 0; virtual void setMousePos(s32 x, s32 y) = 0; @@ -131,6 +136,8 @@ public: virtual void step(float dtime) {} virtual void clear() {} + + JoystickController joystick; }; class ChatBackend; /* to avoid having to include chat.h */ diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 368cae1ff..c4660cf44 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -182,3 +182,15 @@ std::string gob_cmd_update_nametag_attributes(video::SColor color) writeARGB8(os, color); return os.str(); } + +std::string gob_cmd_update_infant(u16 id, u8 type, std::string client_initialization_data) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SPAWN_INFANT); + // parameters + writeU16(os, id); + writeU8(os, type); + os<<serializeLongString(client_initialization_data); + return os.str(); +} diff --git a/src/genericobject.h b/src/genericobject.h index b92570831..48e71db75 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -35,7 +35,8 @@ enum GenericCMD { GENERIC_CMD_SET_BONE_POSITION, GENERIC_CMD_ATTACH_TO, GENERIC_CMD_SET_PHYSICS_OVERRIDE, - GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES + GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES, + GENERIC_CMD_SPAWN_INFANT }; #include "object_properties.h" @@ -77,5 +78,7 @@ std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f posit std::string gob_cmd_update_nametag_attributes(video::SColor color); +std::string gob_cmd_update_infant(u16 id, u8 type, std::string client_initialization_data); + #endif diff --git a/src/gettext.h b/src/gettext.h index b8d27f77c..885d7ca2d 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -25,6 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #if USE_GETTEXT #include <libintl.h> #else + // In certain environments, some standard headers like <iomanip> + // and <locale> include libintl.h. If libintl.h is included after + // we define our gettext macro below, this causes a syntax error + // at the declaration of the gettext function in libintl.h. + // Fix this by including such a header before defining the macro. + // See issue #4446. + // Note that we can't include libintl.h directly since we're in + // the USE_GETTEXT=0 case and can't assume that gettext is installed. + #include <locale> + #define gettext(String) String #endif diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 17a1689c7..8dd5ab032 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -32,7 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #if USE_FREETYPE -#include "xCGUITTFont.h" + #include "xCGUITTFont.h" #endif inline u32 clamp_u8(s32 value) @@ -340,13 +340,28 @@ void GUIChatConsole::drawText() s32 x = (fragment.column + 1) * m_fontsize.X; core::rect<s32> destrect( x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); - m_font->draw( - fragment.text.c_str(), - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); + + + #if USE_FREETYPE + // Draw colored text if FreeType is enabled + irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(m_font); + tmp->draw( + fragment.text, + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + #else + // Otherwise use standard text + m_font->draw( + fragment.text.c_str(), + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + #endif } } } @@ -615,7 +630,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Char != 0 && !event.KeyInput.Control) { - #if (defined(linux) || defined(__linux)) + #if (defined(__linux__)) wchar_t wc = L'_'; mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); prompt.input(wc); diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index ba286a91c..a3c35f68d 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "fontengine.h" #include "guiscalingfilter.h" +#include "irrlicht_changes/static_text.h" #ifdef __ANDROID__ #include "client/tile.h" @@ -130,6 +131,7 @@ void MenuMusicFetcher::fetchSounds(const std::string &name, /** GUIEngine */ /******************************************************************************/ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, IMenuManager *menumgr, scene::ISceneManager* smgr, @@ -172,21 +174,22 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_sound_manager = &dummySoundManager; //create topleft header - std::wstring t = utf8_to_wide(std::string(PROJECT_NAME_C " ") + - g_version_hash); + m_toplefttext = L""; - core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(t), g_fontengine->getTextHeight()); + core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), + g_fontengine->getTextHeight()); rect += v2s32(4, 0); m_irr_toplefttext = - m_device->getGUIEnvironment()->addStaticText(t.c_str(), - rect,false,true,0,-1); + addStaticText(m_device->getGUIEnvironment(), m_toplefttext, + rect, false, true, 0, -1); //create formspecsource m_formspecgui = new FormspecFormSource(""); /* Create menu */ m_menu = new GUIFormSpecMenu(m_device, + joystick, m_parent, -1, m_menumanager, @@ -430,7 +433,7 @@ void GUIEngine::drawOverlay(video::IVideoDriver* driver) video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture; - /* If no texture, draw background of solid color */ + /* If no texture, draw nothing */ if(!texture) return; @@ -459,7 +462,7 @@ void GUIEngine::drawHeader(video::IVideoDriver* driver) v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, ((f32)texture->getOriginalSize().Height) * mult); - // Don't draw the header is there isn't enough room + // Don't draw the header if there isn't enough room s32 free_space = (((s32)screensize.Height)-320)/2; if (free_space > splashsize.Y) { @@ -567,18 +570,9 @@ bool GUIEngine::downloadFile(std::string url, std::string target) } /******************************************************************************/ -void GUIEngine::setTopleftText(std::string append) +void GUIEngine::setTopleftText(const std::string &text) { - std::wstring toset = utf8_to_wide(std::string(PROJECT_NAME_C " ") + - g_version_hash); - - if (append != "") - { - toset += L" / "; - toset += utf8_to_wide(append); - } - - m_irr_toplefttext->setText(toset.c_str()); + m_toplefttext = utf8_to_wide(text); updateTopLeftTextSize(); } @@ -586,15 +580,14 @@ void GUIEngine::setTopleftText(std::string append) /******************************************************************************/ void GUIEngine::updateTopLeftTextSize() { - std::wstring text = m_irr_toplefttext->getText(); - - core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(text), g_fontengine->getTextHeight()); - rect += v2s32(4, 0); + core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), + g_fontengine->getTextHeight()); + rect += v2s32(4, 0); m_irr_toplefttext->remove(); m_irr_toplefttext = - m_device->getGUIEnvironment()->addStaticText(text.c_str(), - rect,false,true,0,-1); + addStaticText(m_device->getGUIEnvironment(), m_toplefttext, + rect, false, true, 0, -1); } /******************************************************************************/ diff --git a/src/guiEngine.h b/src/guiEngine.h index d527f7222..897244808 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiFormSpecMenu.h" #include "sound.h" #include "client/tile.h" +#include "util/enriched_string.h" /******************************************************************************/ /* Typedefs and macros */ @@ -148,7 +149,8 @@ public: * @param smgr scene manager to add scene elements to * @param data struct to transfer data to main game handling */ - GUIEngine( irr::IrrlichtDevice* dev, + GUIEngine(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, IMenuManager *menumgr, scene::ISceneManager* smgr, @@ -268,13 +270,15 @@ private: void drawVersion(); /** - * specify text to be appended to version string + * specify text to appear as top left string * @param text to set */ - void setTopleftText(std::string append); + void setTopleftText(const std::string &text); /** pointer to gui element shown at topleft corner */ irr::gui::IGUIStaticText* m_irr_toplefttext; + /** and text that is in it */ + EnrichedString m_toplefttext; /** initialize cloud subsystem */ void cloudInit(); diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 2bf06c1d6..bfc7a9b79 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/hex.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() +#include "irrlicht_changes/static_text.h" #include "guiscalingfilter.h" #if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 @@ -78,6 +79,7 @@ static unsigned int font_line_height(gui::IGUIFont *font) } GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, IGameDef *gamedef, ISimpleTextureSource *tsrc, IFormSource* fsrc, TextDest* tdst, @@ -101,6 +103,8 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, m_text_dst(tdst), m_formspec_version(0), m_focused_element(""), + m_joystick(joystick), + current_field_enter_pending(""), m_font(NULL), m_remap_dbl_click(remap_dbl_click) #ifdef __ANDROID__ @@ -249,37 +253,6 @@ std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string & return NULL; } -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++) { - char si = s.c_str()[i]; - if (last_was_escape) { - current += '\\'; - current += si; - last_was_escape = false; - } else { - if (si == delim) { - tokens.push_back(current); - current = ""; - last_was_escape = false; - } else if (si == '\\') { - last_was_escape = true; - } else { - current += si; - last_was_escape = false; - } - } - } - //push last element - tokens.push_back(current); - - return tokens; -} - void GUIFormSpecMenu::parseSize(parserData* data,std::string element) { std::vector<std::string> parts = split(element,','); @@ -306,6 +279,32 @@ void GUIFormSpecMenu::parseSize(parserData* data,std::string element) errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl; } +void GUIFormSpecMenu::parseContainer(parserData* data, std::string element) +{ + std::vector<std::string> parts = split(element, ','); + + if (parts.size() >= 2) { + if (parts[1].find(';') != std::string::npos) + parts[1] = parts[1].substr(0, parts[1].find(';')); + + container_stack.push(pos_offset); + pos_offset.X += MYMAX(0, stof(parts[0])); + pos_offset.Y += MYMAX(0, stof(parts[1])); + return; + } + errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseContainerEnd(parserData* data) +{ + if (container_stack.empty()) { + errorstream<< "Invalid container end element, no matching container start element" << std::endl; + } else { + pos_offset = container_stack.top(); + container_stack.pop(); + } +} + void GUIFormSpecMenu::parseList(parserData* data,std::string element) { if (m_gamedef == 0) { @@ -336,7 +335,7 @@ void GUIFormSpecMenu::parseList(parserData* data,std::string element) else loc.deSerialize(location); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -413,7 +412,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) MY_CHECKPOS("checkbox",0); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; @@ -464,7 +463,7 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, std::string element) MY_CHECKPOS("scrollbar",0); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; @@ -522,10 +521,10 @@ void GUIFormSpecMenu::parseImage(parserData* data,std::string element) std::vector<std::string> v_geom = split(parts[1],','); std::string name = unescape_string(parts[2]); - MY_CHECKPOS("image",0); - MY_CHECKGEOM("image",1); + MY_CHECKPOS("image", 0); + MY_CHECKGEOM("image", 1); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; @@ -533,23 +532,21 @@ void GUIFormSpecMenu::parseImage(parserData* data,std::string element) geom.X = stof(v_geom[0]) * (float)imgsize.X; geom.Y = stof(v_geom[1]) * (float)imgsize.Y; - if(!data->explicit_size) + if (!data->explicit_size) warningstream<<"invalid use of image without a size[] element"<<std::endl; m_images.push_back(ImageDrawSpec(name, pos, geom)); return; - } - - if (parts.size() == 2) { + } else if (parts.size() == 2) { std::vector<std::string> v_pos = split(parts[0],','); std::string name = unescape_string(parts[1]); - MY_CHECKPOS("image",0); + MY_CHECKPOS("image", 0); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; - if(!data->explicit_size) + if (!data->explicit_size) warningstream<<"invalid use of image without a size[] element"<<std::endl; m_images.push_back(ImageDrawSpec(name, pos)); return; @@ -571,7 +568,7 @@ void GUIFormSpecMenu::parseItemImage(parserData* data,std::string element) MY_CHECKPOS("itemimage",0); MY_CHECKGEOM("itemimage",1); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; @@ -603,7 +600,7 @@ void GUIFormSpecMenu::parseButton(parserData* data,std::string element, MY_CHECKPOS("button",0); MY_CHECKGEOM("button",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -656,25 +653,25 @@ void GUIFormSpecMenu::parseBackground(parserData* data,std::string element) MY_CHECKPOS("background",0); MY_CHECKGEOM("background",1); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; - pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2; - pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; + pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X - (float)imgsize.X)/2; + pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y - (float)imgsize.Y)/2; v2s32 geom; geom.X = stof(v_geom[0]) * (float)spacing.X; geom.Y = stof(v_geom[1]) * (float)spacing.Y; - if (parts.size() == 4) { - m_clipbackground = is_yes(parts[3]); - if (m_clipbackground) { - pos.X = stoi(v_pos[0]); //acts as offset - pos.Y = stoi(v_pos[1]); //acts as offset - } + if (!data->explicit_size) + warningstream<<"invalid use of background without a size[] element"<<std::endl; + + bool clip = false; + if (parts.size() == 4 && is_yes(parts[3])) { + pos.X = stoi(v_pos[0]); //acts as offset + pos.Y = stoi(v_pos[1]); //acts as offset + clip = true; } + m_backgrounds.push_back(ImageDrawSpec(name, pos, geom, clip)); - if(!data->explicit_size) - warningstream<<"invalid use of background without a size[] element"<<std::endl; - m_backgrounds.push_back(ImageDrawSpec(name, pos, geom)); return; } errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -732,7 +729,7 @@ void GUIFormSpecMenu::parseTable(parserData* data,std::string element) MY_CHECKPOS("table",0); MY_CHECKGEOM("table",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -803,7 +800,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) MY_CHECKPOS("textlist",0); MY_CHECKGEOM("textlist",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -868,7 +865,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) MY_CHECKPOS("dropdown",0); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -917,12 +914,22 @@ void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) << element << "'" << std::endl; } +void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, + const std::string &element) +{ + std::vector<std::string> parts = split(element,';'); + if (parts.size() == 2 || + (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) { + field_close_on_enter[parts[0]] = is_yes(parts[1]); + } +} + void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) { std::vector<std::string> parts = split(element,';'); - if ((parts.size() == 4) || - ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) + if ((parts.size() == 4) || (parts.size() == 5) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) { std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_geom = split(parts[1],','); @@ -932,7 +939,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) MY_CHECKPOS("pwdfield",0); MY_CHECKGEOM("pwdfield",1); - v2s32 pos; + v2s32 pos = pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -966,7 +973,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } e->setPasswordBox(true,L'*'); @@ -979,6 +986,14 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) evt.KeyInput.Shift = 0; evt.KeyInput.PressedDown = true; e->OnEvent(evt); + + if (parts.size() >= 5) { + // TODO: remove after 2016-11-03 + warningstream << "pwdfield: use field_close_on_enter[name, enabled]" << + " instead of the 5th param" << std::endl; + field_close_on_enter[name] = is_yes(parts[4]); + } + m_fields.push_back(spec); return; } @@ -997,7 +1012,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, if(data->explicit_size) warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl; - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.Y = ((m_fields.size()+2)*60); v2s32 size = DesiredRect.getSize(); @@ -1021,7 +1036,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, if (name == "") { // spec field id to 0, this stops submit searching for a value that isn't there - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid); } else { @@ -1056,10 +1071,17 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } } + if (parts.size() >= 4) { + // TODO: remove after 2016-11-03 + warningstream << "field/simple: use field_close_on_enter[name, enabled]" << + " instead of the 4th param" << std::endl; + field_close_on_enter[name] = is_yes(parts[3]); + } + m_fields.push_back(spec); } @@ -1076,9 +1098,9 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, MY_CHECKPOS(type,0); MY_CHECKGEOM(type,1); - v2s32 pos; - pos.X = stof(v_pos[0]) * (float) spacing.X; - pos.Y = stof(v_pos[1]) * (float) spacing.Y; + v2s32 pos = pos_offset * spacing; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; v2s32 geom; @@ -1117,7 +1139,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, if (name == "") { // spec field id to 0, this stops submit searching for a value that isn't there - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid); } else { @@ -1161,9 +1183,17 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0); } } + + if (parts.size() >= 6) { + // TODO: remove after 2016-11-03 + warningstream << "field/textarea: use field_close_on_enter[name, enabled]" << + " instead of the 6th param" << std::endl; + field_close_on_enter[name] = is_yes(parts[5]); + } + m_fields.push_back(spec); } @@ -1177,8 +1207,8 @@ void GUIFormSpecMenu::parseField(parserData* data,std::string element, return; } - if ((parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + if ((parts.size() == 5) || (parts.size() == 6) || + ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) { parseTextArea(data,parts,type); return; @@ -1198,7 +1228,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) MY_CHECKPOS("label",0); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y; @@ -1230,7 +1260,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) 258+m_fields.size() ); gui::IGUIStaticText *e = - Environment->addStaticText(spec.flabel.c_str(), + addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); @@ -1255,7 +1285,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) MY_CHECKPOS("vertlabel",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; @@ -1284,7 +1314,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) 258+m_fields.size() ); gui::IGUIStaticText *t = - Environment->addStaticText(spec.flabel.c_str(), rect, false, false, this, spec.fid); + addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); m_fields.push_back(spec); return; @@ -1309,7 +1339,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element, MY_CHECKPOS("imagebutton",0); MY_CHECKGEOM("imagebutton",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; v2s32 geom; @@ -1414,7 +1444,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) spec.ftype = f_TabHeader; - v2s32 pos(0,0); + v2s32 pos = pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2; v2s32 geom; @@ -1479,7 +1509,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) MY_CHECKPOS("itemimagebutton",0); MY_CHECKGEOM("itemimagebutton",1); - v2s32 pos = padding; + v2s32 pos = padding + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float)spacing.X; pos.Y += stof(v_pos[1]) * (float)spacing.Y; v2s32 geom; @@ -1517,7 +1547,7 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) rect+=data->basepos-padding; spec.rect=rect; m_fields.push_back(spec); - pos = padding + AbsoluteRect.UpperLeftCorner; + pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; m_itemimages.push_back(ImageDrawSpec("", item_name, e, pos, geom)); @@ -1540,7 +1570,7 @@ void GUIFormSpecMenu::parseBox(parserData* data,std::string element) MY_CHECKPOS("box",0); MY_CHECKGEOM("box",1); - v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing; pos.X += stof(v_pos[0]) * (float) spacing.X; pos.Y += stof(v_pos[1]) * (float) spacing.Y; @@ -1702,8 +1732,18 @@ void GUIFormSpecMenu::parseElement(parserData* data, std::string element) std::string type = trim(parts[0]); std::string description = trim(parts[1]); + if (type == "container") { + parseContainer(data, description); + return; + } + + if (type == "container_end") { + parseContainerEnd(data); + return; + } + if (type == "list") { - parseList(data,description); + parseList(data, description); return; } @@ -1713,22 +1753,22 @@ void GUIFormSpecMenu::parseElement(parserData* data, std::string element) } if (type == "checkbox") { - parseCheckbox(data,description); + parseCheckbox(data, description); return; } if (type == "image") { - parseImage(data,description); + parseImage(data, description); return; } if (type == "item_image") { - parseItemImage(data,description); + parseItemImage(data, description); return; } - if ((type == "button") || (type == "button_exit")) { - parseButton(data,description,type); + if (type == "button" || type == "button_exit") { + parseButton(data, description, type); return; } @@ -1762,6 +1802,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, std::string element) return; } + if (type == "field_close_on_enter") { + parseFieldCloseOnEnter(data, description); + return; + } + if (type == "pwdfield") { parsePwdField(data,description); return; @@ -1905,12 +1950,11 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_slotbordercolor = video::SColor(200,0,0,0); m_slotborder = false; - m_clipbackground = false; // Add tooltip { assert(m_tooltip_element == NULL); // Note: parent != this so that the tooltip isn't clipped by the menu rectangle - m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18)); + m_tooltip_element = addStaticText(Environment, L"",core::rect<s32>(0,0,110,18)); m_tooltip_element->enableOverrideColor(true); m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); m_tooltip_element->setDrawBackground(true); @@ -2050,10 +2094,16 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); + pos_offset = v2s32(); for (; i< elements.size(); i++) { parseElement(&mydata, elements[i]); } + if (!container_stack.empty()) { + errorstream << "Invalid formspec string: container was never closed!" + << std::endl; + } + // If there are fields without explicit size[], add a "Proceed" // button and adjust size to fit all the fields. if (m_fields.size() && !mydata.explicit_size) { @@ -2255,7 +2305,6 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase, std::wstring tooltip_text = L""; if (hovering && !m_selected_item) { tooltip_text = utf8_to_wide(item.getDefinition(m_gamedef->idef()).description); - tooltip_text = unescape_enriched(tooltip_text); } if (tooltip_text != L"") { std::vector<std::wstring> tt_rows = str_split(tooltip_text, L'\n'); @@ -2263,9 +2312,13 @@ 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(tooltip_text.c_str()); + setStaticText(m_tooltip_element, tooltip_text.c_str()); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; +#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1 + s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; +#else s32 tooltip_height = m_tooltip_element->getTextHeight() + 5; +#endif v2u32 screenSize = driver->getScreenSize(); int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; @@ -2309,6 +2362,7 @@ void GUIFormSpecMenu::drawSelectedItem() core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y); core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter()); + rect.constrainTo(driver->getViewPort()); drawItemStack(driver, m_font, stack, rect, NULL, m_gamedef, IT_ROT_DRAGGED); } @@ -2354,7 +2408,7 @@ void GUIFormSpecMenu::drawMenu() // Image rectangle on screen core::rect<s32> rect = imgrect + spec.pos; - if (m_clipbackground) { + if (spec.clip) { core::dimension2d<s32> absrec_size = AbsoluteRect.getSize(); rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X, AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y, @@ -2368,8 +2422,7 @@ void GUIFormSpecMenu::drawMenu() core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL/*&AbsoluteClippingRect*/, colors, true); - } - else { + } else { errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl; errorstream << "\t" << spec.name << std::endl; } @@ -2486,7 +2539,7 @@ void GUIFormSpecMenu::drawMenu() Draw static text elements */ for (u32 i = 0; i < m_static_texts.size(); i++) { - const StaticTextSpec &spec = m_static_texts[i]; + const StaticTextSpec &spec = m_static_texts[i]; core::rect<s32> rect = spec.rect; if (spec.parent_button && spec.parent_button->isPressed()) { #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) @@ -2531,8 +2584,10 @@ void GUIFormSpecMenu::drawMenu() iter != m_fields.end(); ++iter) { if (iter->fid == id && m_tooltips[iter->fname].tooltip != L"") { if (m_old_tooltip != m_tooltips[iter->fname].tooltip) { + m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor); + m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color); m_old_tooltip = m_tooltips[iter->fname].tooltip; - m_tooltip_element->setText(m_tooltips[iter->fname].tooltip.c_str()); + setStaticText(m_tooltip_element, m_tooltips[iter->fname].tooltip.c_str()); std::vector<std::wstring> tt_rows = str_split(m_tooltips[iter->fname].tooltip, L'\n'); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; @@ -2554,8 +2609,6 @@ void GUIFormSpecMenu::drawMenu() core::position2d<s32>(tooltip_x, tooltip_y), core::dimension2d<s32>(tooltip_width, tooltip_height))); } - m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor); - m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color); m_tooltip_element->setVisible(true); this->bringToFront(m_tooltip_element); break; @@ -2564,6 +2617,8 @@ void GUIFormSpecMenu::drawMenu() } } + m_tooltip_element->draw(); + /* Draw dragged item stack */ @@ -2718,6 +2773,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) current_keys_pending.key_enter = false; } + if (!current_field_enter_pending.empty()) { + fields["key_enter_field"] = current_field_enter_pending; + current_field_enter_pending = ""; + } + if (current_keys_pending.key_escape) { fields["key_escape"] = "true"; current_keys_pending.key_escape = false; @@ -3049,6 +3109,25 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } #endif + if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { + /* TODO add a check like: + if (event.JoystickEvent != joystick_we_listen_for) + return false; + */ + bool handled = m_joystick->handleEvent(event.JoystickEvent); + if (handled) { + if (m_joystick->wasKeyDown(KeyType::ESC)) { + tryClose(); + } else if (m_joystick->wasKeyDown(KeyType::JUMP)) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } + } + } + return handled; + } + return false; } @@ -3110,19 +3189,24 @@ bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) return false; } +void GUIFormSpecMenu::tryClose() +{ + if (m_allowclose) { + doPause = false; + acceptInput(quit_mode_cancel); + quitMenu(); + } else { + m_text_dst->gotText(L"MenuQuit"); + } +} + bool GUIFormSpecMenu::OnEvent(const SEvent& event) { if (event.EventType==EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); if (event.KeyInput.PressedDown && ( (kp == EscapeKey) || (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { - if (m_allowclose) { - doPause = false; - acceptInput(quit_mode_cancel); - quitMenu(); - } else { - m_text_dst->gotText(L"MenuQuit"); - } + tryClose(); return true; } else if (m_client != NULL && event.KeyInput.PressedDown && (kp == getKeySetting("keymap_screenshot"))) { @@ -3624,8 +3708,23 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { if (event.GUIEvent.Caller->getID() > 257) { + bool close_on_enter = true; + for (u32 i = 0; i < m_fields.size(); i++) { + FieldSpec &s = m_fields[i]; + if (s.ftype == f_Unknown && + s.fid == event.GUIEvent.Caller->getID()) { + current_field_enter_pending = s.fname; + UNORDERED_MAP<std::string, bool>::const_iterator it = + field_close_on_enter.find(s.fname); + if (it != field_close_on_enter.end()) + close_on_enter = (*it).second; + + break; + } + } - if (m_allowclose) { + if (m_allowclose && close_on_enter) { + current_keys_pending.key_enter = true; acceptInput(quit_mode_accept); quitMenu(); } else { diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index ef230c81c..95df11e6a 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define GUIINVENTORYMENU_HEADER #include <utility> +#include <stack> #include "irrlichttypes_extrabloated.h" #include "inventory.h" @@ -29,7 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modalMenu.h" #include "guiTable.h" #include "network/networkprotocol.h" +#include "client/joystick_controller.h" #include "util/string.h" +#include "util/enriched_string.h" class IGameDef; class InventoryManager; @@ -141,9 +144,10 @@ class GUIFormSpecMenu : public GUIModalMenu struct ImageDrawSpec { ImageDrawSpec(): - parent_button(NULL) - { - } + parent_button(NULL), + clip(false) + {} + ImageDrawSpec(const std::string &a_name, const std::string &a_item_name, gui::IGUIButton *a_parent_button, @@ -153,9 +157,10 @@ class GUIFormSpecMenu : public GUIModalMenu parent_button(a_parent_button), pos(a_pos), geom(a_geom), - scale(true) - { - } + scale(true), + clip(false) + {} + ImageDrawSpec(const std::string &a_name, const std::string &a_item_name, const v2s32 &a_pos, const v2s32 &a_geom): @@ -164,32 +169,36 @@ class GUIFormSpecMenu : public GUIModalMenu parent_button(NULL), pos(a_pos), geom(a_geom), - scale(true) - { - } + scale(true), + clip(false) + {} + ImageDrawSpec(const std::string &a_name, - const v2s32 &a_pos, const v2s32 &a_geom): + const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false): name(a_name), parent_button(NULL), pos(a_pos), geom(a_geom), - scale(true) - { - } + scale(true), + clip(clip) + {} + ImageDrawSpec(const std::string &a_name, const v2s32 &a_pos): name(a_name), parent_button(NULL), pos(a_pos), - scale(false) - { - } + scale(false), + clip(false) + {} + std::string name; std::string item_name; gui::IGUIButton *parent_button; v2s32 pos; v2s32 geom; bool scale; + bool clip; }; struct FieldSpec @@ -200,13 +209,14 @@ class GUIFormSpecMenu : public GUIModalMenu FieldSpec(const std::string &name, const std::wstring &label, const std::wstring &default_text, int id) : fname(name), - fid(id) + flabel(label), + fid(id), + send(false), + ftype(f_Unknown), + is_exit(false) { - flabel = unescape_enriched(label); + //flabel = unescape_enriched(label); fdefault = unescape_enriched(default_text); - send = false; - ftype = f_Unknown; - is_exit = false; } std::string fname; std::wstring flabel; @@ -239,7 +249,8 @@ class GUIFormSpecMenu : public GUIModalMenu bgcolor(a_bgcolor), color(a_color) { - tooltip = unescape_enriched(utf8_to_wide(a_tooltip)); + //tooltip = unescape_enriched(utf8_to_wide(a_tooltip)); + tooltip = utf8_to_wide(a_tooltip); } std::wstring tooltip; irr::video::SColor bgcolor; @@ -256,7 +267,8 @@ class GUIFormSpecMenu : public GUIModalMenu rect(a_rect), parent_button(NULL) { - text = unescape_enriched(a_text); + //text = unescape_enriched(a_text); + text = a_text; } StaticTextSpec(const std::wstring &a_text, const core::rect<s32> &a_rect, @@ -264,7 +276,8 @@ class GUIFormSpecMenu : public GUIModalMenu rect(a_rect), parent_button(a_parent_button) { - text = unescape_enriched(a_text); + //text = unescape_enriched(a_text); + text = a_text; } std::wstring text; core::rect<s32> rect; @@ -273,6 +286,7 @@ class GUIFormSpecMenu : public GUIModalMenu public: GUIFormSpecMenu(irr::IrrlichtDevice* dev, + JoystickController *joystick, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, @@ -365,6 +379,8 @@ protected: v2s32 spacing; v2s32 imgsize; v2s32 offset; + v2s32 pos_offset; + std::stack<v2s32> container_stack; irr::IrrlichtDevice* m_device; InventoryManager *m_invmgr; @@ -382,6 +398,7 @@ protected: std::vector<ImageDrawSpec> m_images; std::vector<ImageDrawSpec> m_itemimages; std::vector<BoxDrawSpec> m_boxes; + UNORDERED_MAP<std::string, bool> field_close_on_enter; std::vector<FieldSpec> m_fields; std::vector<StaticTextSpec> m_static_texts; std::vector<std::pair<FieldSpec,GUITable*> > m_tables; @@ -391,8 +408,6 @@ protected: std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns; ItemSpec *m_selected_item; - f32 m_timer1; - f32 m_timer2; u32 m_selected_amount; bool m_selected_dragging; @@ -419,7 +434,6 @@ protected: bool m_bgfullscreen; bool m_slotborder; - bool m_clipbackground; video::SColor m_bgcolor; video::SColor m_slotbg_n; video::SColor m_slotbg_h; @@ -428,10 +442,11 @@ protected: video::SColor m_default_tooltip_color; private: - IFormSource *m_form_src; - TextDest *m_text_dst; - unsigned int m_formspec_version; - std::string m_focused_element; + IFormSource *m_form_src; + TextDest *m_text_dst; + unsigned int m_formspec_version; + std::string m_focused_element; + JoystickController *m_joystick; typedef struct { bool explicit_size; @@ -444,7 +459,7 @@ private: GUITable::TableOptions table_options; GUITable::TableColumns table_columns; // used to restore table selection/scroll/treeview state - std::map<std::string, GUITable::DynamicData> table_dyndata; + UNORDERED_MAP<std::string, GUITable::DynamicData> table_dyndata; } parserData; typedef struct { @@ -455,14 +470,17 @@ private: } fs_key_pendig; fs_key_pendig current_keys_pending; + std::string current_field_enter_pending; - void parseElement(parserData* data,std::string element); + void parseElement(parserData* data, std::string element); - 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 parseSize(parserData* data, std::string element); + void parseContainer(parserData* data, std::string element); + void parseContainerEnd(parserData* data); + 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); void parseButton(parserData* data,std::string element,std::string typ); void parseBackground(parserData* data,std::string element); @@ -471,6 +489,7 @@ private: void parseTable(parserData* data,std::string element); void parseTextList(parserData* data,std::string element); void parseDropDown(parserData* data,std::string element); + void parseFieldCloseOnEnter(parserData *data, const std::string &element); void parsePwdField(parserData* data,std::string element); void parseField(parserData* data,std::string element,std::string type); void parseSimpleField(parserData* data,std::vector<std::string> &parts); @@ -489,6 +508,8 @@ private: bool parseSizeDirect(parserData* data, std::string element); void parseScrollBar(parserData* data, std::string element); + void tryClose(); + /** * check if event is part of a double click * @param event event to evaluate @@ -547,4 +568,3 @@ public: }; #endif - diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index b05818256..07137d1bc 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -59,6 +59,7 @@ enum GUI_ID_KEY_INVENTORY_BUTTON, GUI_ID_KEY_DUMP_BUTTON, GUI_ID_KEY_RANGE_BUTTON, + GUI_ID_KEY_ZOOM_BUTTON, // other GUI_ID_CB_AUX1_DESCENDS, GUI_ID_CB_DOUBLETAP_JUMP, @@ -137,7 +138,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { key_setting *k = key_settings.at(i); { - core::rect < s32 > rect(0, 0, 110, 20); + core::rect < s32 > rect(0, 0, 150, 20); rect += topleft + v2s32(offset.X, offset.Y); Environment->addStaticText(k->button_name, rect, false, true, this, -1); } @@ -414,5 +415,6 @@ void GUIKeyChangeMenu::init_keys() 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_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom"); } diff --git a/src/guiTable.cpp b/src/guiTable.cpp index 3cc95ce4f..6b33b8266 100644 --- a/src/guiTable.cpp +++ b/src/guiTable.cpp @@ -565,10 +565,8 @@ void GUITable::setSelected(s32 index) --index; // Switch from 1-based indexing to 0-based indexing s32 rowcount = m_rows.size(); - if (rowcount == 0) { + if (rowcount == 0 || index < 0) { return; - } else if (index < 0) { - index = 0; } else if (index >= rowcount) { index = rowcount - 1; } diff --git a/src/guiVolumeChange.cpp b/src/guiVolumeChange.cpp index c8e257f7f..8425bc04f 100644 --- a/src/guiVolumeChange.cpp +++ b/src/guiVolumeChange.cpp @@ -37,8 +37,7 @@ const int ID_soundSlider = 266; GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - Client* client + IMenuManager *menumgr ): GUIModalMenu(env, parent, id, menumgr) { @@ -51,26 +50,17 @@ GUIVolumeChange::~GUIVolumeChange() void GUIVolumeChange::removeChildren() { - { - gui::IGUIElement *e = getElementFromId(ID_soundText1); - if(e != NULL) - e->remove(); - } - { - gui::IGUIElement *e = getElementFromId(ID_soundText2); - if(e != NULL) - e->remove(); - } - { - gui::IGUIElement *e = getElementFromId(ID_soundExitButton); - if(e != NULL) - e->remove(); - } - { - gui::IGUIElement *e = getElementFromId(ID_soundSlider); - if(e != NULL) - e->remove(); - } + if (gui::IGUIElement *e = getElementFromId(ID_soundText1)) + e->remove(); + + if (gui::IGUIElement *e = getElementFromId(ID_soundText2)) + e->remove(); + + if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton)) + e->remove(); + + if (gui::IGUIElement *e = getElementFromId(ID_soundSlider)) + e->remove(); } void GUIVolumeChange::regenerateGui(v2u32 screensize) @@ -95,7 +85,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) v2s32 size = rect.getSize(); v2s32 topleft_client(40, 0); - int volume=(int)(g_settings->getFloat("sound_volume")*100); + int volume = (int)(g_settings->getFloat("sound_volume")*100); /* Add stuff */ @@ -137,45 +127,43 @@ void GUIVolumeChange::drawMenu() if (!skin) return; video::IVideoDriver* driver = Environment->getVideoDriver(); - video::SColor bgcolor(140,0,0,0); + video::SColor bgcolor(140, 0, 0, 0); driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); gui::IGUIElement::draw(); } bool GUIVolumeChange::OnEvent(const SEvent& event) { - if(event.EventType==EET_KEY_INPUT_EVENT) - { - if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) - { + if (event.EventType == EET_KEY_INPUT_EVENT) { + if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) { quitMenu(); return true; } - if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) - { + + if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) { quitMenu(); return true; } } - if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) - { - if (event.GUIEvent.Caller->getID() == ID_soundExitButton) - { - quitMenu(); - return true; - } + + if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { + if (event.GUIEvent.Caller->getID() == ID_soundExitButton) { + quitMenu(); + return true; } - if(event.GUIEvent.EventType==gui::EGET_SCROLL_BAR_CHANGED) - { - if (event.GUIEvent.Caller->getID() == ID_soundSlider) - { - s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos(); - g_settings->setFloat("sound_volume",(float)pos/100); - gui::IGUIElement *e = getElementFromId(ID_soundText2); - e->setText( core::stringw(pos).c_str() ); - return true; - } + } + + if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { + if (event.GUIEvent.Caller->getID() == ID_soundSlider) { + s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos(); + g_settings->setFloat("sound_volume", (float)pos/100); + + gui::IGUIElement *e = getElementFromId(ID_soundText2); + e->setText(core::stringw(pos).c_str()); + return true; } + } + return Parent ? Parent->OnEvent(event) : false; } diff --git a/src/guiVolumeChange.h b/src/guiVolumeChange.h index 9f8199fa8..98731c673 100644 --- a/src/guiVolumeChange.h +++ b/src/guiVolumeChange.h @@ -30,8 +30,7 @@ class GUIVolumeChange : public GUIModalMenu public: GUIVolumeChange(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, - IMenuManager *menumgr, - Client* client); + IMenuManager *menumgr); ~GUIVolumeChange(); void removeChildren(); diff --git a/src/hud.cpp b/src/hud.cpp index 19feaef7b..43d957380 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -544,6 +544,12 @@ void Hud::drawSelectionMesh() video::SMaterial oldmaterial = driver->getMaterial2D(); driver->setMaterial(m_selection_material); setMeshColor(m_selection_mesh, m_selection_mesh_color); + video::SColor face_color(0, + MYMIN(255, m_selection_mesh_color.getRed() * 1.5), + MYMIN(255, m_selection_mesh_color.getGreen() * 1.5), + MYMIN(255, m_selection_mesh_color.getBlue() * 1.5)); + setMeshColorByNormal(m_selection_mesh, m_selected_face_normal, + face_color); scene::IMesh* mesh = cloneMesh(m_selection_mesh); translateMesh(mesh, m_selection_pos_with_offset); u32 mc = m_selection_mesh->getMeshBufferCount(); @@ -139,8 +139,11 @@ public: v3f getSelectionPos() const { return m_selection_pos; } - void setSelectionMeshColor(const video::SColor &c) - { m_selection_mesh_color = c; } + void setSelectionMeshColor(const video::SColor &color) + { m_selection_mesh_color = color; } + + void setSelectedFaceNormal(const v3f &face_normal) + { m_selected_face_normal = face_normal; } void drawLuaElements(const v3s16 &camera_offset); @@ -169,6 +172,8 @@ private: scene::IMesh* m_selection_mesh; video::SColor m_selection_mesh_color; + v3f m_selected_face_normal; + video::SMaterial m_selection_material; bool m_use_selection_mesh; }; diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp index 33bf8a13c..29f828076 100644 --- a/src/intlGUIEditBox.cpp +++ b/src/intlGUIEditBox.cpp @@ -271,7 +271,7 @@ bool intlGUIEditBox::OnEvent(const SEvent& event) break; case EET_KEY_INPUT_EVENT: { -#if (defined(linux) || defined(__linux) || defined(__FreeBSD__)) +#if (defined(__linux__) || defined(__FreeBSD__)) // ################################################################ // ValkaTR: // This part is the difference from the original intlGUIEditBox diff --git a/src/inventory.h b/src/inventory.h index a690eb5ae..7d7e58d61 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -80,15 +80,14 @@ struct ItemStack // Maximum size of a stack u16 getStackMax(IItemDefManager *itemdef) const { - s16 max = itemdef->get(name).stack_max; - return (max >= 0) ? max : 0; + return itemdef->get(name).stack_max; } // Number of items that can be added to this stack u16 freeSpace(IItemDefManager *itemdef) const { u16 max = getStackMax(itemdef); - if(count > max) + if (count >= max) return 0; return max - count; } diff --git a/src/irrlicht_changes/CMakeLists.txt b/src/irrlicht_changes/CMakeLists.txt new file mode 100644 index 000000000..3a265c99d --- /dev/null +++ b/src/irrlicht_changes/CMakeLists.txt @@ -0,0 +1,7 @@ +if (BUILD_CLIENT) + set(client_irrlicht_changes_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/static_text.cpp + PARENT_SCOPE + ) +endif() + diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp new file mode 100644 index 000000000..703287eb3 --- /dev/null +++ b/src/irrlicht_changes/static_text.cpp @@ -0,0 +1,679 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// Copyright (C) 2016 NathanaĆ«l Courant: +// Modified the functions to use EnrichedText instead of string. +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "static_text.h" +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include <vector> +#include <string> +#include <iostream> +#include <IGUISkin.h> +#include <IGUIEnvironment.h> +#include <IGUIFont.h> +#include <IVideoDriver.h> +#include <rect.h> +#include <SColor.h> + +#if USE_FREETYPE + #include "cguittfont/xCGUITTFont.h" +#endif + +#include "util/string.h" + +namespace irr +{ + +#if USE_FREETYPE + +namespace gui +{ +//! constructor +StaticText::StaticText(const EnrichedString &text, bool border, + IGUIEnvironment* environment, IGUIElement* parent, + s32 id, const core::rect<s32>& rectangle, + bool background) +: IGUIStaticText(environment, parent, id, rectangle), + HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), + Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background), + RestrainTextInside(true), RightToLeft(false), + OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)), + OverrideFont(0), LastBreakFont(0) +{ + #ifdef _DEBUG + setDebugName("StaticText"); + #endif + + Text = text.c_str(); + cText = text; + if (environment && environment->getSkin()) + { + BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE); + } +} + + +//! destructor +StaticText::~StaticText() +{ + if (OverrideFont) + OverrideFont->drop(); +} + +//! draws the element and its children +void StaticText::draw() +{ + if (!IsVisible) + return; + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + core::rect<s32> frameRect(AbsoluteRect); + + // draw background + + if (Background) + { + if ( !OverrideBGColorEnabled ) // skin-colors can change + BGColor = skin->getColor(gui::EGDC_3D_FACE); + + driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect); + } + + // draw the border + + if (Border) + { + skin->draw3DSunkenPane(this, 0, true, false, frameRect, &AbsoluteClippingRect); + frameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X); + } + + // draw the text + if (cText.size()) + { + IGUIFont* font = getActiveFont(); + + if (font) + { + if (!WordWrap) + { + // TODO: add colors here + if (VAlign == EGUIA_LOWERRIGHT) + { + frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - + font->getDimension(L"A").Height - font->getKerningHeight(); + } + if (HAlign == EGUIA_LOWERRIGHT) + { + frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X - + font->getDimension(cText.c_str()).Width; + } + + irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); + tmp->draw(cText, frameRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), + HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + } + else + { + if (font != LastBreakFont) + breakText(); + + core::rect<s32> r = frameRect; + s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + s32 totalHeight = height * BrokenText.size(); + if (VAlign == EGUIA_CENTER) + { + r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); + } + else if (VAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; + } + + irr::video::SColor previous_color(255, 255, 255, 255); + for (u32 i=0; i<BrokenText.size(); ++i) + { + if (HAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.X = frameRect.LowerRightCorner.X - + font->getDimension(BrokenText[i].c_str()).Width; + } + + //std::vector<irr::video::SColor> colors; + //std::wstring str; + EnrichedString str = BrokenText[i]; + + //str = colorizeText(BrokenText[i].c_str(), colors, previous_color); + //if (!colors.empty()) + // previous_color = colors[colors.size() - 1]; + + irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); + tmp->draw(str, r, + previous_color, // FIXME + HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + + r.LowerRightCorner.Y += height; + r.UpperLeftCorner.Y += height; + } + } + } + } + + IGUIElement::draw(); +} + + +//! Sets another skin independent font. +void StaticText::setOverrideFont(IGUIFont* font) +{ + if (OverrideFont == font) + return; + + if (OverrideFont) + OverrideFont->drop(); + + OverrideFont = font; + + if (OverrideFont) + OverrideFont->grab(); + + breakText(); +} + +//! Gets the override font (if any) +IGUIFont * StaticText::getOverrideFont() const +{ + return OverrideFont; +} + +//! Get the font which is used right now for drawing +IGUIFont* StaticText::getActiveFont() const +{ + if ( OverrideFont ) + return OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (skin) + return skin->getFont(); + return 0; +} + +//! Sets another color for the text. +void StaticText::setOverrideColor(video::SColor color) +{ + OverrideColor = color; + OverrideColorEnabled = true; +} + + +//! Sets another color for the text. +void StaticText::setBackgroundColor(video::SColor color) +{ + BGColor = color; + OverrideBGColorEnabled = true; + Background = true; +} + + +//! Sets whether to draw the background +void StaticText::setDrawBackground(bool draw) +{ + Background = draw; +} + + +//! Gets the background color +video::SColor StaticText::getBackgroundColor() const +{ + return BGColor; +} + + +//! Checks if background drawing is enabled +bool StaticText::isDrawBackgroundEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return Background; +} + + +//! Sets whether to draw the border +void StaticText::setDrawBorder(bool draw) +{ + Border = draw; +} + + +//! Checks if border drawing is enabled +bool StaticText::isDrawBorderEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return Border; +} + + +void StaticText::setTextRestrainedInside(bool restrainTextInside) +{ + RestrainTextInside = restrainTextInside; +} + + +bool StaticText::isTextRestrainedInside() const +{ + return RestrainTextInside; +} + + +void StaticText::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + HAlign = horizontal; + VAlign = vertical; +} + + +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 +const video::SColor& StaticText::getOverrideColor() const +#else +video::SColor StaticText::getOverrideColor() const +#endif +{ + return OverrideColor; +} + + +//! Sets if the static text should use the overide color or the +//! color in the gui skin. +void StaticText::enableOverrideColor(bool enable) +{ + OverrideColorEnabled = enable; +} + + +bool StaticText::isOverrideColorEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return OverrideColorEnabled; +} + + +//! Enables or disables word wrap for using the static text as +//! multiline text control. +void StaticText::setWordWrap(bool enable) +{ + WordWrap = enable; + breakText(); +} + + +bool StaticText::isWordWrapEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return WordWrap; +} + + +void StaticText::setRightToLeft(bool rtl) +{ + if (RightToLeft != rtl) + { + RightToLeft = rtl; + breakText(); + } +} + + +bool StaticText::isRightToLeft() const +{ + return RightToLeft; +} + + +//! Breaks the single text line. +void StaticText::breakText() +{ + if (!WordWrap) + return; + + BrokenText.clear(); + + IGUISkin* skin = Environment->getSkin(); + IGUIFont* font = getActiveFont(); + if (!font) + return; + + LastBreakFont = font; + + EnrichedString line; + EnrichedString word; + EnrichedString whitespace; + s32 size = cText.size(); + s32 length = 0; + s32 elWidth = RelativeRect.getWidth(); + if (Border) + elWidth -= 2*skin->getSize(EGDS_TEXT_DISTANCE_X); + wchar_t c; + + //std::vector<irr::video::SColor> colors; + + // We have to deal with right-to-left and left-to-right differently + // However, most parts of the following code is the same, it's just + // some order and boundaries which change. + if (!RightToLeft) + { + // regular (left-to-right) + for (s32 i=0; i<size; ++i) + { + c = cText.getString()[i]; + bool lineBreak = false; + + if (c == L'\r') // Mac or Windows breaks + { + lineBreak = true; + //if (Text[i+1] == L'\n') // Windows breaks + //{ + // Text.erase(i+1); + // --size; + //} + c = '\0'; + } + else if (c == L'\n') // Unix breaks + { + lineBreak = true; + c = '\0'; + } + + bool isWhitespace = (c == L' ' || c == 0); + if ( !isWhitespace ) + { + // part of a word + //word += c; + word.addChar(cText, i); + } + + if ( isWhitespace || i == (size-1)) + { + if (word.size()) + { + // here comes the next whitespace, look if + // we must break the last word to the next line. + const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; + //const std::wstring sanitized = removeEscapes(word.c_str()); + const s32 wordlgth = font->getDimension(word.c_str()).Width; + + if (wordlgth > elWidth) + { + // This word is too long to fit in the available space, look for + // the Unicode Soft HYphen (SHY / 00AD) character for a place to + // break the word at + int where = core::stringw(word.c_str()).findFirst( wchar_t(0x00AD) ); + if (where != -1) + { + EnrichedString first = word.substr(0, where); + EnrichedString second = word.substr(where, word.size() - where); + first.addCharNoColor(L'-'); + BrokenText.push_back(line + first); + const s32 secondLength = font->getDimension(second.c_str()).Width; + + length = secondLength; + line = second; + } + else + { + // No soft hyphen found, so there's nothing more we can do + // break to next line + if (length) + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + } + else if (length && (length + wordlgth + whitelgth > elWidth)) + { + // break to next line + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + else + { + // add word to line + line += whitespace; + line += word; + length += whitelgth + wordlgth; + } + + word.clear(); + whitespace.clear(); + } + + if ( isWhitespace && c != 0) + { + whitespace.addChar(cText, i); + } + + // compute line break + if (lineBreak) + { + line += whitespace; + line += word; + BrokenText.push_back(line); + line.clear(); + word.clear(); + whitespace.clear(); + length = 0; + } + } + } + + line += whitespace; + line += word; + BrokenText.push_back(line); + } + else + { + // right-to-left + for (s32 i=size; i>=0; --i) + { + c = cText.getString()[i]; + bool lineBreak = false; + + if (c == L'\r') // Mac or Windows breaks + { + lineBreak = true; + //if ((i>0) && Text[i-1] == L'\n') // Windows breaks + //{ + // Text.erase(i-1); + // --size; + //} + c = '\0'; + } + else if (c == L'\n') // Unix breaks + { + lineBreak = true; + c = '\0'; + } + + if (c==L' ' || c==0 || i==0) + { + if (word.size()) + { + // here comes the next whitespace, look if + // we must break the last word to the next line. + const s32 whitelgth = font->getDimension(whitespace.c_str()).Width; + const s32 wordlgth = font->getDimension(word.c_str()).Width; + + if (length && (length + wordlgth + whitelgth > elWidth)) + { + // break to next line + BrokenText.push_back(line); + length = wordlgth; + line = word; + } + else + { + // add word to line + line = whitespace + line; + line = word + line; + length += whitelgth + wordlgth; + } + + word.clear(); + whitespace.clear(); + } + + if (c != 0) + // whitespace = core::stringw(&c, 1) + whitespace; + whitespace = cText.substr(i, 1) + whitespace; + + // compute line break + if (lineBreak) + { + line = whitespace + line; + line = word + line; + BrokenText.push_back(line); + line.clear(); + word.clear(); + whitespace.clear(); + length = 0; + } + } + else + { + // yippee this is a word.. + //word = core::stringw(&c, 1) + word; + word = cText.substr(i, 1) + word; + } + } + + line = whitespace + line; + line = word + line; + BrokenText.push_back(line); + } +} + + +//! Sets the new caption of this element. +void StaticText::setText(const wchar_t* text) +{ + setText(EnrichedString(text)); +} + +//! Sets the new caption of this element. +void StaticText::setText(const EnrichedString &text) +{ + IGUIElement::setText(text.c_str()); + cText = text; + if (text.hasBackground()) { + setBackgroundColor(text.getBackground()); + } + breakText(); +} + + +void StaticText::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + breakText(); +} + + +//! Returns the height of the text in pixels when it is drawn. +s32 StaticText::getTextHeight() const +{ + IGUIFont* font = getActiveFont(); + if (!font) + return 0; + + s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + + if (WordWrap) + height *= BrokenText.size(); + + return height; +} + + +s32 StaticText::getTextWidth() const +{ + IGUIFont * font = getActiveFont(); + if(!font) + return 0; + + if(WordWrap) + { + s32 widest = 0; + + for(u32 line = 0; line < BrokenText.size(); ++line) + { + s32 width = font->getDimension(BrokenText[line].c_str()).Width; + + if(width > widest) + widest = width; + } + + return widest; + } + else + { + return font->getDimension(cText.c_str()).Width; + } +} + + +//! Writes attributes of the element. +//! Implement this to expose the attributes of your element for +//! scripting languages, editors, debuggers or xml serialization purposes. +void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + IGUIStaticText::serializeAttributes(out,options); + + out->addBool ("Border", Border); + out->addBool ("OverrideColorEnabled",OverrideColorEnabled); + out->addBool ("OverrideBGColorEnabled",OverrideBGColorEnabled); + out->addBool ("WordWrap", WordWrap); + out->addBool ("Background", Background); + out->addBool ("RightToLeft", RightToLeft); + out->addBool ("RestrainTextInside", RestrainTextInside); + out->addColor ("OverrideColor", OverrideColor); + out->addColor ("BGColor", BGColor); + out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + + // out->addFont ("OverrideFont", OverrideFont); +} + + +//! Reads attributes of the element +void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + IGUIStaticText::deserializeAttributes(in,options); + + Border = in->getAttributeAsBool("Border"); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled"); + setWordWrap(in->getAttributeAsBool("WordWrap")); + Background = in->getAttributeAsBool("Background"); + RightToLeft = in->getAttributeAsBool("RightToLeft"); + RestrainTextInside = in->getAttributeAsBool("RestrainTextInside"); + OverrideColor = in->getAttributeAsColor("OverrideColor"); + BGColor = in->getAttributeAsColor("BGColor"); + + setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + + // OverrideFont = in->getAttributeAsFont("OverrideFont"); +} + +} // end namespace gui + +#endif // USE_FREETYPE + +} // end namespace irr + + +#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h new file mode 100644 index 000000000..408a12784 --- /dev/null +++ b/src/irrlicht_changes/static_text.h @@ -0,0 +1,268 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// Copyright (C) 2016 NathanaĆ«l Courant +// Modified this class to work with EnrichedStrings too +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_GUI_STATIC_TEXT_H_INCLUDED__ +#define __C_GUI_STATIC_TEXT_H_INCLUDED__ + +#include "IrrCompileConfig.h" +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIStaticText.h" +#include "irrArray.h" + +#include "log.h" + +#include <vector> + +#include "util/enriched_string.h" +#include "config.h" +#include <IGUIEnvironment.h> + +#if USE_FREETYPE + +namespace irr +{ + +namespace gui +{ + + const EGUI_ELEMENT_TYPE EGUIET_ENRICHED_STATIC_TEXT = (EGUI_ELEMENT_TYPE)(0x1000); + + class StaticText : public IGUIStaticText + { + public: + + //! constructor + StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment, + IGUIElement* parent, s32 id, const core::rect<s32>& rectangle, + bool background = false); + + //! destructor + virtual ~StaticText(); + + //! draws the element and its children + virtual void draw(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont* font=0); + + //! Gets the override font (if any) + virtual IGUIFont* getOverrideFont() const; + + //! Get the font which is used right now for drawing + virtual IGUIFont* getActiveFont() const; + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Sets another color for the background. + virtual void setBackgroundColor(video::SColor color); + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); + + //! Gets the background color + virtual video::SColor getBackgroundColor() const; + + //! Checks if background drawing is enabled + virtual bool isDrawBackgroundEnabled() const; + + //! Sets whether to draw the border + virtual void setDrawBorder(bool draw); + + //! Checks if border drawing is enabled + virtual bool isDrawBorderEnabled() const; + + //! Sets alignment mode for text + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! Gets the override color + #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7 + virtual const video::SColor& getOverrideColor() const; + #else + virtual video::SColor getOverrideColor() const; + #endif + + //! Sets if the static 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 + virtual bool isOverrideColorEnabled() const; + + //! Set whether the text in this label should be clipped if it goes outside bounds + virtual void setTextRestrainedInside(bool restrainedInside); + + //! Checks if the text in this label should be clipped if it goes outside bounds + virtual bool isTextRestrainedInside() const; + + //! Enables or disables word wrap for using the static text as + //! multiline text control. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + virtual bool isWordWrapEnabled() const; + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text); + + //! Returns the height of the text in pixels when it is drawn. + virtual s32 getTextHeight() const; + + //! Returns the width of the current text, in the current font + virtual s32 getTextWidth() const; + + //! Updates the absolute position, splits text if word wrap is enabled + virtual void updateAbsolutePosition(); + + //! Set whether the string should be interpreted as right-to-left (RTL) text + /** \note This component does not implement the Unicode bidi standard, the + text of the component should be already RTL if you call this. The + main difference when RTL is enabled is that the linebreaks for multiline + elements are performed starting from the end. + */ + virtual void setRightToLeft(bool rtl); + + //! Checks if the text should be interpreted as right-to-left text + virtual bool isRightToLeft() const; + + //! 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); + + virtual bool hasType(EGUI_ELEMENT_TYPE t) const { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); + }; + + virtual bool hasType(EGUI_ELEMENT_TYPE t) { + return (t == EGUIET_ENRICHED_STATIC_TEXT) || (t == EGUIET_STATIC_TEXT); + }; + + void setText(const EnrichedString &text); + + private: + + //! Breaks the single text line. + void breakText(); + + EGUI_ALIGNMENT HAlign, VAlign; + bool Border; + bool OverrideColorEnabled; + bool OverrideBGColorEnabled; + bool WordWrap; + bool Background; + bool RestrainTextInside; + bool RightToLeft; + + video::SColor OverrideColor, BGColor; + gui::IGUIFont* OverrideFont; + gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. + + EnrichedString cText; + core::array< EnrichedString > BrokenText; + }; + + +} // end namespace gui + +} // end namespace irr + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) +{ + if (parent == NULL) { + // parent is NULL, so we must find one, or we need not to drop + // result, but then there will be a memory leak. + // + // What Irrlicht does is to use guienv as a parent, but the problem + // is that guienv is here only an IGUIEnvironment, while it is a + // CGUIEnvironment in Irrlicht, which inherits from both IGUIElement + // and IGUIEnvironment. + // + // A solution would be to dynamic_cast guienv to a + // IGUIElement*, but Irrlicht is shipped without rtti support + // in some distributions, causing the dymanic_cast to segfault. + // + // Thus, to find the parent, we create a dummy StaticText and ask + // for its parent, and then remove it. + irr::gui::IGUIStaticText *dummy_text = + guienv->addStaticText(L"", rectangle, border, wordWrap, + parent, id, fillBackground); + parent = dummy_text->getParent(); + dummy_text->remove(); + } + irr::gui::IGUIStaticText *result = new irr::gui::StaticText( + text, border, guienv, parent, + id, rectangle, fillBackground); + + result->setWordWrap(wordWrap); + result->drop(); + return result; +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +{ + // dynamic_cast not possible due to some distributions shipped + // without rtti support in irrlicht + if (static_text->hasType(irr::gui::EGUIET_ENRICHED_STATIC_TEXT)) { + irr::gui::StaticText* stext = static_cast<irr::gui::StaticText*>(static_text); + stext->setText(text); + } else { + static_text->setText(text.c_str()); + } +} + +#else // USE_FREETYPE + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const EnrichedString &text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) +{ + return guienv->addStaticText(text.c_str(), rectangle, border, wordWrap, parent, id, fillBackground); +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedString &text) +{ + static_text->setText(text.c_str()); +} + +#endif + +inline irr::gui::IGUIStaticText *addStaticText( + irr::gui::IGUIEnvironment *guienv, + const wchar_t *text, + const core::rect< s32 > &rectangle, + bool border = false, + bool wordWrap = true, + irr::gui::IGUIElement *parent = NULL, + s32 id = -1, + bool fillBackground = false) { + return addStaticText(guienv, EnrichedString(text), rectangle, border, wordWrap, parent, id, fillBackground); +} + +inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text) +{ + setStaticText(static_text, EnrichedString(text)); +} + +#endif // _IRR_COMPILE_WITH_GUI_ + +#endif // C_GUI_STATIC_TEXT_H_INCLUDED diff --git a/src/itemdef.cpp b/src/itemdef.cpp index a618ad631..1aa6331dc 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -146,9 +146,9 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const } os<<serializeString(tool_capabilities_s); writeU16(os, groups.size()); - for(std::map<std::string, int>::const_iterator + for (ItemGroupList::const_iterator i = groups.begin(); i != groups.end(); ++i){ - os<<serializeString(i->first); + os << serializeString(i->first); writeS16(os, i->second); } os<<serializeString(node_placement_prediction); @@ -466,11 +466,17 @@ public: infostream<<"ItemDefManager: erased alias "<<def.name <<" because item was defined"<<std::endl; } + virtual void unregisterItem(const std::string &name) + { + verbosestream<<"ItemDefManager: unregistering \""<<name<<"\""<<std::endl; + + delete m_item_definitions[name]; + m_item_definitions.erase(name); + } virtual void registerAlias(const std::string &name, const std::string &convert_to) { - if(m_item_definitions.find(name) == m_item_definitions.end()) - { + if (m_item_definitions.find(name) == m_item_definitions.end()) { verbosestream<<"ItemDefManager: setting alias "<<name <<" -> "<<convert_to<<std::endl; m_aliases[name] = convert_to; diff --git a/src/itemdef.h b/src/itemdef.h index 805b4aa5d..dcb98e8a9 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -61,7 +61,7 @@ struct ItemDefinition /* Item stack and interaction properties */ - s16 stack_max; + u16 stack_max; bool usable; bool liquids_pointable; // May be NULL. If non-NULL, deleted by destructor @@ -144,6 +144,7 @@ public: virtual void clear()=0; // Register item definition virtual void registerItem(const ItemDefinition &def)=0; + virtual void unregisterItem(const std::string &name)=0; // Set an alias so that items named <name> will load as <convert_to>. // Alias is not set if <name> has already been defined. // Alias will be removed if <name> is defined at a later point of time. diff --git a/src/itemgroup.h b/src/itemgroup.h index f6ae86736..f91ccc221 100644 --- a/src/itemgroup.h +++ b/src/itemgroup.h @@ -21,14 +21,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #define ITEMGROUP_HEADER #include <string> -#include <map> +#include "util/cpp11_container.h" -typedef std::map<std::string, int> ItemGroupList; +typedef UNORDERED_MAP<std::string, int> ItemGroupList; static inline int itemgroup_get(const ItemGroupList &groups, const std::string &name) { - std::map<std::string, int>::const_iterator i = groups.find(name); + ItemGroupList::const_iterator i = groups.find(name); if(i == groups.end()) return 0; return i->second; diff --git a/src/json/CMakeLists.txt b/src/jsoncpp/json/CMakeLists.txt index 9056e4b6d..9056e4b6d 100644 --- a/src/json/CMakeLists.txt +++ b/src/jsoncpp/json/CMakeLists.txt diff --git a/src/json/UPDATING b/src/jsoncpp/json/UPDATING index d00076601..d00076601 100644 --- a/src/json/UPDATING +++ b/src/jsoncpp/json/UPDATING diff --git a/src/json/json.h b/src/jsoncpp/json/json.h index 396aafa82..396aafa82 100644 --- a/src/json/json.h +++ b/src/jsoncpp/json/json.h diff --git a/src/json/jsoncpp.cpp b/src/jsoncpp/json/jsoncpp.cpp index 7a04736de..7a04736de 100644 --- a/src/json/jsoncpp.cpp +++ b/src/jsoncpp/json/jsoncpp.cpp diff --git a/src/keycode.cpp b/src/keycode.cpp index 990dee339..2e211ad59 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "debug.h" #include "util/hex.h" +#include "util/string.h" class UnknownKeycode : public BaseException { @@ -31,321 +32,335 @@ public: BaseException(s) {}; }; -#define CHECKKEY(x){if(strcmp(name, #x)==0) return irr::x;} +struct table_key { + const char *Name; + irr::EKEY_CODE Key; + wchar_t Char; // L'\0' means no character assigned + const char *LangName; // NULL means it doesn't have a human description +}; -irr::EKEY_CODE keyname_to_keycode(const char *name) +#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ + { #x, irr::x, L'\0', lang }, +#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \ + { #x, irr::x, ch, lang }, +#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \ + { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) }, +#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \ + { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) }, +#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \ + { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch }, + +#define N_(text) text + +static const struct table_key table[] = { + // Keys that can be reliably mapped between Char and Key + DEFINEKEY3(0) + DEFINEKEY3(1) + DEFINEKEY3(2) + DEFINEKEY3(3) + DEFINEKEY3(4) + DEFINEKEY3(5) + DEFINEKEY3(6) + DEFINEKEY3(7) + DEFINEKEY3(8) + DEFINEKEY3(9) + DEFINEKEY3(A) + DEFINEKEY3(B) + DEFINEKEY3(C) + DEFINEKEY3(D) + DEFINEKEY3(E) + DEFINEKEY3(F) + DEFINEKEY3(G) + DEFINEKEY3(H) + DEFINEKEY3(I) + DEFINEKEY3(J) + DEFINEKEY3(K) + DEFINEKEY3(L) + DEFINEKEY3(M) + DEFINEKEY3(N) + DEFINEKEY3(O) + DEFINEKEY3(P) + DEFINEKEY3(Q) + DEFINEKEY3(R) + DEFINEKEY3(S) + DEFINEKEY3(T) + DEFINEKEY3(U) + DEFINEKEY3(V) + DEFINEKEY3(W) + DEFINEKEY3(X) + DEFINEKEY3(Y) + DEFINEKEY3(Z) + DEFINEKEY2(KEY_PLUS, L'+', "+") + DEFINEKEY2(KEY_COMMA, L',', ",") + DEFINEKEY2(KEY_MINUS, L'-', "-") + DEFINEKEY2(KEY_PERIOD, L'.', ".") + + // Keys without a Char + DEFINEKEY1(KEY_LBUTTON, N_("Left Button")) + DEFINEKEY1(KEY_RBUTTON, N_("Right Button")) + DEFINEKEY1(KEY_CANCEL, N_("Cancel")) + DEFINEKEY1(KEY_MBUTTON, N_("Middle Button")) + DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1")) + DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2")) + DEFINEKEY1(KEY_BACK, N_("Back")) + DEFINEKEY1(KEY_TAB, N_("Tab")) + DEFINEKEY1(KEY_CLEAR, N_("Clear")) + DEFINEKEY1(KEY_RETURN, N_("Return")) + DEFINEKEY1(KEY_SHIFT, N_("Shift")) + DEFINEKEY1(KEY_CONTROL, N_("Control")) + DEFINEKEY1(KEY_MENU, N_("Menu")) + DEFINEKEY1(KEY_PAUSE, N_("Pause")) + DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock")) + DEFINEKEY1(KEY_SPACE, N_("Space")) + DEFINEKEY1(KEY_PRIOR, N_("Prior")) + DEFINEKEY1(KEY_NEXT, N_("Next")) + DEFINEKEY1(KEY_END, N_("End")) + DEFINEKEY1(KEY_HOME, N_("Home")) + DEFINEKEY1(KEY_LEFT, N_("Left")) + DEFINEKEY1(KEY_UP, N_("Up")) + DEFINEKEY1(KEY_RIGHT, N_("Right")) + DEFINEKEY1(KEY_DOWN, N_("Down")) + DEFINEKEY1(KEY_SELECT, N_("Select")) + DEFINEKEY1(KEY_PRINT, N_("Print")) + DEFINEKEY1(KEY_EXECUT, N_("Execute")) + DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot")) + DEFINEKEY1(KEY_INSERT, N_("Insert")) + DEFINEKEY1(KEY_DELETE, N_("Delete")) + DEFINEKEY1(KEY_HELP, N_("Help")) + DEFINEKEY1(KEY_LWIN, N_("Left Windows")) + DEFINEKEY1(KEY_RWIN, N_("Right Windows")) + DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char + DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9]. + DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2")) + DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3")) + DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4")) + DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5")) + DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6")) + DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7")) + DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8")) + DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9")) + DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *")) + DEFINEKEY1(KEY_ADD, N_("Numpad +")) + DEFINEKEY1(KEY_SEPARATOR, N_("Numpad .")) + DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -")) + DEFINEKEY1(KEY_DECIMAL, NULL) + DEFINEKEY1(KEY_DIVIDE, N_("Numpad /")) + DEFINEKEY4(1) + DEFINEKEY4(2) + DEFINEKEY4(3) + DEFINEKEY4(4) + DEFINEKEY4(5) + DEFINEKEY4(6) + DEFINEKEY4(7) + DEFINEKEY4(8) + DEFINEKEY4(9) + DEFINEKEY4(10) + DEFINEKEY4(11) + DEFINEKEY4(12) + DEFINEKEY4(13) + DEFINEKEY4(14) + DEFINEKEY4(15) + DEFINEKEY4(16) + DEFINEKEY4(17) + DEFINEKEY4(18) + DEFINEKEY4(19) + DEFINEKEY4(20) + DEFINEKEY4(21) + DEFINEKEY4(22) + DEFINEKEY4(23) + DEFINEKEY4(24) + DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock")) + DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock")) + DEFINEKEY1(KEY_LSHIFT, N_("Left Shift")) + DEFINEKEY1(KEY_RSHIFT, N_("Right Shift")) + DEFINEKEY1(KEY_LCONTROL, N_("Left Control")) + DEFINEKEY1(KEY_RCONTROL, N_("Right Control")) + DEFINEKEY1(KEY_LMENU, N_("Left Menu")) + DEFINEKEY1(KEY_RMENU, N_("Right Menu")) + + // Rare/weird keys + DEFINEKEY1(KEY_KANA, "Kana") + DEFINEKEY1(KEY_HANGUEL, "Hangul") + DEFINEKEY1(KEY_HANGUL, "Hangul") + DEFINEKEY1(KEY_JUNJA, "Junja") + DEFINEKEY1(KEY_FINAL, "Final") + DEFINEKEY1(KEY_KANJI, "Kanji") + DEFINEKEY1(KEY_HANJA, "Hanja") + DEFINEKEY1(KEY_ESCAPE, N_("IME Escape")) + DEFINEKEY1(KEY_CONVERT, N_("IME Convert")) + DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert")) + DEFINEKEY1(KEY_ACCEPT, N_("IME Accept")) + DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change")) + DEFINEKEY1(KEY_APPS, N_("Apps")) + DEFINEKEY1(KEY_SLEEP, N_("Sleep")) +#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3) + DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple + DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char + DEFINEKEY1(KEY_OEM_3, "OEM 3") + DEFINEKEY1(KEY_OEM_4, "OEM 4") + DEFINEKEY1(KEY_OEM_5, "OEM 5") + DEFINEKEY1(KEY_OEM_6, "OEM 6") + DEFINEKEY1(KEY_OEM_7, "OEM 7") + DEFINEKEY1(KEY_OEM_8, "OEM 8") + DEFINEKEY1(KEY_OEM_AX, "OEM AX") + DEFINEKEY1(KEY_OEM_102, "OEM 102") +#endif + DEFINEKEY1(KEY_ATTN, "Attn") + DEFINEKEY1(KEY_CRSEL, "CrSel") + DEFINEKEY1(KEY_EXSEL, "ExSel") + DEFINEKEY1(KEY_EREOF, N_("Erase EOF")) + DEFINEKEY1(KEY_PLAY, N_("Play")) + DEFINEKEY1(KEY_ZOOM, N_("Zoom")) + DEFINEKEY1(KEY_PA1, "PA1") + DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear")) + + // Keys without Irrlicht keycode + DEFINEKEY5("!") + DEFINEKEY5("\"") + DEFINEKEY5("#") + DEFINEKEY5("$") + DEFINEKEY5("%") + DEFINEKEY5("&") + DEFINEKEY5("'") + DEFINEKEY5("(") + DEFINEKEY5(")") + DEFINEKEY5("*") + DEFINEKEY5("/") + DEFINEKEY5(":") + DEFINEKEY5(";") + DEFINEKEY5("<") + DEFINEKEY5("=") + DEFINEKEY5(">") + DEFINEKEY5("?") + DEFINEKEY5("@") + DEFINEKEY5("[") + DEFINEKEY5("\\") + DEFINEKEY5("]") + DEFINEKEY5("^") + DEFINEKEY5("_") +}; + +#undef N_ + +#define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) + +struct table_key lookup_keyname(const char *name) { - CHECKKEY(KEY_LBUTTON) - CHECKKEY(KEY_RBUTTON) - CHECKKEY(KEY_CANCEL) - CHECKKEY(KEY_MBUTTON) - CHECKKEY(KEY_XBUTTON1) - CHECKKEY(KEY_XBUTTON2) - CHECKKEY(KEY_BACK) - CHECKKEY(KEY_TAB) - CHECKKEY(KEY_CLEAR) - CHECKKEY(KEY_RETURN) - CHECKKEY(KEY_SHIFT) - CHECKKEY(KEY_CONTROL) - CHECKKEY(KEY_MENU) - CHECKKEY(KEY_PAUSE) - CHECKKEY(KEY_CAPITAL) - CHECKKEY(KEY_KANA) - CHECKKEY(KEY_HANGUEL) - CHECKKEY(KEY_HANGUL) - CHECKKEY(KEY_JUNJA) - CHECKKEY(KEY_FINAL) - CHECKKEY(KEY_HANJA) - CHECKKEY(KEY_KANJI) - CHECKKEY(KEY_ESCAPE) - CHECKKEY(KEY_CONVERT) - CHECKKEY(KEY_NONCONVERT) - CHECKKEY(KEY_ACCEPT) - CHECKKEY(KEY_MODECHANGE) - CHECKKEY(KEY_SPACE) - CHECKKEY(KEY_PRIOR) - CHECKKEY(KEY_NEXT) - CHECKKEY(KEY_END) - CHECKKEY(KEY_HOME) - CHECKKEY(KEY_LEFT) - CHECKKEY(KEY_UP) - CHECKKEY(KEY_RIGHT) - CHECKKEY(KEY_DOWN) - CHECKKEY(KEY_SELECT) - CHECKKEY(KEY_PRINT) - CHECKKEY(KEY_EXECUT) - CHECKKEY(KEY_SNAPSHOT) - CHECKKEY(KEY_INSERT) - CHECKKEY(KEY_DELETE) - CHECKKEY(KEY_HELP) - CHECKKEY(KEY_KEY_0) - CHECKKEY(KEY_KEY_1) - CHECKKEY(KEY_KEY_2) - CHECKKEY(KEY_KEY_3) - CHECKKEY(KEY_KEY_4) - CHECKKEY(KEY_KEY_5) - CHECKKEY(KEY_KEY_6) - CHECKKEY(KEY_KEY_7) - CHECKKEY(KEY_KEY_8) - CHECKKEY(KEY_KEY_9) - CHECKKEY(KEY_KEY_A) - CHECKKEY(KEY_KEY_B) - CHECKKEY(KEY_KEY_C) - CHECKKEY(KEY_KEY_D) - CHECKKEY(KEY_KEY_E) - CHECKKEY(KEY_KEY_F) - CHECKKEY(KEY_KEY_G) - CHECKKEY(KEY_KEY_H) - CHECKKEY(KEY_KEY_I) - CHECKKEY(KEY_KEY_J) - CHECKKEY(KEY_KEY_K) - CHECKKEY(KEY_KEY_L) - CHECKKEY(KEY_KEY_M) - CHECKKEY(KEY_KEY_N) - CHECKKEY(KEY_KEY_O) - CHECKKEY(KEY_KEY_P) - CHECKKEY(KEY_KEY_Q) - CHECKKEY(KEY_KEY_R) - CHECKKEY(KEY_KEY_S) - CHECKKEY(KEY_KEY_T) - CHECKKEY(KEY_KEY_U) - CHECKKEY(KEY_KEY_V) - CHECKKEY(KEY_KEY_W) - CHECKKEY(KEY_KEY_X) - CHECKKEY(KEY_KEY_Y) - CHECKKEY(KEY_KEY_Z) - CHECKKEY(KEY_LWIN) - CHECKKEY(KEY_RWIN) - CHECKKEY(KEY_APPS) - CHECKKEY(KEY_SLEEP) - CHECKKEY(KEY_NUMPAD0) - CHECKKEY(KEY_NUMPAD1) - CHECKKEY(KEY_NUMPAD2) - CHECKKEY(KEY_NUMPAD3) - CHECKKEY(KEY_NUMPAD4) - CHECKKEY(KEY_NUMPAD5) - CHECKKEY(KEY_NUMPAD6) - CHECKKEY(KEY_NUMPAD7) - CHECKKEY(KEY_NUMPAD8) - CHECKKEY(KEY_NUMPAD9) - CHECKKEY(KEY_MULTIPLY) - CHECKKEY(KEY_ADD) - CHECKKEY(KEY_SEPARATOR) - CHECKKEY(KEY_SUBTRACT) - CHECKKEY(KEY_DECIMAL) - CHECKKEY(KEY_DIVIDE) - CHECKKEY(KEY_F1) - CHECKKEY(KEY_F2) - CHECKKEY(KEY_F3) - CHECKKEY(KEY_F4) - CHECKKEY(KEY_F5) - CHECKKEY(KEY_F6) - CHECKKEY(KEY_F7) - CHECKKEY(KEY_F8) - CHECKKEY(KEY_F9) - CHECKKEY(KEY_F10) - CHECKKEY(KEY_F11) - CHECKKEY(KEY_F12) - CHECKKEY(KEY_F13) - CHECKKEY(KEY_F14) - CHECKKEY(KEY_F15) - CHECKKEY(KEY_F16) - CHECKKEY(KEY_F17) - CHECKKEY(KEY_F18) - CHECKKEY(KEY_F19) - CHECKKEY(KEY_F20) - CHECKKEY(KEY_F21) - CHECKKEY(KEY_F22) - CHECKKEY(KEY_F23) - CHECKKEY(KEY_F24) - CHECKKEY(KEY_NUMLOCK) - CHECKKEY(KEY_SCROLL) - CHECKKEY(KEY_LSHIFT) - CHECKKEY(KEY_RSHIFT) - CHECKKEY(KEY_LCONTROL) - CHECKKEY(KEY_RCONTROL) - CHECKKEY(KEY_LMENU) - CHECKKEY(KEY_RMENU) - CHECKKEY(KEY_PLUS) - CHECKKEY(KEY_COMMA) - CHECKKEY(KEY_MINUS) - CHECKKEY(KEY_PERIOD) - CHECKKEY(KEY_ATTN) - CHECKKEY(KEY_CRSEL) - CHECKKEY(KEY_EXSEL) - CHECKKEY(KEY_EREOF) - CHECKKEY(KEY_PLAY) - CHECKKEY(KEY_ZOOM) - CHECKKEY(KEY_PA1) - CHECKKEY(KEY_OEM_CLEAR) + for (u16 i = 0; i < ARRAYSIZE(table); i++) { + if (strcmp(table[i].Name, name) == 0) + return table[i]; + } throw UnknownKeycode(name); } -static const char *KeyNames[] = -{ "-", "KEY_LBUTTON", "KEY_RBUTTON", "KEY_CANCEL", "KEY_MBUTTON", "KEY_XBUTTON1", - "KEY_XBUTTON2", "-", "KEY_BACK", "KEY_TAB", "-", "-", "KEY_CLEAR", "KEY_RETURN", "-", - "-", "KEY_SHIFT", "KEY_CONTROL", "KEY_MENU", "KEY_PAUSE", "KEY_CAPITAL", "KEY_KANA", "-", - "KEY_JUNJA", "KEY_FINAL", "KEY_KANJI", "-", "KEY_ESCAPE", "KEY_CONVERT", "KEY_NONCONVERT", - "KEY_ACCEPT", "KEY_MODECHANGE", "KEY_SPACE", "KEY_PRIOR", "KEY_NEXT", "KEY_END", - "KEY_HOME", "KEY_LEFT", "KEY_UP", "KEY_RIGHT", "KEY_DOWN", "KEY_SELECT", "KEY_PRINT", - "KEY_EXECUTE", "KEY_SNAPSHOT", "KEY_INSERT", "KEY_DELETE", "KEY_HELP", "KEY_KEY_0", - "KEY_KEY_1", "KEY_KEY_2", "KEY_KEY_3", "KEY_KEY_4", "KEY_KEY_5", - "KEY_KEY_6", "KEY_KEY_7", "KEY_KEY_8", "KEY_KEY_9", "-", "-", "-", "-", - "-", "-", "-", "KEY_KEY_A", "KEY_KEY_B", "KEY_KEY_C", "KEY_KEY_D", - "KEY_KEY_E", "KEY_KEY_F", "KEY_KEY_G", "KEY_KEY_H", "KEY_KEY_I", - "KEY_KEY_J", "KEY_KEY_K", "KEY_KEY_L", "KEY_KEY_M", "KEY_KEY_N", - "KEY_KEY_O", "KEY_KEY_P", "KEY_KEY_Q", "KEY_KEY_R", "KEY_KEY_S", - "KEY_KEY_T", "KEY_KEY_U", "KEY_KEY_V", "KEY_KEY_W", "KEY_KEY_X", - "KEY_KEY_Y", "KEY_KEY_Z", "KEY_LWIN", "KEY_RWIN", "KEY_APPS", "-", - "KEY_SLEEP", "KEY_NUMPAD0", "KEY_NUMPAD1", "KEY_NUMPAD2", "KEY_NUMPAD3", - "KEY_NUMPAD4", "KEY_NUMPAD5", "KEY_NUMPAD6", "KEY_NUMPAD7", - "KEY_NUMPAD8", "KEY_NUMPAD9", "KEY_MULTIPLY", "KEY_ADD", "KEY_SEPERATOR", - "KEY_SUBTRACT", "KEY_DECIMAL", "KEY_DIVIDE", "KEY_F1", "KEY_F2", "KEY_F3", - "KEY_F4", "KEY_F5", "KEY_F6", "KEY_F7", "KEY_F8", "KEY_F9", "KEY_F10", - "KEY_F11", "KEY_F12", "KEY_F13", "KEY_F14", "KEY_F15", "KEY_F16", - "KEY_F17", "KEY_F18", "KEY_F19", "KEY_F20", "KEY_F21", "KEY_F22", - "KEY_F23", "KEY_F24", "-", "-", "-", "-", "-", "-", "-", "-", - "KEY_NUMLOCK", "KEY_SCROLL", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "KEY_LSHIFT", "KEY_RSHIFT", "KEY_LCONTROL", - "KEY_RCONTROL", "KEY_LMENU", "KEY_RMENU", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "KEY_PLUS", "KEY_COMMA", "KEY_MINUS", "KEY_PERIOD", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "KEY_ATTN", "KEY_CRSEL", "KEY_EXSEL", - "KEY_EREOF", "KEY_PLAY", "KEY_ZOOM", "KEY_PA1", "KEY_OEM_CLEAR", "-" }; +struct table_key lookup_keykey(irr::EKEY_CODE key) +{ + for (u16 i = 0; i < ARRAYSIZE(table); i++) { + if (table[i].Key == key) + return table[i]; + } -#define N_(text) text + std::ostringstream os; + os << "<Keycode " << (int) key << ">"; + throw UnknownKeycode(os.str().c_str()); +} -static const char *KeyNamesLang[] = - { "-", N_("Left Button"), N_("Right Button"), N_("Cancel"), N_("Middle Button"), N_("X Button 1"), - N_("X Button 2"), "-", N_("Back"), N_("Tab"), "-", "-", N_("Clear"), N_("Return"), "-", - "-", N_("Shift"), N_("Control"), N_("Menu"), N_("Pause"), N_("Capital"), N_("Kana"), "-", - N_("Junja"), N_("Final"), N_("Kanji"), "-", N_("Escape"), N_("Convert"), N_("Nonconvert"), - N_("Accept"), N_("Mode Change"), N_("Space"), N_("Prior"), N_("Next"), N_("End"), N_("Home"), - N_("Left"), N_("Up"), N_("Right"), N_("Down"), N_("Select"), N_("Print"), N_("Execute"), - N_("Snapshot"), N_("Insert"), N_("Delete"), N_("Help"), "0", "1", "2", "3", "4", "5", - "6", "7", "8", "9", "-", "-", "-", "-", "-", "-", "-", "A", "B", "C", - "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", - "R", "S", "T", "U", "V", "W", "X", "Y", "Z", N_("Left Windows"), - N_("Right Windows"), N_("Apps"), "-", N_("Sleep"), N_("Numpad 0"), N_("Numpad 1"), - N_("Numpad 2"), N_("Numpad 3"), N_("Numpad 4"), N_("Numpad 5"), N_("Numpad 6"), N_("Numpad 7"), - N_("Numpad 8"), N_("Numpad 9"), N_("Numpad *"), N_("Numpad +"), N_("Numpad /"), N_("Numpad -"), - "Numpad .", "Numpad /", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", - "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", - "F19", "F20", "F21", "F22", "F23", "F24", "-", "-", "-", "-", "-", "-", - "-", "-", N_("Num Lock"), N_("Scroll Lock"), "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", N_("Left Shift"), N_("Right Shift"), - N_("Left Control"), N_("Right Control"), N_("Left Menu"), N_("Right Menu"), "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", N_("Plus"), N_("Comma"), N_("Minus"), N_("Period"), "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", - "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", N_("Attn"), N_("CrSel"), - N_("ExSel"), N_("Erase OEF"), N_("Play"), N_("Zoom"), N_("PA1"), N_("OEM Clear"), "-" }; +struct table_key lookup_keychar(wchar_t Char) +{ + for (u16 i = 0; i < ARRAYSIZE(table); i++) { + if (table[i].Char == Char) + return table[i]; + } -#undef N_ + std::ostringstream os; + os << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">"; + throw UnknownKeycode(os.str().c_str()); +} KeyPress::KeyPress() : Key(irr::KEY_KEY_CODES_COUNT), - Char(L'\0') + Char(L'\0'), + m_name("") {} KeyPress::KeyPress(const char *name) { - if (name[0] == 0) { + if (strlen(name) == 0) { Key = irr::KEY_KEY_CODES_COUNT; Char = L'\0'; + m_name = ""; return; - } else if (strlen(name) > 4) { + } else if (strlen(name) <= 4) { + // Lookup by resulting character + int chars_read = mbtowc(&Char, name, 1); + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); 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); - - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); - } else - Char = L'\0'; + struct table_key k = lookup_keychar(Char); + m_name = k.Name; + Key = k.Key; return; } catch (UnknownKeycode &e) {}; } else { - // see if we can set it up as a KEY_KEY_something - m_name = "KEY_KEY_"; - m_name += name; + // Lookup by name + m_name = name; try { - Key = keyname_to_keycode(m_name.c_str()); - int chars_read = mbtowc(&Char, name, 1); - - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); + struct table_key k = lookup_keyname(name); + Key = k.Key; + Char = k.Char; return; } catch (UnknownKeycode &e) {}; } - // it's not a (known) key, just take the first char and use that - + // It's not a known key, complain and try to do something Key = irr::KEY_KEY_CODES_COUNT; - - int mbtowc_ret = mbtowc(&Char, name, 1); - FATAL_ERROR_IF(mbtowc_ret != 1, "Unexpected multibyte character"); - m_name = name[0]; + int chars_read = mbtowc(&Char, name, 1); + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); + m_name = ""; + warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char."; } KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) { - Key = in.Key; + if (prefer_character) + Key = irr::KEY_KEY_CODES_COUNT; + else + Key = in.Key; Char = in.Char; - if(prefer_character){ - m_name.resize(MB_CUR_MAX+1, '\0'); - int written = wctomb(&m_name[0], Char); - if(written > 0){ - infostream<<"KeyPress: Preferring character for "<<m_name<<std::endl; - Key = irr::KEY_KEY_CODES_COUNT; - return; - } - } - - if (valid_kcode(Key)) { - m_name = KeyNames[Key]; - } else { - m_name.resize(MB_CUR_MAX+1, '\0'); - int written = wctomb(&m_name[0], Char); - if(written < 0){ - std::string hexstr = hex_encode((const char*)&Char, sizeof(Char)); - errorstream<<"KeyPress: Unexpected multibyte character "<<hexstr<<std::endl; - } - } + try { + if (valid_kcode(Key)) + m_name = lookup_keykey(Key).Name; + else + m_name = lookup_keychar(Char).Name; + } catch (UnknownKeycode &e) { + m_name = ""; + }; } const char *KeyPress::sym() const { - if (Key && Key < irr::KEY_KEY_CODES_COUNT) - return KeyNames[Key]; - else { - return m_name.c_str(); - } + return m_name.c_str(); } const char *KeyPress::name() const { - if (Key && Key < irr::KEY_KEY_CODES_COUNT) - return KeyNamesLang[Key]; - else { - return m_name.c_str(); - } + if (m_name == "") + return ""; + const char *ret; + if (valid_kcode(Key)) + ret = lookup_keykey(Key).LangName; + else + ret = lookup_keychar(Char).LangName; + return ret ? ret : "<Unnamed key>"; } const KeyPress EscapeKey("KEY_ESCAPE"); const KeyPress CancelKey("KEY_CANCEL"); const KeyPress NumberKey[] = { - KeyPress("KEY_KEY_0"), KeyPress("KEY_KEY_1"), KeyPress("KEY_KEY_2"), - KeyPress("KEY_KEY_3"), KeyPress("KEY_KEY_4"), KeyPress("KEY_KEY_5"), - KeyPress("KEY_KEY_6"), KeyPress("KEY_KEY_7"), KeyPress("KEY_KEY_8"), - KeyPress("KEY_KEY_9")}; + KeyPress("0"), KeyPress("1"), KeyPress("2"), KeyPress("3"), KeyPress("4"), + KeyPress("5"), KeyPress("6"), KeyPress("7"), KeyPress("8"), KeyPress("9") +}; /* Key config @@ -360,11 +375,18 @@ KeyPress getKeySetting(const char *settingname) n = g_key_setting_cache.find(settingname); if(n != g_key_setting_cache.end()) return n->second; - g_key_setting_cache[settingname] = g_settings->get(settingname).c_str(); - return g_key_setting_cache.find(settingname)->second; + + KeyPress k(g_settings->get(settingname).c_str()); + g_key_setting_cache[settingname] = k; + return k; } void clearKeyCache() { g_key_setting_cache.clear(); } + +irr::EKEY_CODE keyname_to_keycode(const char *name) +{ + return lookup_keyname(name).Key; +} diff --git a/src/keycode.h b/src/keycode.h index 459a85a46..4d66cf7b5 100644 --- a/src/keycode.h +++ b/src/keycode.h @@ -44,8 +44,6 @@ public: const char *sym() const; const char *name() const; - - std::string debug() const; protected: static bool valid_kcode(irr::EKEY_CODE k) { diff --git a/src/light.h b/src/light.h index f49be4518..984e6d7c2 100644 --- a/src/light.h +++ b/src/light.h @@ -27,8 +27,10 @@ with this program; if not, write to the Free Software Foundation, Inc., */ // This directly sets the range of light. -// Actually this is not the real maximum, and this is not the -// brightest. The brightest is LIGHT_SUN. +// Actually this is not the real maximum, and this is not the brightest, the +// brightest is LIGHT_SUN. +// If changed, this constant as defined in builtin/game/constants.lua must +// also be changed. #define LIGHT_MAX 14 // Light is stored as 4 bits, thus 15 is the maximum. // This brightness is reserved for sunlight diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 507f31980..4d0ca0600 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -26,27 +26,46 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "environment.h" #include "map.h" -#include "util/numeric.h" +#include "client.h" /* LocalPlayer */ -LocalPlayer::LocalPlayer(IGameDef *gamedef, const char *name): - Player(gamedef, name), +LocalPlayer::LocalPlayer(Client *gamedef, const char *name): + Player(name, gamedef->idef()), parent(0), + hp(PLAYER_MAX_HP), + got_teleported(false), isAttached(false), + touching_ground(false), + in_liquid(false), + in_liquid_stable(false), + liquid_viscosity(0), + is_climbing(false), + swimming_vertical(false), + // Movement overrides are multipliers and must be 1 by default + physics_override_speed(1.0f), + physics_override_jump(1.0f), + physics_override_gravity(1.0f), + physics_override_sneak(true), + physics_override_sneak_glitch(true), overridePosition(v3f(0,0,0)), last_position(v3f(0,0,0)), last_speed(v3f(0,0,0)), last_pitch(0), last_yaw(0), last_keyPressed(0), + last_camera_fov(0), + last_wanted_range(0), camera_impact(0.f), last_animation(NO_ANIM), hotbar_image(""), hotbar_selected_image(""), light_color(255,255,255,255), + hurt_tilt_timer(0.0f), + hurt_tilt_strength(0.0f), + m_position(0,0,0), m_sneak_node(32767,32767,32767), m_sneak_node_exists(false), m_need_to_get_new_sneak_node(true), @@ -54,7 +73,13 @@ LocalPlayer::LocalPlayer(IGameDef *gamedef, const char *name): m_old_node_below(32767,32767,32767), m_old_node_below_type("air"), m_can_jump(false), - m_cao(NULL) + m_breath(PLAYER_MAX_BREATH), + m_yaw(0), + m_pitch(0), + camera_barely_in_ceiling(false), + m_collisionbox(-BS * 0.30, 0.0, -BS * 0.30, BS * 0.30, BS * 1.75, BS * 0.30), + m_cao(NULL), + m_gamedef(gamedef) { // Initialize hp to 0, so that no hearts will be shown if server // doesn't support health points @@ -528,18 +553,23 @@ void LocalPlayer::applyControl(float dtime) speedH += move_direction; } } - if(control.down) - { + if (control.down) { speedH -= move_direction; } - if(control.left) - { + if (!control.up && !control.down) { + speedH -= move_direction * + (control.forw_move_joystick_axis / 32767.f); + } + if (control.left) { speedH += move_direction.crossProduct(v3f(0,1,0)); } - if(control.right) - { + if (control.right) { speedH += move_direction.crossProduct(v3f(0,-1,0)); } + if (!control.left && !control.right) { + speedH -= move_direction.crossProduct(v3f(0,1,0)) * + (control.sidew_move_joystick_axis / 32767.f); + } if(control.jump) { if (free_move) { diff --git a/src/localplayer.h b/src/localplayer.h index 3ae0c4e51..7a1cb7466 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -21,28 +21,43 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LOCALPLAYER_HEADER #include "player.h" +#include "environment.h" #include <list> +class Client; class Environment; class GenericCAO; class ClientActiveObject; +class IGameDef; enum LocalPlayerAnimations {NO_ANIM, WALK_ANIM, DIG_ANIM, WD_ANIM}; // no local animation, walking, digging, both class LocalPlayer : public Player { public: - LocalPlayer(IGameDef *gamedef, const char *name); + LocalPlayer(Client *gamedef, const char *name); virtual ~LocalPlayer(); - bool isLocal() const - { - return true; - } - ClientActiveObject *parent; + u16 hp; + bool got_teleported; bool isAttached; + bool touching_ground; + // This oscillates so that the player jumps a bit above the surface + bool in_liquid; + // This is more stable and defines the maximum speed of the player + bool in_liquid_stable; + // Gets the viscosity of water to calculate friction + u8 liquid_viscosity; + bool is_climbing; + bool swimming_vertical; + + float physics_override_speed; + float physics_override_jump; + float physics_override_gravity; + bool physics_override_sneak; + bool physics_override_sneak_glitch; v3f overridePosition; @@ -60,6 +75,8 @@ public: float last_pitch; float last_yaw; unsigned int last_keyPressed; + u8 last_camera_fov; + u8 last_wanted_range; float camera_impact; @@ -71,6 +88,9 @@ public: video::SColor light_color; + float hurt_tilt_timer; + float hurt_tilt_strength; + GenericCAO* getCAO() const { return m_cao; } @@ -80,10 +100,47 @@ public: m_cao = toset; } + u32 maxHudId() const { return hud.size(); } + + u16 getBreath() const { return m_breath; } + void setBreath(u16 breath) { m_breath = breath; } + + v3s16 getLightPosition() const + { + return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); + } + + void setYaw(f32 yaw) + { + m_yaw = yaw; + } + + f32 getYaw() const { return m_yaw; } + + void setPitch(f32 pitch) + { + m_pitch = pitch; + } + + f32 getPitch() const { return m_pitch; } + + void setPosition(const v3f &position) + { + m_position = position; + } + + v3f getPosition() const { return m_position; } + v3f getEyePosition() const { return m_position + getEyeOffset(); } + v3f getEyeOffset() const + { + float eye_height = camera_barely_in_ceiling ? 1.5f : 1.625f; + return v3f(0, BS * eye_height, 0); + } private: void accelerateHorizontal(const v3f &target_speed, const f32 max_increase); void accelerateVertical(const v3f &target_speed, const f32 max_increase); + v3f m_position; // This is used for determining the sneaking range v3s16 m_sneak_node; // Whether the player is allowed to sneak @@ -98,8 +155,14 @@ private: v3s16 m_old_node_below; std::string m_old_node_below_type; bool m_can_jump; + u16 m_breath; + f32 m_yaw; + f32 m_pitch; + bool camera_barely_in_ceiling; + aabb3f m_collisionbox; GenericCAO* m_cao; + Client *m_gamedef; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 1b95a9f1c..a54454653 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,15 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef _MSC_VER - #ifndef SERVER // Dedicated server isn't linked with Irrlicht - #pragma comment(lib, "Irrlicht.lib") - // This would get rid of the console window - //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") - #endif - #pragma comment(lib, "zlibwapi.lib") - #pragma comment(lib, "Shell32.lib") -#endif +// This would get rid of the console window +//#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") #include "irrlicht.h" // createDevice @@ -948,7 +941,8 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_ 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); + std::string data; + old_db->loadBlock(*it, &data); if (!data.empty()) { new_db->saveBlock(*it, data); } else { diff --git a/src/map.cpp b/src/map.cpp index 66fabaf87..7bb8c4a13 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock.h" #include "filesys.h" #include "voxel.h" +#include "voxelalgorithms.h" #include "porting.h" #include "serialization.h" #include "nodemetadata.h" @@ -34,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/mathconstants.h" #include "rollback_interface.h" #include "environment.h" +#include "reflowscan.h" #include "emerge.h" #include "mapgen_v6.h" #include "mg_biome.h" @@ -50,6 +52,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #if USE_REDIS #include "database-redis.h" #endif +#if USE_POSTGRESQL +#include "database-postgresql.h" +#endif #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -170,7 +175,7 @@ bool Map::isNodeUnderground(v3s16 p) bool Map::isValidPosition(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreate(blockpos); + MapBlock *block = getBlockNoCreateNoEx(blockpos); return (block != NULL); } @@ -231,7 +236,6 @@ void Map::setNode(v3s16 p, MapNode & n) block->setNodeNoCheck(relpos, n); } - /* Goes recursively through the neighbours of the node. @@ -411,20 +415,6 @@ void Map::unspreadLight(enum LightBank bank, } /* - A single-node wrapper of the above -*/ -void Map::unLightNeighbors(enum LightBank bank, - v3s16 pos, u8 lightwas, - std::set<v3s16> & light_sources, - std::map<v3s16, MapBlock*> & modified_blocks) -{ - std::map<v3s16, u8> from_nodes; - from_nodes[pos] = lightwas; - - unspreadLight(bank, from_nodes, light_sources, modified_blocks); -} - -/* Lights neighbors of from_nodes, collects all them and then goes on recursively. */ @@ -565,108 +555,6 @@ void Map::spreadLight(enum LightBank bank, spreadLight(bank, lighted_nodes, modified_blocks); } -/* - A single-node source variation of the above. -*/ -void Map::lightNeighbors(enum LightBank bank, - v3s16 pos, - std::map<v3s16, MapBlock*> & modified_blocks) -{ - std::set<v3s16> from_nodes; - from_nodes.insert(pos); - spreadLight(bank, from_nodes, modified_blocks); -} - -v3s16 Map::getBrightestNeighbour(enum LightBank bank, v3s16 p) -{ - INodeDefManager *nodemgr = m_gamedef->ndef(); - - 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 - }; - - u8 brightest_light = 0; - v3s16 brightest_pos(0,0,0); - bool found_something = false; - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++){ - // Get the position of the neighbor node - v3s16 n2pos = p + dirs[i]; - MapNode n2; - bool is_valid_position; - n2 = getNodeNoEx(n2pos, &is_valid_position); - if (!is_valid_position) - continue; - - if(n2.getLight(bank, nodemgr) > brightest_light || found_something == false){ - brightest_light = n2.getLight(bank, nodemgr); - brightest_pos = n2pos; - found_something = true; - } - } - - if(found_something == false) - throw InvalidPositionException(); - - return brightest_pos; -} - -/* - Propagates sunlight down from a node. - Starting point gets sunlight. - - Returns the lowest y value of where the sunlight went. - - Mud is turned into grass in where the sunlight stops. -*/ -s16 Map::propagateSunlight(v3s16 start, - std::map<v3s16, MapBlock*> & modified_blocks) -{ - INodeDefManager *nodemgr = m_gamedef->ndef(); - - s16 y = start.Y; - for(; ; y--) - { - v3s16 pos(start.X, y, start.Z); - - v3s16 blockpos = getNodeBlockPos(pos); - MapBlock *block; - try{ - block = getBlockNoCreate(blockpos); - } - catch(InvalidPositionException &e) - { - break; - } - - v3s16 relpos = pos - blockpos*MAP_BLOCKSIZE; - bool is_valid_position; - MapNode n = block->getNode(relpos, &is_valid_position); - if (!is_valid_position) - break; - - if(nodemgr->get(n).sunlight_propagates) - { - n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr); - block->setNode(relpos, n); - - modified_blocks[blockpos] = block; - } - else - { - // Sunlight goes no further - break; - } - } - return y + 1; -} - void Map::updateLighting(enum LightBank bank, std::map<v3s16, MapBlock*> & a_blocks, std::map<v3s16, MapBlock*> & modified_blocks) @@ -919,150 +807,34 @@ void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks, } } -/* -*/ void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map<v3s16, MapBlock*> &modified_blocks, bool remove_metadata) { INodeDefManager *ndef = m_gamedef->ndef(); - /*PrintInfo(m_dout); - m_dout<<"Map::addNodeAndUpdate(): p=(" - <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/ - - /* - From this node to nodes underneath: - If lighting is sunlight (1.0), unlight neighbours and - set lighting to 0. - Else discontinue. - */ - - v3s16 toppos = p + v3s16(0,1,0); - //v3s16 bottompos = p + v3s16(0,-1,0); - - bool node_under_sunlight = true; - std::set<v3s16> light_sources; - - /* - Collect old node for rollback - */ + // Collect old node for rollback RollbackNode rollback_oldnode(this, p, m_gamedef); - /* - If there is a node at top and it doesn't have sunlight, - there has not been any sunlight going down. - - Otherwise there probably is. - */ - - bool is_valid_position; - MapNode topnode = getNodeNoEx(toppos, &is_valid_position); - - if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) - node_under_sunlight = false; - - /* - Remove all light that has come out of this node - */ + // This is needed for updating the lighting + MapNode oldnode = getNodeNoEx(p); - enum LightBank banks[] = - { - LIGHTBANK_DAY, - LIGHTBANK_NIGHT - }; - for(s32 i=0; i<2; i++) - { - enum LightBank bank = banks[i]; - - u8 lightwas = getNodeNoEx(p).getLight(bank, ndef); - - // Add the block of the added node to modified_blocks - v3s16 blockpos = getNodeBlockPos(p); - MapBlock * block = getBlockNoCreate(blockpos); - assert(block != NULL); - modified_blocks[blockpos] = block; - - assert(isValidPosition(p)); - - // Unlight neighbours of node. - // This means setting light of all consequent dimmer nodes - // to 0. - // This also collects the nodes at the border which will spread - // light again into this. - unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks); - - n.setLight(bank, 0, ndef); - } - - /* - If node lets sunlight through and is under sunlight, it has - sunlight too. - */ - if(node_under_sunlight && ndef->get(n).sunlight_propagates) - { - n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef); - } - - /* - Remove node metadata - */ + // Remove node metadata if (remove_metadata) { removeNodeMetadata(p); } - /* - Set the node on the map - */ - + // Set the node on the map + // Ignore light (because calling voxalgo::update_lighting_nodes) + n.setLight(LIGHTBANK_DAY, 0, ndef); + n.setLight(LIGHTBANK_NIGHT, 0, ndef); setNode(p, n); - /* - If node is under sunlight and doesn't let sunlight through, - take all sunlighted nodes under it and clear light from them - and from where the light has been spread. - TODO: This could be optimized by mass-unlighting instead - of looping - */ - if(node_under_sunlight && !ndef->get(n).sunlight_propagates) - { - s16 y = p.Y - 1; - for(;; y--){ - //m_dout<<"y="<<y<<std::endl; - v3s16 n2pos(p.X, y, p.Z); - - MapNode n2; - - n2 = getNodeNoEx(n2pos, &is_valid_position); - if (!is_valid_position) - break; - - if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) - { - unLightNeighbors(LIGHTBANK_DAY, - n2pos, n2.getLight(LIGHTBANK_DAY, ndef), - light_sources, modified_blocks); - n2.setLight(LIGHTBANK_DAY, 0, ndef); - setNode(n2pos, n2); - } - else - break; - } - } - - for(s32 i=0; i<2; i++) - { - enum LightBank bank = banks[i]; + // Update lighting + std::vector<std::pair<v3s16, MapNode> > oldnodes; + oldnodes.push_back(std::pair<v3s16, MapNode>(p, oldnode)); + voxalgo::update_lighting_nodes(this, ndef, oldnodes, modified_blocks); - /* - Spread light from all nodes that might be capable of doing so - */ - spreadLight(bank, light_sources, modified_blocks); - } - - /* - Update information about whether day and night light differ - */ for(std::map<v3s16, MapBlock*>::iterator i = modified_blocks.begin(); i != modified_blocks.end(); ++i) @@ -1070,9 +842,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, i->second->expireDayNightDiff(); } - /* - Report for rollback - */ + // Report for rollback if(m_gamedef->rollback()) { RollbackNode rollback_newnode(this, p, m_gamedef); @@ -1082,22 +852,23 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } /* - Add neighboring liquid nodes and the node itself if it is - liquid (=water node was added) to transform queue. - */ + Add neighboring liquid nodes and this node to transform queue. + (it's vital for the node itself to get updated last, if it was removed.) + */ v3s16 dirs[7] = { - v3s16(0,0,0), // self 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 + v3s16(0,0,0), // self }; for(u16 i=0; i<7; i++) { v3s16 p2 = p + dirs[i]; + bool is_valid_position; MapNode n2 = getNodeNoEx(p2, &is_valid_position); if(is_valid_position && (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)) @@ -1107,183 +878,10 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } } -/* -*/ void Map::removeNodeAndUpdate(v3s16 p, std::map<v3s16, MapBlock*> &modified_blocks) { - INodeDefManager *ndef = m_gamedef->ndef(); - - /*PrintInfo(m_dout); - m_dout<<"Map::removeNodeAndUpdate(): p=(" - <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/ - - bool node_under_sunlight = true; - - v3s16 toppos = p + v3s16(0,1,0); - - // Node will be replaced with this - content_t replace_material = CONTENT_AIR; - - /* - Collect old node for rollback - */ - RollbackNode rollback_oldnode(this, p, m_gamedef); - - /* - If there is a node at top and it doesn't have sunlight, - there will be no sunlight going down. - */ - bool is_valid_position; - MapNode topnode = getNodeNoEx(toppos, &is_valid_position); - - if(is_valid_position && topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) - node_under_sunlight = false; - - std::set<v3s16> light_sources; - - enum LightBank banks[] = - { - LIGHTBANK_DAY, - LIGHTBANK_NIGHT - }; - for(s32 i=0; i<2; i++) - { - enum LightBank bank = banks[i]; - - /* - Unlight neighbors (in case the node is a light source) - */ - unLightNeighbors(bank, p, - getNodeNoEx(p).getLight(bank, ndef), - light_sources, modified_blocks); - } - - /* - Remove node metadata - */ - - removeNodeMetadata(p); - - /* - Remove the node. - This also clears the lighting. - */ - - MapNode n(replace_material); - setNode(p, n); - - for(s32 i=0; i<2; i++) - { - enum LightBank bank = banks[i]; - - /* - Recalculate lighting - */ - spreadLight(bank, light_sources, modified_blocks); - } - - // Add the block of the removed node to modified_blocks - v3s16 blockpos = getNodeBlockPos(p); - MapBlock * block = getBlockNoCreate(blockpos); - assert(block != NULL); - modified_blocks[blockpos] = block; - - /* - If the removed node was under sunlight, propagate the - sunlight down from it and then light all neighbors - of the propagated blocks. - */ - if(node_under_sunlight) - { - s16 ybottom = propagateSunlight(p, modified_blocks); - /*m_dout<<"Node was under sunlight. " - "Propagating sunlight"; - m_dout<<" -> ybottom="<<ybottom<<std::endl;*/ - s16 y = p.Y; - for(; y >= ybottom; y--) - { - v3s16 p2(p.X, y, p.Z); - /*m_dout<<"lighting neighbors of node (" - <<p2.X<<","<<p2.Y<<","<<p2.Z<<")" - <<std::endl;*/ - lightNeighbors(LIGHTBANK_DAY, p2, modified_blocks); - } - } - else - { - // Set the lighting of this node to 0 - // TODO: Is this needed? Lighting is cleared up there already. - MapNode n = getNodeNoEx(p, &is_valid_position); - if (is_valid_position) { - n.setLight(LIGHTBANK_DAY, 0, ndef); - setNode(p, n); - } else { - FATAL_ERROR("Invalid position"); - } - } - - for(s32 i=0; i<2; i++) - { - enum LightBank bank = banks[i]; - - // Get the brightest neighbour node and propagate light from it - v3s16 n2p = getBrightestNeighbour(bank, p); - try{ - //MapNode n2 = getNode(n2p); - lightNeighbors(bank, n2p, modified_blocks); - } - catch(InvalidPositionException &e) - { - } - } - - /* - Update information about whether day and night light differ - */ - for(std::map<v3s16, MapBlock*>::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { - i->second->expireDayNightDiff(); - } - - /* - Report for rollback - */ - if(m_gamedef->rollback()) - { - RollbackNode rollback_newnode(this, p, m_gamedef); - RollbackAction action; - action.setSetNode(p, rollback_oldnode, rollback_newnode); - m_gamedef->rollback()->reportAction(action); - } - - /* - Add neighboring liquid nodes and this node to transform queue. - (it's vital for the node itself to get updated last.) - */ - v3s16 dirs[7] = { - 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 - v3s16(0,0,0), // self - }; - for(u16 i=0; i<7; i++) - { - v3s16 p2 = p + dirs[i]; - - bool is_position_valid; - MapNode n2 = getNodeNoEx(p2, &is_position_valid); - if (is_position_valid - && (ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)) - { - m_transforming_liquid.push_back(p2); - } - } + addNodeAndUpdate(p, MapNode(CONTENT_AIR), modified_blocks, true); } bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata) @@ -1631,8 +1229,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) // list of nodes that due to viscosity have not reached their max level height std::deque<v3s16> must_reflow; - // List of MapBlocks that will require a lighting update (due to lava) - std::map<v3s16, MapBlock *> lighting_modified_blocks; + std::vector<std::pair<v3s16, MapNode> > changed_nodes; u32 liquid_loop_max = g_settings->getS32("liquid_loop_max"); u32 loop_max = liquid_loop_max; @@ -1673,7 +1270,11 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) Collect information about current node */ s8 liquid_level = -1; + // The liquid node which will be placed there if + // the liquid flows into this node. content_t liquid_kind = CONTENT_IGNORE; + // The node which will be placed there if liquid + // can't flow into this node. content_t floodable_node = CONTENT_AIR; const ContentFeatures &cf = nodemgr->get(n0); LiquidType liquid_type = cf.liquid_type; @@ -1709,6 +1310,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) NodeNeighbor neutrals[6]; // nodes that are solid or another kind of liquid int num_neutrals = 0; bool flowing_down = false; + bool ignored_sources = false; for (u16 i = 0; i < 6; i++) { NeighborType nt = NEIGHBOR_SAME_LEVEL; switch (i) { @@ -1736,10 +1338,15 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) flowing_down = true; } else { neutrals[num_neutrals++] = nb; - // If neutral below is ignore prevent water spreading outwards - if (nb.t == NEIGHBOR_LOWER && - nb.n.getContent() == CONTENT_IGNORE) - flowing_down = true; + if (nb.n.getContent() == CONTENT_IGNORE) { + // If node below is ignore prevent water from + // spreading outwards and otherwise prevent from + // flowing away as ignore node might be the source + if (nb.t == NEIGHBOR_LOWER) + flowing_down = true; + else + ignored_sources = true; + } } break; case LIQUID_SOURCE: @@ -1792,6 +1399,11 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) new_node_content = liquid_kind; else new_node_content = floodable_node; + } else if (ignored_sources && liquid_level >= 0) { + // Maybe there are neighbouring sources that aren't loaded yet + // so prevent flowing away. + new_node_level = liquid_level; + new_node_content = liquid_kind; } else { // no surrounding sources, so get the maximum level that can flow into this node for (u16 i = 0; i < num_flows; i++) { @@ -1865,6 +1477,10 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) } n0.setContent(new_node_content); + // Ignore light (because calling voxalgo::update_lighting_nodes) + n0.setLight(LIGHTBANK_DAY, 0, nodemgr); + n0.setLight(LIGHTBANK_NIGHT, 0, nodemgr); + // Find out whether there is a suspect for this action std::string suspect; if (m_gamedef->rollback()) @@ -1891,10 +1507,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) MapBlock *block = getBlockNoCreateNoEx(blockpos); if (block != NULL) { modified_blocks[blockpos] = block; - // If new or old node emits light, MapBlock requires lighting update - if (nodemgr->get(n0).light_source != 0 || - nodemgr->get(n00).light_source != 0) - lighting_modified_blocks[block->getPos()] = block; + changed_nodes.push_back(std::pair<v3s16, MapNode>(p0, n00)); } /* @@ -1923,7 +1536,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks) for (std::deque<v3s16>::iterator iter = must_reflow.begin(); iter != must_reflow.end(); ++iter) m_transforming_liquid.push_back(*iter); - updateLighting(lighting_modified_blocks, modified_blocks); + voxalgo::update_lighting_nodes(this, nodemgr, changed_nodes, modified_blocks); /* ---------------------------------------------------------------------- @@ -2084,11 +1697,13 @@ NodeTimer Map::getNodeTimer(v3s16 p) return NodeTimer(); } NodeTimer t = block->m_node_timers.get(p_rel); - return t; + NodeTimer nt(t.timeout, t.elapsed, p); + return nt; } -void Map::setNodeTimer(v3s16 p, NodeTimer t) +void Map::setNodeTimer(const NodeTimer &t) { + v3s16 p = t.position; v3s16 blockpos = getNodeBlockPos(p); v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE; MapBlock *block = getBlockNoCreateNoEx(blockpos); @@ -2102,7 +1717,8 @@ void Map::setNodeTimer(v3s16 p, NodeTimer t) <<std::endl; return; } - block->m_node_timers.set(p_rel, t); + NodeTimer nt(t.timeout, t.elapsed, p_rel); + block->m_node_timers.set(nt); } void Map::removeNodeTimer(v3s16 p) @@ -2124,11 +1740,15 @@ void Map::removeNodeTimer(v3s16 p) */ ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emerge): Map(dout_server, gamedef), + settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"), m_emerge(emerge), m_map_metadata_changed(true) { verbosestream<<FUNCTION_NAME<<std::endl; + // Tell the EmergeManager about our MapSettingsManager + emerge->map_settings_mgr = &settings_mgr; + /* Try to load map; if not found, create a new one. */ @@ -2164,26 +1784,15 @@ ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emer } else { - try{ - // Load map metadata (seed, chunksize) - loadMapMeta(); - } - catch(SettingNotFoundException &e){ - infostream<<"ServerMap: Some metadata not found." - <<" Using default settings."<<std::endl; - } - catch(FileNotGoodException &e){ - warningstream<<"Could not load map metadata" - //<<" Disabling chunk-based generator." - <<std::endl; - //m_chunksize = 0; - } - infostream<<"ServerMap: Successfully loaded map " - <<"metadata from "<<savedir - <<", assuming valid save directory." - <<" seed="<< m_emerge->params.seed <<"." - <<std::endl; + if (settings_mgr.loadMapMeta()) { + infostream << "ServerMap: Metadata loaded from " + << savedir << std::endl; + } else { + infostream << "ServerMap: Metadata could not be loaded " + "from " << savedir << ", assuming valid save " + "directory." << std::endl; + } m_map_saving_enabled = true; // Map loaded, not creating new one @@ -2253,19 +1862,26 @@ ServerMap::~ServerMap() #endif } +MapgenParams *ServerMap::getMapgenParams() +{ + // getMapgenParams() should only ever be called after Server is initialized + assert(settings_mgr.mapgen_params != NULL); + return settings_mgr.mapgen_params; +} + u64 ServerMap::getSeed() { - return m_emerge->params.seed; + return getMapgenParams()->seed; } s16 ServerMap::getWaterLevel() { - return m_emerge->params.water_level; + return getMapgenParams()->water_level; } bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) { - s16 csize = m_emerge->params.chunksize; + s16 csize = getMapgenParams()->chunksize; v3s16 bpmin = EmergeManager::getContainingChunk(blockpos, csize); v3s16 bpmax = bpmin + v3s16(1, 1, 1) * (csize - 1); @@ -2281,7 +1897,7 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) blockpos_over_limit(full_bpmax)) return false; - data->seed = m_emerge->params.seed; + data->seed = getSeed(); data->blockpos_min = bpmin; data->blockpos_max = bpmax; data->blockpos_requested = blockpos; @@ -2899,8 +2515,9 @@ 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) { - saveMapMeta(); + if (m_map_metadata_changed || save_level == MOD_STATE_CLEAN) { + if (settings_mgr.saveMapMeta()) + m_map_metadata_changed = false; } // Profile modified reasons @@ -2999,55 +2616,6 @@ void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst) } } -void ServerMap::saveMapMeta() -{ - DSTACK(FUNCTION_NAME); - - createDirs(m_savedir); - - std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt"; - std::ostringstream oss(std::ios_base::binary); - Settings conf; - - m_emerge->params.save(conf); - conf.writeLines(oss); - - oss << "[end_of_params]\n"; - - if(!fs::safeWriteToFile(fullpath, oss.str())) { - errorstream << "ServerMap::saveMapMeta(): " - << "could not write " << fullpath << std::endl; - throw FileNotGoodException("Cannot save chunk metadata"); - } - - m_map_metadata_changed = false; -} - -void ServerMap::loadMapMeta() -{ - DSTACK(FUNCTION_NAME); - - Settings conf; - std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt"; - - 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 (!conf.parseConfigLines(is, "[end_of_params]")) { - throw SerializationError("ServerMap::loadMapMeta(): " - "[end_of_params] not found!"); - } - - m_emerge->params.load(conf); - - verbosestream << "ServerMap::loadMapMeta(): seed=" - << m_emerge->params.seed << std::endl; -} - void ServerMap::saveSectorMeta(ServerMapSector *sector) { DSTACK(FUNCTION_NAME); @@ -3240,6 +2808,10 @@ Database *ServerMap::createDatabase( else if (name == "redis") return new Database_Redis(conf); #endif + #if USE_POSTGRESQL + else if (name == "postgresql") + return new Database_PostgreSQL(conf); + #endif else throw BaseException(std::string("Database backend ") + name + " not supported."); } @@ -3334,8 +2906,11 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, block->deSerialize(is, version, true); // If it's a new block, insert it to the map - if(created_new) + if (created_new) { sector->insertBlock(block); + ReflowScan scanner(this, m_emerge->ndef); + scanner.scan(block, &m_transforming_liquid); + } /* Save blocks loaded in old format in new format @@ -3401,8 +2976,11 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool block->deSerialize(is, version, true); // If it's a new block, insert it to the map - if(created_new) + if (created_new) { sector->insertBlock(block); + ReflowScan scanner(this, m_emerge->ndef); + scanner.scan(block, &m_transforming_liquid); + } /* Save blocks loaded in old format in new format @@ -3442,8 +3020,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos) v2s16 p2d(blockpos.X, blockpos.Z); std::string ret; - - ret = dbase->loadBlock(blockpos); + dbase->loadBlock(blockpos, &ret); if (ret != "") { loadBlock(&ret, blockpos, createSector(p2d), false); return getBlockNoCreateNoEx(blockpos); @@ -32,7 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "voxel.h" #include "modifiedstate.h" #include "util/container.h" +#include "util/cpp11_container.h" #include "nodetimer.h" +#include "map_settings_manager.h" class Settings; class Database; @@ -46,8 +48,6 @@ class IRollbackManager; class EmergeManager; class ServerEnvironment; struct BlockMakeData; -struct MapgenParams; - /* MapEditEvent @@ -211,24 +211,10 @@ public: std::set<v3s16> & light_sources, std::map<v3s16, MapBlock*> & modified_blocks); - void unLightNeighbors(enum LightBank bank, - v3s16 pos, u8 lightwas, - std::set<v3s16> & light_sources, - std::map<v3s16, MapBlock*> & modified_blocks); - void spreadLight(enum LightBank bank, std::set<v3s16> & from_nodes, std::map<v3s16, MapBlock*> & modified_blocks); - - void lightNeighbors(enum LightBank bank, - v3s16 pos, - std::map<v3s16, MapBlock*> & modified_blocks); - - v3s16 getBrightestNeighbour(enum LightBank bank, v3s16 p); - - s16 propagateSunlight(v3s16 start, - std::map<v3s16, MapBlock*> & modified_blocks); - + void updateLighting(enum LightBank bank, std::map<v3s16, MapBlock*> & a_blocks, std::map<v3s16, MapBlock*> & modified_blocks); @@ -327,7 +313,7 @@ public: */ NodeTimer getNodeTimer(v3s16 p); - void setNodeTimer(v3s16 p, NodeTimer t); + void setNodeTimer(const NodeTimer &t); void removeNodeTimer(v3s16 p); /* @@ -463,9 +449,8 @@ public: void save(ModifiedState save_level); void listAllLoadableBlocks(std::vector<v3s16> &dst); void listAllLoadedBlocks(std::vector<v3s16> &dst); - // Saves map seed and possibly other stuff - void saveMapMeta(); - void loadMapMeta(); + + MapgenParams *getMapgenParams(); /*void saveChunkMeta(); void loadChunkMeta();*/ @@ -506,6 +491,8 @@ public: u64 getSeed(); s16 getWaterLevel(); + MapSettingsManager settings_mgr; + private: // Emerge manager EmergeManager *m_emerge; diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp new file mode 100644 index 000000000..53d17125c --- /dev/null +++ b/src/map_settings_manager.cpp @@ -0,0 +1,194 @@ +/* +Minetest +Copyright (C) 2010-2013 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 "debug.h" +#include "filesys.h" +#include "log.h" +#include "mapgen.h" +#include "settings.h" + +#include "map_settings_manager.h" + +MapSettingsManager::MapSettingsManager( + Settings *user_settings, const std::string &map_meta_path) +{ + m_map_meta_path = map_meta_path; + m_user_settings = user_settings; + m_map_settings = new Settings; + mapgen_params = NULL; + + assert(m_user_settings != NULL); +} + + +MapSettingsManager::~MapSettingsManager() +{ + delete m_map_settings; + delete mapgen_params; +} + + +bool MapSettingsManager::getMapSetting( + const std::string &name, std::string *value_out) +{ + if (m_map_settings->getNoEx(name, *value_out)) + return true; + + // Compatibility kludge + if (m_user_settings == g_settings && name == "seed") + return m_user_settings->getNoEx("fixed_map_seed", *value_out); + + return m_user_settings->getNoEx(name, *value_out); +} + + +bool MapSettingsManager::getMapSettingNoiseParams( + const std::string &name, NoiseParams *value_out) +{ + return m_map_settings->getNoiseParams(name, *value_out) || + m_user_settings->getNoiseParams(name, *value_out); +} + + +bool MapSettingsManager::setMapSetting( + const std::string &name, const std::string &value, bool override_meta) +{ + if (mapgen_params) + return false; + + if (override_meta) + m_map_settings->set(name, value); + else + m_map_settings->setDefault(name, value); + + return true; +} + + +bool MapSettingsManager::setMapSettingNoiseParams( + const std::string &name, const NoiseParams *value, bool override_meta) +{ + if (mapgen_params) + return false; + + m_map_settings->setNoiseParams(name, *value, !override_meta); + return true; +} + + +bool MapSettingsManager::loadMapMeta() +{ + std::ifstream is(m_map_meta_path.c_str(), std::ios_base::binary); + + if (!is.good()) { + errorstream << "loadMapMeta: could not open " + << m_map_meta_path << std::endl; + return false; + } + + if (!m_map_settings->parseConfigLines(is, "[end_of_params]")) { + errorstream << "loadMapMeta: [end_of_params] not found!" << std::endl; + return false; + } + + return true; +} + + +bool MapSettingsManager::saveMapMeta() +{ + // If mapgen params haven't been created yet; abort + if (!mapgen_params) + return false; + + if (!fs::CreateAllDirs(fs::RemoveLastPathComponent(m_map_meta_path))) { + errorstream << "saveMapMeta: could not create dirs to " + << m_map_meta_path; + return false; + } + + std::ostringstream oss(std::ios_base::binary); + Settings conf; + + mapgen_params->MapgenParams::writeParams(&conf); + mapgen_params->writeParams(&conf); + conf.writeLines(oss); + + // NOTE: If there are ever types of map settings other than + // those relating to map generation, save them here + + oss << "[end_of_params]\n"; + + if (!fs::safeWriteToFile(m_map_meta_path, oss.str())) { + errorstream << "saveMapMeta: could not write " + << m_map_meta_path << std::endl; + return false; + } + + return true; +} + + +MapgenParams *MapSettingsManager::makeMapgenParams() +{ + if (mapgen_params) + return mapgen_params; + + assert(m_user_settings != NULL); + assert(m_map_settings != NULL); + + // At this point, we have (in order of precedence): + // 1). m_mapgen_settings->m_settings containing map_meta.txt settings or + // explicit overrides from scripts + // 2). m_mapgen_settings->m_defaults containing script-set mgparams without + // overrides + // 3). g_settings->m_settings containing all user-specified config file + // settings + // 4). g_settings->m_defaults containing any low-priority settings from + // scripts, e.g. mods using Lua as an enhanced config file) + + // Now, get the mapgen type so we can create the appropriate MapgenParams + std::string mg_name; + MapgenType mgtype = getMapSetting("mg_name", &mg_name) ? + Mapgen::getMapgenType(mg_name) : MAPGEN_DEFAULT; + if (mgtype == MAPGEN_INVALID) { + errorstream << "EmergeManager: mapgen '" << mg_name << + "' not valid; falling back to " << + Mapgen::getMapgenName(MAPGEN_DEFAULT) << std::endl; + mgtype = MAPGEN_DEFAULT; + } + + // Create our MapgenParams + MapgenParams *params = Mapgen::createMapgenParams(mgtype); + if (params == NULL) + return NULL; + + params->mgtype = mgtype; + + // Load the rest of the mapgen params from our active settings + params->MapgenParams::readParams(m_user_settings); + params->MapgenParams::readParams(m_map_settings); + params->readParams(m_user_settings); + params->readParams(m_map_settings); + + // Hold onto our params + mapgen_params = params; + + return params; +} diff --git a/src/map_settings_manager.h b/src/map_settings_manager.h new file mode 100644 index 000000000..9f766f1f0 --- /dev/null +++ b/src/map_settings_manager.h @@ -0,0 +1,79 @@ +/* +Minetest +Copyright (C) 2010-2013 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 MAP_SETTINGS_MANAGER_HEADER +#define MAP_SETTINGS_MANAGER_HEADER + +#include <string> + +class Settings; +struct NoiseParams; +struct MapgenParams; + +/* + MapSettingsManager is a centralized object for management (creating, + loading, storing, saving, etc.) of config settings related to the Map. + + It has two phases: the initial r/w "gather and modify settings" state, and + the final r/o "read and save settings" state. + + The typical use case is, in order, as follows: + - Create a MapSettingsManager object + - Try to load map metadata into it from the metadata file + - Manually view and modify the current configuration as desired through a + Settings-like interface + - When all modifications are finished, create a 'Parameters' object + containing the finalized, active parameters. This could be passed along + to whichever Map-related objects that may require it. + - Save these active settings to the metadata file when requested +*/ +class MapSettingsManager { +public: + // Finalized map generation parameters + MapgenParams *mapgen_params; + + MapSettingsManager(Settings *user_settings, + const std::string &map_meta_path); + ~MapSettingsManager(); + + bool getMapSetting(const std::string &name, std::string *value_out); + + bool getMapSettingNoiseParams( + const std::string &name, NoiseParams *value_out); + + // Note: Map config becomes read-only after makeMapgenParams() gets called + // (i.e. mapgen_params is non-NULL). Attempts to set map config after + // params have been finalized will result in failure. + bool setMapSetting(const std::string &name, + const std::string &value, bool override_meta = false); + + bool setMapSettingNoiseParams(const std::string &name, + const NoiseParams *value, bool override_meta = false); + + bool loadMapMeta(); + bool saveMapMeta(); + MapgenParams *makeMapgenParams(); + +private: + std::string m_map_meta_path; + Settings *m_map_settings; + Settings *m_user_settings; +}; + +#endif diff --git a/src/mapblock.h b/src/mapblock.h index 73c17ee60..5adfcf3fb 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -488,9 +488,9 @@ public: m_node_timers.remove(p); } - inline void setNodeTimer(v3s16 p, NodeTimer t) + inline void setNodeTimer(const NodeTimer &t) { - m_node_timers.set(p,t); + m_node_timers.set(t); } inline void clearNodeTimers() diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index e1b044271..00f83e7ab 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -839,7 +839,7 @@ static void updateFastFaceRow( { v3s16 p = startpos; - u16 continuous_tiles_count = 0; + u16 continuous_tiles_count = 1; bool makes_face = false; v3s16 p_corrected; @@ -889,8 +889,8 @@ static void updateFastFaceRow( && (tile.material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) { next_is_different = false; - } - else{ + continuous_tiles_count++; + } else { /*if(makes_face){ g_profiler->add("Meshgen: diff: next_makes_face != makes_face", next_makes_face != makes_face ? 1 : 0); @@ -915,8 +915,6 @@ static void updateFastFaceRow( g_profiler->add("Meshgen: diff: last position", 1);*/ } - continuous_tiles_count++; - if(next_is_different) { /* @@ -928,8 +926,6 @@ static void updateFastFaceRow( v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z); // Center point of face (kind of) v3f sp = pf - ((f32)continuous_tiles_count / 2.0 - 0.5) * translate_dir_f; - if(continuous_tiles_count != 1) - sp += translate_dir_f; v3f scale(1,1,1); if(translate_dir.X != 0) { @@ -952,19 +948,18 @@ static void updateFastFaceRow( } } - continuous_tiles_count = 0; - - makes_face = next_makes_face; - p_corrected = next_p_corrected; - face_dir_corrected = next_face_dir_corrected; - lights[0] = next_lights[0]; - lights[1] = next_lights[1]; - lights[2] = next_lights[2]; - lights[3] = next_lights[3]; - tile = next_tile; - light_source = next_light_source; + continuous_tiles_count = 1; } + makes_face = next_makes_face; + p_corrected = next_p_corrected; + face_dir_corrected = next_face_dir_corrected; + lights[0] = next_lights[0]; + lights[1] = next_lights[1]; + lights[2] = next_lights[2]; + lights[3] = next_lights[3]; + tile = next_tile; + light_source = next_light_source; p = p_next; } } @@ -1038,7 +1033,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_enable_shaders = data->m_use_shaders; m_use_tangent_vertices = data->m_use_tangent_vertices; m_enable_vbo = g_settings->getBool("enable_vbo"); - + if (g_settings->getBool("enable_minimap")) { m_minimap_mapblock = new MinimapMapblock; m_minimap_mapblock->getMinimapNodes( @@ -1303,10 +1298,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat // Cracks if(crack != m_last_crack) { - for(std::map<u32, std::string>::iterator - i = m_crack_materials.begin(); - i != m_crack_materials.end(); ++i) - { + for (UNORDERED_MAP<u32, std::string>::iterator i = m_crack_materials.begin(); + i != m_crack_materials.end(); ++i) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); std::string basename = i->second; @@ -1320,9 +1313,9 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat // If the current material is also animated, // update animation info - std::map<u32, TileSpec>::iterator anim_iter = - m_animation_tiles.find(i->first); - if(anim_iter != m_animation_tiles.end()){ + UNORDERED_MAP<u32, TileSpec>::iterator anim_iter = + m_animation_tiles.find(i->first); + if (anim_iter != m_animation_tiles.end()){ TileSpec &tile = anim_iter->second; tile.texture = new_texture; tile.texture_id = new_texture_id; @@ -1335,10 +1328,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat } // Texture animation - for(std::map<u32, TileSpec>::iterator - i = m_animation_tiles.begin(); - i != m_animation_tiles.end(); ++i) - { + for (UNORDERED_MAP<u32, TileSpec>::iterator i = m_animation_tiles.begin(); + i != m_animation_tiles.end(); ++i) { const TileSpec &tile = i->second; // Figure out current frame int frameoffset = m_animation_frame_offsets[i->first]; @@ -1448,7 +1439,7 @@ void MeshCollector::append(const TileSpec &tile, vertices[i].Color, vertices[i].TCoords); p->vertices.push_back(vert); } - } + } for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; @@ -1504,7 +1495,7 @@ void MeshCollector::append(const TileSpec &tile, vertices[i].Normal, c, vertices[i].TCoords); p->vertices.push_back(vert); } - } + } for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index f89fbe669..8376468da 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "client/tile.h" #include "voxel.h" +#include "util/cpp11_container.h" #include <map> class IGameDef; @@ -121,7 +122,7 @@ public: if(m_animation_force_timer > 0) m_animation_force_timer--; } - + void updateCameraOffset(v3s16 camera_offset); private: @@ -144,20 +145,20 @@ private: // Last crack value passed to animate() int m_last_crack; // Maps mesh buffer (i.e. material) indices to base texture names - std::map<u32, std::string> m_crack_materials; + UNORDERED_MAP<u32, std::string> m_crack_materials; // Animation info: texture animationi // Maps meshbuffers to TileSpecs - std::map<u32, TileSpec> m_animation_tiles; - std::map<u32, int> m_animation_frames; // last animation frame - std::map<u32, int> m_animation_frame_offsets; - + UNORDERED_MAP<u32, TileSpec> m_animation_tiles; + UNORDERED_MAP<u32, int> m_animation_frames; // last animation frame + UNORDERED_MAP<u32, int> m_animation_frame_offsets; + // Animation info: day/night transitions // Last daynight_ratio value passed to animate() u32 m_last_daynight_ratio; // For each meshbuffer, maps vertex indices to (day,night) pairs std::map<u32, std::map<u32, std::pair<u8, u8> > > m_daynight_diffs; - + // Camera offset info -> do we have to translate the mesh? v3s16 m_camera_offset; }; diff --git a/src/mapgen.cpp b/src/mapgen.cpp index b3c9380a0..fd4f5858f 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -39,12 +39,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "filesys.h" #include "log.h" +#include "mapgen_flat.h" +#include "mapgen_fractal.h" +#include "mapgen_v5.h" +#include "mapgen_v6.h" +#include "mapgen_v7.h" +#include "mapgen_valleys.h" +#include "mapgen_singlenode.h" +#include "cavegen.h" +#include "dungeongen.h" FlagDesc flagdesc_mapgen[] = { - {"trees", MG_TREES}, {"caves", MG_CAVES}, {"dungeons", MG_DUNGEONS}, - {"flat", MG_FLAT}, {"light", MG_LIGHT}, {"decorations", MG_DECORATIONS}, {NULL, 0} @@ -61,6 +68,28 @@ FlagDesc flagdesc_gennotify[] = { {NULL, 0} }; +struct MapgenDesc { + const char *name; + bool is_user_visible; +}; + +//// +//// Built-in mapgens +//// + +static MapgenDesc g_reg_mapgens[] = { + {"v5", true}, + {"v6", true}, + {"v7", true}, + {"flat", true}, + {"fractal", true}, + {"valleys", true}, + {"singlenode", false}, +}; + +STATIC_ASSERT( + ARRLEN(g_reg_mapgens) == MAPGEN_INVALID, + registered_mapgens_is_wrong_size); //// //// Mapgen @@ -76,10 +105,9 @@ Mapgen::Mapgen() vm = NULL; ndef = NULL; - heightmap = NULL; + biomegen = NULL; biomemap = NULL; - heatmap = NULL; - humidmap = NULL; + heightmap = NULL; } @@ -88,17 +116,30 @@ Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) : { 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); + /* + We are losing half our entropy by doing this, but it is necessary to + preserve reverse compatibility. If the top half of our current 64 bit + seeds ever starts getting used, existing worlds will break due to a + different hash outcome and no way to differentiate between versions. + + A solution could be to add a new bit to designate that the top half of + the seed value should be used, essentially a 1-bit version code, but + this would require increasing the total size of a seed to 9 bytes (yuck) + + It's probably okay if this never gets fixed. 4.2 billion possibilities + ought to be enough for anyone. + */ + seed = (s32)params->seed; + vm = NULL; - ndef = NULL; - heightmap = NULL; + ndef = emerge->ndef; + biomegen = NULL; biomemap = NULL; - heatmap = NULL; - humidmap = NULL; + heightmap = NULL; } @@ -107,7 +148,84 @@ Mapgen::~Mapgen() } -u32 Mapgen::getBlockSeed(v3s16 p, int seed) +MapgenType Mapgen::getMapgenType(const std::string &mgname) +{ + for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) { + if (mgname == g_reg_mapgens[i].name) + return (MapgenType)i; + } + + return MAPGEN_INVALID; +} + + +const char *Mapgen::getMapgenName(MapgenType mgtype) +{ + size_t index = (size_t)mgtype; + if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens)) + return "invalid"; + + return g_reg_mapgens[index].name; +} + + +Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid, + MapgenParams *params, EmergeManager *emerge) +{ + switch (mgtype) { + case MAPGEN_FLAT: + return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge); + case MAPGEN_FRACTAL: + return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge); + case MAPGEN_SINGLENODE: + return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge); + case MAPGEN_V5: + return new MapgenV5(mgid, (MapgenV5Params *)params, emerge); + case MAPGEN_V6: + return new MapgenV6(mgid, (MapgenV6Params *)params, emerge); + case MAPGEN_V7: + return new MapgenV7(mgid, (MapgenV7Params *)params, emerge); + case MAPGEN_VALLEYS: + return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge); + default: + return NULL; + } +} + + +MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype) +{ + switch (mgtype) { + case MAPGEN_FLAT: + return new MapgenFlatParams; + case MAPGEN_FRACTAL: + return new MapgenFractalParams; + case MAPGEN_SINGLENODE: + return new MapgenSinglenodeParams; + case MAPGEN_V5: + return new MapgenV5Params; + case MAPGEN_V6: + return new MapgenV6Params; + case MAPGEN_V7: + return new MapgenV7Params; + case MAPGEN_VALLEYS: + return new MapgenValleysParams; + default: + return NULL; + } +} + + +void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden) +{ + for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) { + if (include_hidden || g_reg_mapgens[i].is_user_visible) + mgnames->push_back(g_reg_mapgens[i].name); + } +} + + +u32 Mapgen::getBlockSeed(v3s16 p, s32 seed) { return (u32)seed + p.Z * 38134234 + @@ -116,7 +234,7 @@ u32 Mapgen::getBlockSeed(v3s16 p, int seed) } -u32 Mapgen::getBlockSeed2(v3s16 p, int seed) +u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) { u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed; n = (n >> 13) ^ n; @@ -199,27 +317,86 @@ void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) //printf("updateHeightmap: %dus\n", t.stop()); } +inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em) +{ + u32 vi_neg_x = vi; + vm->m_area.add_x(em, vi_neg_x, -1); + if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]); + if (c_nx.floodable && !c_nx.isLiquid()) + return true; + } + u32 vi_pos_x = vi; + vm->m_area.add_x(em, vi_pos_x, +1); + if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]); + if (c_px.floodable && !c_px.isLiquid()) + return true; + } + u32 vi_neg_z = vi; + vm->m_area.add_z(em, vi_neg_z, -1); + if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]); + if (c_nz.floodable && !c_nz.isLiquid()) + return true; + } + u32 vi_pos_z = vi; + vm->m_area.add_z(em, vi_pos_z, +1); + if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]); + if (c_pz.floodable && !c_pz.isLiquid()) + return true; + } + return false; +} void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax) { - bool isliquid, wasliquid; + bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed; v3s16 em = vm->m_area.getExtent(); - for (s16 z = nmin.Z; z <= nmax.Z; z++) { - for (s16 x = nmin.X; x <= nmax.X; x++) { - wasliquid = true; - - u32 i = vm->m_area.index(x, nmax.Y, z); - for (s16 y = nmax.Y; y >= nmin.Y; y--) { - isliquid = ndef->get(vm->m_data[i]).isLiquid(); - - // there was a change between liquid and nonliquid, add to queue. - if (isliquid != wasliquid) + for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++) + for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) { + wasignored = true; + wasliquid = false; + waschecked = false; + waspushed = false; + + u32 vi = vm->m_area.index(x, nmax.Y, z); + for (s16 y = nmax.Y; y >= nmin.Y; y--) { + isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE; + isliquid = ndef->get(vm->m_data[vi]).isLiquid(); + + if (isignored || wasignored || isliquid == wasliquid) { + // Neither topmost node of liquid column nor topmost node below column + waschecked = false; + waspushed = false; + } else if (isliquid) { + // This is the topmost node in the column + bool ispushed = false; + if (isLiquidHorizontallyFlowable(vi, em)) { trans_liquid->push_back(v3s16(x, y, z)); - - wasliquid = isliquid; - vm->m_area.add_y(em, i, -1); + ispushed = true; + } + // Remember waschecked and waspushed to avoid repeated + // checks/pushes in case the column consists of only this node + waschecked = true; + waspushed = ispushed; + } else { + // This is the topmost node below a liquid column + u32 vi_above = vi; + vm->m_area.add_y(em, vi_above, 1); + if (!waspushed && (ndef->get(vm->m_data[vi]).floodable || + (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) { + // Push back the lowest node in the column which is one + // node above this one + trans_liquid->push_back(v3s16(x, y + 1, z)); + } } + + wasliquid = isliquid; + wasignored = isignored; + vm->m_area.add_y(em, vi, -1); } } } @@ -372,6 +549,349 @@ void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax) //// +//// MapgenBasic +//// + +MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge) + : Mapgen(mapgenid, params, emerge) +{ + this->m_emerge = emerge; + this->m_bmgr = emerge->biomemgr; + + //// Here, 'stride' refers to the number of elements needed to skip to index + //// an adjacent element for that coordinate in noise/height/biome maps + //// (*not* vmanip content map!) + + // Note there is no X stride explicitly defined. Items adjacent in the X + // coordinate are assumed to be adjacent in memory as well (i.e. stride of 1). + + // Number of elements to skip to get to the next Y coordinate + this->ystride = csize.X; + + // Number of elements to skip to get to the next Z coordinate + this->zstride = csize.X * csize.Y; + + // Z-stride value for maps oversized for 1-down overgeneration + this->zstride_1d = csize.X * (csize.Y + 1); + + // Z-stride value for maps oversized for 1-up 1-down overgeneration + this->zstride_1u1d = csize.X * (csize.Y + 2); + + //// Allocate heightmap + this->heightmap = new s16[csize.X * csize.Z]; + + //// Initialize biome generator + // TODO(hmmmm): should we have a way to disable biomemanager biomes? + biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize); + biomemap = biomegen->biomemap; + + //// Look up some commonly used content + c_stone = ndef->getId("mapgen_stone"); + c_water_source = ndef->getId("mapgen_water_source"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_sandstone = ndef->getId("mapgen_sandstone"); + c_river_water_source = ndef->getId("mapgen_river_water_source"); + + // Fall back to more basic content if not defined + if (c_desert_stone == CONTENT_IGNORE) + c_desert_stone = c_stone; + if (c_sandstone == CONTENT_IGNORE) + c_sandstone = c_stone; + if (c_river_water_source == CONTENT_IGNORE) + c_river_water_source = c_water_source; + + //// Content used for dungeon generation + 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"); + + // Fall back to more basic content if not defined + if (c_mossycobble == CONTENT_IGNORE) + c_mossycobble = c_cobble; + if (c_stair_cobble == CONTENT_IGNORE) + c_stair_cobble = c_cobble; + if (c_sandstonebrick == CONTENT_IGNORE) + c_sandstonebrick = c_sandstone; + if (c_stair_sandstonebrick == CONTENT_IGNORE) + c_stair_sandstonebrick = c_sandstone; +} + + +MapgenBasic::~MapgenBasic() +{ + delete biomegen; + delete []heightmap; +} + + +MgStoneType MapgenBasic::generateBiomes() +{ + // can't generate biomes without a biome generator! + assert(biomegen); + assert(biomemap); + + v3s16 em = vm->m_area.getExtent(); + u32 index = 0; + MgStoneType stone_type = MGSTONE_STONE; + + noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); + + 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 = NULL; + u16 depth_top = 0; + u16 base_filler = 0; + u16 depth_water_top = 0; + u16 depth_riverbed = 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 river_water_above = c_above == c_river_water_source; + bool water_above = c_above == c_water_source || river_water_above; + + biomemap[index] = BIOME_NONE; + + // 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_MAX; + + for (s16 y = node_max.Y; y >= node_min.Y; y--) { + 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. + bool is_stone_surface = (c == c_stone) && + (air_above || water_above || !biome); + + bool is_water_surface = + (c == c_water_source || c == c_river_water_source) && + (air_above || !biome); + + if (is_stone_surface || is_water_surface) { + biome = biomegen->getBiomeAtIndex(index, y); + + if (biomemap[index] == BIOME_NONE && is_stone_surface) + biomemap[index] = biome->index; + + depth_top = biome->depth_top; + base_filler = MYMAX(depth_top + + biome->depth_filler + + noise_filler_depth->result[index], 0.f); + depth_water_top = biome->depth_water_top; + depth_riverbed = biome->depth_riverbed; + + // 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 = MGSTONE_DESERT_STONE; + else if (biome->c_stone == c_sandstone) + stone_type = MGSTONE_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 + || c_below == c_river_water_source) + nplaced = U16_MAX; + + if (river_water_above) { + if (nplaced < depth_riverbed) { + vm->m_data[vi] = MapNode(biome->c_riverbed); + nplaced++; + } else { + nplaced = U16_MAX; // Disable top/filler placement + river_water_above = false; + } + } else 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); + } + + air_above = false; + water_above = false; + } else if (c == c_water_source) { + 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 == c_river_water_source) { + vm->m_data[vi] = MapNode(biome->c_river_water); + nplaced = 0; // Enable riverbed placement for next surface + air_above = false; + water_above = true; + river_water_above = true; + } else if (c == CONTENT_AIR) { + 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_MAX; // Disable top/filler placement + air_above = false; + water_above = false; + } + + vm->m_area.add_y(em, vi, -1); + } + } + + return stone_type; +} + + +void MapgenBasic::dustTopNodes() +{ + if (node_max.Y < water_level) + return; + + v3s16 em = vm->m_area.getExtent(); + 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++) { + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]); + + if (biome->c_dust == CONTENT_IGNORE) + continue; + + 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; + + vm->m_area.add_y(em, vi, -1); + } + + content_t c = vm->m_data[vi].getContent(); + 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); + } + } +} + + +void MapgenBasic::generateCaves(s16 max_stone_y, s16 large_cave_depth) +{ + if (max_stone_y < node_min.Y) + return; + + CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize, + &np_cave1, &np_cave2, seed, cave_width); + + caves_noise.generateCaves(vm, node_min, node_max, biomemap); + + if (node_max.Y > large_cave_depth) + return; + + PseudoRandom ps(blockseed + 21343); + u32 bruises_count = ps.range(0, 2); + for (u32 i = 0; i < bruises_count; i++) { + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, + c_water_source, CONTENT_IGNORE); + + cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); + } +} + + +void MapgenBasic::generateDungeons(s16 max_stone_y, MgStoneType stone_type) +{ + if (max_stone_y < node_min.Y) + return; + + DungeonParams dp; + + dp.seed = seed; + dp.c_water = c_water_source; + dp.c_river_water = c_river_water_source; + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; + dp.np_density = nparams_dungeon_density; + dp.np_alt_wall = nparams_dungeon_alt_wall; + + switch (stone_type) { + default: + case MGSTONE_STONE: + dp.c_wall = c_cobble; + dp.c_alt_wall = c_mossycobble; + dp.c_stair = c_stair_cobble; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0, 0, 0); + dp.notifytype = GENNOTIFY_DUNGEON; + break; + case MGSTONE_DESERT_STONE: + dp.c_wall = c_desert_stone; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_desert_stone; + + dp.diagonal_dirs = true; + dp.holesize = v3s16(2, 3, 2); + dp.roomsize = v3s16(2, 5, 2); + dp.notifytype = GENNOTIFY_TEMPLE; + break; + case MGSTONE_SANDSTONE: + dp.c_wall = c_sandstonebrick; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_sandstonebrick; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(2, 2, 2); + dp.roomsize = v3s16(2, 0, 2); + dp.notifytype = GENNOTIFY_DUNGEON; + break; + } + + DungeonGen dgen(ndef, &gennotify, &dp); + dgen.generate(vm, blockseed, full_node_min, full_node_max); +} + + +//// //// GenerateNotifier //// @@ -444,46 +964,50 @@ void GenerateNotifier::getEvents( //// MapgenParams //// -void MapgenParams::load(const Settings &settings) + +MapgenParams::~MapgenParams() +{ + delete bparams; +} + + +void MapgenParams::readParams(const Settings *settings) { 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; - MapgenFactory *mgfactory = EmergeManager::getMapgenFactory(mg_name); - if (mgfactory) { - sparams = mgfactory->createMapgenParams(); - sparams->readParams(&settings); + const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed"; + + if (settings->getNoEx(seed_name, seed_str)) { + if (!seed_str.empty()) + seed = read_seed(seed_str.c_str()); + else + myrand_bytes(&seed, sizeof(seed)); + } + + std::string mg_name; + if (settings->getNoEx("mg_name", mg_name)) + this->mgtype = Mapgen::getMapgenType(mg_name); + + settings->getS16NoEx("water_level", water_level); + settings->getS16NoEx("chunksize", chunksize); + settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen); + + delete bparams; + bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL); + if (bparams) { + bparams->readParams(settings); + bparams->seed = seed; } } -void MapgenParams::save(Settings &settings) const +void MapgenParams::writeParams(Settings *settings) const { - 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_MAX); - 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); + settings->set("mg_name", Mapgen::getMapgenName(mgtype)); + settings->setU64("seed", seed); + settings->setS16("water_level", water_level); + settings->setS16("chunksize", chunksize); + settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX); + + if (bparams) + bparams->writeParams(settings); } diff --git a/src/mapgen.h b/src/mapgen.h index abc3d2e89..b18bfb930 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -26,16 +26,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/container.h" -#define DEFAULT_MAPGEN "v6" +#define MAPGEN_DEFAULT MAPGEN_V7 +#define MAPGEN_DEFAULT_NAME "v7" /////////////////// Mapgen flags -#define MG_TREES 0x01 +#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags #define MG_CAVES 0x02 #define MG_DUNGEONS 0x04 -#define MG_FLAT 0x08 +#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags #define MG_LIGHT 0x10 #define MG_DECORATIONS 0x20 +typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include + class Settings; class MMVManip; class INodeDefManager; @@ -44,6 +47,9 @@ extern FlagDesc flagdesc_mapgen[]; extern FlagDesc flagdesc_gennotify[]; class Biome; +class BiomeGen; +struct BiomeParams; +class BiomeManager; class EmergeManager; class MapBlock; class VoxelManipulator; @@ -73,9 +79,9 @@ enum GenNotifyType { // TODO(hmmmm/paramat): make stone type selection dynamic enum MgStoneType { - STONE, - DESERT_STONE, - SANDSTONE, + MGSTONE_STONE, + MGSTONE_DESERT_STONE, + MGSTONE_SANDSTONE, }; struct GenNotifyEvent { @@ -102,46 +108,55 @@ private: std::list<GenNotifyEvent> m_notify_events; }; -struct MapgenSpecificParams { - virtual void readParams(const Settings *settings) = 0; - virtual void writeParams(Settings *settings) const = 0; - virtual ~MapgenSpecificParams() {} +enum MapgenType { + MAPGEN_V5, + MAPGEN_V6, + MAPGEN_V7, + MAPGEN_FLAT, + MAPGEN_FRACTAL, + MAPGEN_VALLEYS, + MAPGEN_SINGLENODE, + MAPGEN_INVALID, }; struct MapgenParams { - std::string mg_name; + MapgenType mgtype; s16 chunksize; u64 seed; s16 water_level; u32 flags; - NoiseParams np_biome_heat; - NoiseParams np_biome_heat_blend; - NoiseParams np_biome_humidity; - NoiseParams np_biome_humidity_blend; - - MapgenSpecificParams *sparams; + BiomeParams *bparams; MapgenParams() : - mg_name(DEFAULT_MAPGEN), + mgtype(MAPGEN_DEFAULT), chunksize(5), seed(0), water_level(1), flags(MG_CAVES | MG_LIGHT | MG_DECORATIONS), - np_biome_heat(NoiseParams(50, 50, v3f(750.0, 750.0, 750.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(750.0, 750.0, 750.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; + bparams(NULL) + { + } + + virtual ~MapgenParams(); + + virtual void readParams(const Settings *settings); + virtual void writeParams(Settings *settings) const; }; + +/* + Generic interface for map generators. All mapgens must inherit this class. + If a feature exposed by a public member pointer is not supported by a + certain mapgen, it must be set to NULL. + + Apart from makeChunk, getGroundLevelAtPoint, and getSpawnLevelAtPoint, all + methods can be used by constructing a Mapgen base class and setting the + appropriate public members (e.g. vm, ndef, and so on). +*/ class Mapgen { public: - int seed; + s32 seed; int water_level; u32 flags; bool generating; @@ -152,19 +167,20 @@ public: u32 blockseed; s16 *heightmap; - u8 *biomemap; - float *heatmap; - float *humidmap; + biome_t *biomemap; v3s16 csize; + BiomeGen *biomegen; GenerateNotifier gennotify; Mapgen(); Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge); virtual ~Mapgen(); - static u32 getBlockSeed(v3s16 p, int seed); - static u32 getBlockSeed2(v3s16 p, int seed); + virtual MapgenType getType() const { return MAPGEN_INVALID; } + + static u32 getBlockSeed(v3s16 p, s32 seed); + static u32 getBlockSeed2(v3s16 p, s32 seed); s16 findGroundLevelFull(v2s16 p2d); s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax); s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax); @@ -188,15 +204,81 @@ public: // signify this and to cause Server::findSpawnPos() to try another (X, Z). virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; } + // Mapgen management functions + static MapgenType getMapgenType(const std::string &mgname); + static const char *getMapgenName(MapgenType mgtype); + static Mapgen *createMapgen(MapgenType mgtype, int mgid, + MapgenParams *params, EmergeManager *emerge); + static MapgenParams *createMapgenParams(MapgenType mgtype); + static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden); + private: + // isLiquidHorizontallyFlowable() is a helper function for updateLiquid() + // that checks whether there are floodable nodes without liquid beneath + // the node at index vi. + inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em); DISABLE_CLASS_COPY(Mapgen); }; -struct MapgenFactory { - virtual Mapgen *createMapgen(int mgid, MapgenParams *params, - EmergeManager *emerge) = 0; - virtual MapgenSpecificParams *createMapgenParams() = 0; - virtual ~MapgenFactory() {} +/* + MapgenBasic is a Mapgen implementation that handles basic functionality + the majority of conventional mapgens will probably want to use, but isn't + generic enough to be included as part of the base Mapgen class (such as + generating biome terrain over terrain node skeletons, generating caves, + dungeons, etc.) + + Inherit MapgenBasic instead of Mapgen to add this basic functionality to + your mapgen without having to reimplement it. Feel free to override any of + these methods if you desire different or more advanced behavior. + + Note that you must still create your own generateTerrain implementation when + inheriting MapgenBasic. +*/ +class MapgenBasic : public Mapgen { +public: + MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge); + virtual ~MapgenBasic(); + + virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth); + virtual void generateDungeons(s16 max_stone_y, MgStoneType stone_type); + virtual MgStoneType generateBiomes(); + virtual void dustTopNodes(); + +protected: + EmergeManager *m_emerge; + BiomeManager *m_bmgr; + + Noise *noise_filler_depth; + + v3s16 node_min; + v3s16 node_max; + v3s16 full_node_min; + v3s16 full_node_max; + + // Content required for generateBiomes + content_t c_stone; + content_t c_water_source; + content_t c_river_water_source; + content_t c_desert_stone; + content_t c_sandstone; + + // Content required for generateDungeons + content_t c_cobble; + content_t c_stair_cobble; + content_t c_mossycobble; + content_t c_sandstonebrick; + content_t c_stair_sandstonebrick; + + int ystride; + int zstride; + int zstride_1d; + int zstride_1u1d; + + u32 spflags; + + NoiseParams np_cave1; + NoiseParams np_cave2; + float cave_width; }; #endif diff --git a/src/mapgen_flat.cpp b/src/mapgen_flat.cpp index 4669f1716..cc120b580 100644 --- a/src/mapgen_flat.cpp +++ b/src/mapgen_flat.cpp @@ -49,75 +49,24 @@ FlagDesc flagdesc_mapgen_flat[] = { /////////////////////////////////////////////////////////////////////////////////////// -MapgenFlat::MapgenFlat(int mapgenid, MapgenParams *params, EmergeManager *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) { - this->m_emerge = emerge; - this->bmgr = emerge->biomemgr; - - //// amount of elements to skip for the next index - //// for noise/height/biome maps (not vmanip) - this->ystride = csize.X; - // 1-down overgeneration - this->zstride_1d = csize.X * (csize.Y + 1); - - this->biomemap = new u8[csize.X * csize.Z]; - this->heightmap = new s16[csize.X * csize.Z]; - this->heatmap = NULL; - this->humidmap = NULL; - - MapgenFlatParams *sp = (MapgenFlatParams *)params->sparams; - - this->spflags = sp->spflags; - this->ground_level = sp->ground_level; - this->large_cave_depth = sp->large_cave_depth; - this->cave_width = sp->cave_width; - this->lake_threshold = sp->lake_threshold; - this->lake_steepness = sp->lake_steepness; - this->hill_threshold = sp->hill_threshold; - this->hill_steepness = sp->hill_steepness; + this->spflags = params->spflags; + this->ground_level = params->ground_level; + this->large_cave_depth = params->large_cave_depth; + this->cave_width = params->cave_width; + this->lake_threshold = params->lake_threshold; + this->lake_steepness = params->lake_steepness; + this->hill_threshold = params->hill_threshold; + this->hill_steepness = params->hill_steepness; //// 2D noise - noise_terrain = new Noise(&sp->np_terrain, seed, csize.X, csize.Z); - noise_filler_depth = new Noise(&sp->np_filler_depth, seed, csize.X, csize.Z); - - //// 3D noise - // 1-down overgeneraion - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 1, 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_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_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; + noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; } @@ -125,16 +74,6 @@ MapgenFlat::~MapgenFlat() { delete noise_terrain; delete noise_filler_depth; - delete noise_cave1; - delete noise_cave2; - - delete noise_heat; - delete noise_humidity; - delete noise_heat_blend; - delete noise_humidity_blend; - - delete[] heightmap; - delete[] biomemap; } @@ -143,7 +82,7 @@ MapgenFlatParams::MapgenFlatParams() spflags = 0; ground_level = 8; large_cave_depth = -33; - cave_width = 0.3; + cave_width = 0.09; lake_threshold = -0.45; lake_steepness = 48.0; hill_threshold = 0.45; @@ -151,8 +90,8 @@ MapgenFlatParams::MapgenFlatParams() np_terrain = NoiseParams(0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0); np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); - np_cave1 = NoiseParams(0, 12, v3f(96, 96, 96), 52534, 4, 0.5, 2.0); - np_cave2 = NoiseParams(0, 12, v3f(96, 96, 96), 10325, 4, 0.5, 2.0); + np_cave1 = NoiseParams(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0); + np_cave2 = NoiseParams(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0); } @@ -243,67 +182,21 @@ void MapgenFlat::makeChunk(BlockMakeData *data) blockseed = getBlockSeed2(full_node_min, seed); - // Make some noise - calculateNoise(); - // Generate base terrain, mountains, and ridges with initial heightmaps s16 stone_surface_max_y = generateTerrain(); // Create heightmap updateHeightmap(node_min, node_max); - // 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 - MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + MgStoneType stone_type = generateBiomes(); if (flags & MG_CAVES) - generateCaves(stone_surface_max_y); - - if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - 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; - } + generateCaves(stone_surface_max_y, large_cave_depth); - DungeonGen dgen(this, &dp); - dgen.generate(blockseed, full_node_min, full_node_max); - } + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, stone_type); // Generate the registered decorations if (flags & MG_DECORATIONS) @@ -330,35 +223,6 @@ void MapgenFlat::makeChunk(BlockMakeData *data) } -void MapgenFlat::calculateNoise() -{ - //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); - s16 x = node_min.X; - s16 z = node_min.Z; - - if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) - noise_terrain->perlinMap2D(x, z); - - // Cave noises are calculated in generateCaves() - // only if solid terrain is present in mapchunk - - 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()); -} - - s16 MapgenFlat::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -369,13 +233,14 @@ s16 MapgenFlat::generateTerrain() s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 ni2d = 0; + bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS); + if (use_noise) + noise_terrain->perlinMap2D(node_min.X, node_min.Z); + for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) { s16 stone_level = ground_level; - float n_terrain = 0.0f; - - if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) - n_terrain = noise_terrain->result[ni2d]; + float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f; if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { s16 depress = (lake_threshold - n_terrain) * lake_steepness; @@ -404,219 +269,3 @@ s16 MapgenFlat::generateTerrain() return stone_surface_max_y; } - - -MgStoneType MapgenFlat::generateBiomes(float *heat_map, float *humidity_map) -{ - 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 = 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_MAX; - - - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - 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_MAX; - - 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); - } - - air_above = false; - water_above = false; - } else if (c == c_water_source) { - 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) { - 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_MAX; // Disable top/filler placement - air_above = false; - water_above = false; - } - - vm->m_area.add_y(em, vi, -1); - } - } - - return stone_type; -} - - -void MapgenFlat::dustTopNodes() -{ - if (node_max.Y < water_level) - return; - - v3s16 em = vm->m_area.getExtent(); - 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++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); - - if (biome->c_dust == CONTENT_IGNORE) - continue; - - 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; - - vm->m_area.add_y(em, vi, -1); - } - - content_t c = vm->m_data[vi].getContent(); - 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); - } - } -} - - -void MapgenFlat::generateCaves(s16 max_stone_y) -{ - if (max_stone_y < node_min.Y) - return; - - noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - - v3s16 em = vm->m_area.getExtent(); - u32 index2d = 0; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - bool column_is_open = false; // Is column open to overground - bool is_tunnel = false; // Is tunnel or tunnel floor - u32 vi = vm->m_area.index(x, node_max.Y, z); - u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + - (x - node_min.X); - // Biome of column - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index2d]); - - // Don't excavate the overgenerated stone at node_max.Y + 1, - // this creates a 'roof' over the tunnel, preventing light in - // tunnels at mapchunk borders when generating mapchunks upwards. - // This 'roof' is removed when the mapchunk above is generated. - for (s16 y = node_max.Y; y >= node_min.Y - 1; y--, - index3d -= ystride, - vm->m_area.add_y(em, vi, -1)) { - - content_t c = vm->m_data[vi].getContent(); - if (c == CONTENT_AIR || c == biome->c_water_top || - c == biome->c_water) { - column_is_open = true; - continue; - } - // Ground - float d1 = contour(noise_cave1->result[index3d]); - float d2 = contour(noise_cave2->result[index3d]); - - if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { - // In tunnel and ground content, excavate - vm->m_data[vi] = MapNode(CONTENT_AIR); - is_tunnel = true; - } else { - // Not in tunnel or not ground content - if (is_tunnel && column_is_open && - (c == biome->c_filler || c == biome->c_stone)) - // Tunnel entrance floor - vm->m_data[vi] = MapNode(biome->c_top); - - column_is_open = false; - is_tunnel = false; - } - } - } - - if (node_max.Y > large_cave_depth) - return; - - PseudoRandom ps(blockseed + 21343); - u32 bruises_count = ps.range(0, 2); - for (u32 i = 0; i < bruises_count; i++) { - CaveV5 cave(this, &ps); - cave.makeCave(node_min, node_max, max_stone_y); - } -} diff --git a/src/mapgen_flat.h b/src/mapgen_flat.h index 8aed09be5..8b3de2bcf 100644 --- a/src/mapgen_flat.h +++ b/src/mapgen_flat.h @@ -32,7 +32,7 @@ class BiomeManager; extern FlagDesc flagdesc_mapgen_flat[]; -struct MapgenFlatParams : public MapgenSpecificParams { +struct MapgenFlatParams : public MapgenParams { u32 spflags; s16 ground_level; s16 large_cave_depth; @@ -53,72 +53,25 @@ struct MapgenFlatParams : public MapgenSpecificParams { void writeParams(Settings *settings) const; }; -class MapgenFlat : public Mapgen { +class MapgenFlat : public MapgenBasic { public: - EmergeManager *m_emerge; - BiomeManager *bmgr; + MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge); + ~MapgenFlat(); - int ystride; - int zstride_1d; + virtual MapgenType getType() const { return MAPGEN_FLAT; } - v3s16 node_min; - v3s16 node_max; - v3s16 full_node_min; - v3s16 full_node_max; + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + s16 generateTerrain(); - u32 spflags; +private: s16 ground_level; s16 large_cave_depth; - float cave_width; float lake_threshold; float lake_steepness; float hill_threshold; float hill_steepness; Noise *noise_terrain; - Noise *noise_filler_depth; - Noise *noise_cave1; - Noise *noise_cave2; - - Noise *noise_heat; - Noise *noise_humidity; - Noise *noise_heat_blend; - Noise *noise_humidity_blend; - - content_t c_stone; - content_t c_water_source; - content_t c_lava_source; - content_t c_desert_stone; - content_t c_ice; - content_t c_sandstone; - - content_t c_cobble; - content_t c_stair_cobble; - content_t c_mossycobble; - content_t c_sandstonebrick; - content_t c_stair_sandstonebrick; - - MapgenFlat(int mapgenid, MapgenParams *params, EmergeManager *emerge); - ~MapgenFlat(); - - virtual void makeChunk(BlockMakeData *data); - int getSpawnLevelAtPoint(v2s16 p); - void calculateNoise(); - s16 generateTerrain(); - MgStoneType generateBiomes(float *heat_map, float *humidity_map); - void dustTopNodes(); - void generateCaves(s16 max_stone_y); -}; - -struct MapgenFactoryFlat : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenFlat(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenFlatParams(); - }; }; #endif diff --git a/src/mapgen_fractal.cpp b/src/mapgen_fractal.cpp index e2e29f875..a6ed18ae7 100644 --- a/src/mapgen_fractal.cpp +++ b/src/mapgen_fractal.cpp @@ -47,81 +47,30 @@ FlagDesc flagdesc_mapgen_fractal[] = { /////////////////////////////////////////////////////////////////////////////////////// -MapgenFractal::MapgenFractal(int mapgenid, MapgenParams *params, EmergeManager *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) { - this->m_emerge = emerge; - this->bmgr = emerge->biomemgr; - - //// amount of elements to skip for the next index - //// for noise/height/biome maps (not vmanip) - this->ystride = csize.X; - // 1-down overgeneration - this->zstride_1d = csize.X * (csize.Y + 1); - - this->biomemap = new u8[csize.X * csize.Z]; - this->heightmap = new s16[csize.X * csize.Z]; - this->heatmap = NULL; - this->humidmap = NULL; - - MapgenFractalParams *sp = (MapgenFractalParams *)params->sparams; - - this->spflags = sp->spflags; - this->cave_width = sp->cave_width; - this->fractal = sp->fractal; - this->iterations = sp->iterations; - this->scale = sp->scale; - this->offset = sp->offset; - this->slice_w = sp->slice_w; - this->julia_x = sp->julia_x; - this->julia_y = sp->julia_y; - this->julia_z = sp->julia_z; - this->julia_w = sp->julia_w; + this->spflags = params->spflags; + this->cave_width = params->cave_width; + this->fractal = params->fractal; + this->iterations = params->iterations; + this->scale = params->scale; + this->offset = params->offset; + this->slice_w = params->slice_w; + this->julia_x = params->julia_x; + this->julia_y = params->julia_y; + this->julia_z = params->julia_z; + this->julia_w = params->julia_w; //// 2D terrain noise - noise_seabed = new Noise(&sp->np_seabed, seed, csize.X, csize.Z); - noise_filler_depth = new Noise(&sp->np_filler_depth, seed, csize.X, csize.Z); + noise_seabed = new Noise(¶ms->np_seabed, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); - //// 3D terrain noise - // 1-down overgeneraion - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 1, 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_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); + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; this->formula = fractal / 2 + fractal % 2; this->julia = fractal % 2 == 0; - - //// Resolve nodes to be used - INodeDefManager *ndef = emerge->ndef; - - 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; } @@ -129,23 +78,13 @@ MapgenFractal::~MapgenFractal() { delete noise_seabed; delete noise_filler_depth; - delete noise_cave1; - delete noise_cave2; - - delete noise_heat; - delete noise_humidity; - delete noise_heat_blend; - delete noise_humidity_blend; - - delete[] heightmap; - delete[] biomemap; } MapgenFractalParams::MapgenFractalParams() { spflags = 0; - cave_width = 0.3; + cave_width = 0.09; fractal = 1; iterations = 11; scale = v3f(4096.0, 1024.0, 4096.0); @@ -158,8 +97,8 @@ MapgenFractalParams::MapgenFractalParams() np_seabed = NoiseParams(-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0); np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); - np_cave1 = NoiseParams(0, 12, v3f(96, 96, 96), 52534, 4, 0.5, 2.0); - np_cave2 = NoiseParams(0, 12, v3f(96, 96, 96), 10325, 4, 0.5, 2.0); + np_cave1 = NoiseParams(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0); + np_cave2 = NoiseParams(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0); } @@ -217,7 +156,7 @@ int MapgenFractal::getSpawnLevelAtPoint(v2s16 p) s16 search_start = MYMAX(seabed_level, water_level + 1); if (seabed_level > water_level) solid_below = true; - + for (s16 y = search_start; y <= search_start + 128; y++) { if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node solid_below = true; @@ -259,67 +198,21 @@ void MapgenFractal::makeChunk(BlockMakeData *data) blockseed = getBlockSeed2(full_node_min, seed); - // Make some noise - calculateNoise(); - // Generate base terrain, mountains, and ridges with initial heightmaps s16 stone_surface_max_y = generateTerrain(); // Create heightmap updateHeightmap(node_min, node_max); - // 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 - MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + MgStoneType stone_type = generateBiomes(); if (flags & MG_CAVES) - generateCaves(stone_surface_max_y); - - if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - 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; - } + generateCaves(stone_surface_max_y, MGFRACTAL_LARGE_CAVE_DEPTH); - DungeonGen dgen(this, &dp); - dgen.generate(blockseed, full_node_min, full_node_max); - } + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, stone_type); // Generate the registered decorations if (flags & MG_DECORATIONS) @@ -346,34 +239,6 @@ void MapgenFractal::makeChunk(BlockMakeData *data) } -void MapgenFractal::calculateNoise() -{ - //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); - s16 x = node_min.X; - s16 z = node_min.Z; - - noise_seabed->perlinMap2D(x, z); - - // Cave noises are calculated in generateCaves() - // only if solid terrain is present in mapchunk - - 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()); -} - - bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) { float cx, cy, cz, cw, ox, oy, oz, ow; @@ -503,6 +368,8 @@ s16 MapgenFractal::generateTerrain() s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index2d = 0; + noise_seabed->perlinMap2D(node_min.X, node_min.Z); + for (s16 z = node_min.Z; z <= node_max.Z; z++) { for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { u32 vi = vm->m_area.index(node_min.X, y, z); @@ -528,219 +395,3 @@ s16 MapgenFractal::generateTerrain() return stone_surface_max_y; } - - -MgStoneType MapgenFractal::generateBiomes(float *heat_map, float *humidity_map) -{ - 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 = 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_MAX; - - - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - 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_MAX; - - 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); - } - - air_above = false; - water_above = false; - } else if (c == c_water_source) { - 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) { - 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_MAX; // Disable top/filler placement - air_above = false; - water_above = false; - } - - vm->m_area.add_y(em, vi, -1); - } - } - - return stone_type; -} - - -void MapgenFractal::dustTopNodes() -{ - if (node_max.Y < water_level) - return; - - v3s16 em = vm->m_area.getExtent(); - 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++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); - - if (biome->c_dust == CONTENT_IGNORE) - continue; - - 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; - - vm->m_area.add_y(em, vi, -1); - } - - content_t c = vm->m_data[vi].getContent(); - 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); - } - } -} - - -void MapgenFractal::generateCaves(s16 max_stone_y) -{ - if (max_stone_y < node_min.Y) - return; - - noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - - v3s16 em = vm->m_area.getExtent(); - u32 index2d = 0; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - bool column_is_open = false; // Is column open to overground - bool is_tunnel = false; // Is tunnel or tunnel floor - u32 vi = vm->m_area.index(x, node_max.Y, z); - u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + - (x - node_min.X); - // Biome of column - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index2d]); - - // Don't excavate the overgenerated stone at node_max.Y + 1, - // this creates a 'roof' over the tunnel, preventing light in - // tunnels at mapchunk borders when generating mapchunks upwards. - // This 'roof' is removed when the mapchunk above is generated. - for (s16 y = node_max.Y; y >= node_min.Y - 1; y--, - index3d -= ystride, - vm->m_area.add_y(em, vi, -1)) { - - content_t c = vm->m_data[vi].getContent(); - if (c == CONTENT_AIR || c == biome->c_water_top || - c == biome->c_water) { - column_is_open = true; - continue; - } - // Ground - float d1 = contour(noise_cave1->result[index3d]); - float d2 = contour(noise_cave2->result[index3d]); - - if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { - // In tunnel and ground content, excavate - vm->m_data[vi] = MapNode(CONTENT_AIR); - is_tunnel = true; - } else { - // Not in tunnel or not ground content - if (is_tunnel && column_is_open && - (c == biome->c_filler || c == biome->c_stone)) - // Tunnel entrance floor - vm->m_data[vi] = MapNode(biome->c_top); - - column_is_open = false; - is_tunnel = false; - } - } - } - - if (node_max.Y > MGFRACTAL_LARGE_CAVE_DEPTH) - return; - - PseudoRandom ps(blockseed + 21343); - u32 bruises_count = ps.range(0, 2); - for (u32 i = 0; i < bruises_count; i++) { - CaveV5 cave(this, &ps); - cave.makeCave(node_min, node_max, max_stone_y); - } -} diff --git a/src/mapgen_fractal.h b/src/mapgen_fractal.h index dd96045e2..3331848bc 100644 --- a/src/mapgen_fractal.h +++ b/src/mapgen_fractal.h @@ -33,7 +33,7 @@ class BiomeManager; extern FlagDesc flagdesc_mapgen_fractal[]; -struct MapgenFractalParams : public MapgenSpecificParams { +struct MapgenFractalParams : public MapgenParams { u32 spflags; float cave_width; u16 fractal; @@ -57,23 +57,22 @@ struct MapgenFractalParams : public MapgenSpecificParams { void writeParams(Settings *settings) const; }; -class MapgenFractal : public Mapgen { +class MapgenFractal : public MapgenBasic { public: - EmergeManager *m_emerge; - BiomeManager *bmgr; + MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge); + ~MapgenFractal(); + + virtual MapgenType getType() const { return MAPGEN_FRACTAL; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + bool getFractalAtPoint(s16 x, s16 y, s16 z); + s16 generateTerrain(); - int ystride; - int zstride_1d; +private: u16 formula; bool julia; - v3s16 node_min; - v3s16 node_max; - v3s16 full_node_min; - v3s16 full_node_max; - - u32 spflags; - float cave_width; u16 fractal; u16 iterations; v3f scale; @@ -84,51 +83,6 @@ public: float julia_z; float julia_w; Noise *noise_seabed; - Noise *noise_filler_depth; - Noise *noise_cave1; - Noise *noise_cave2; - - Noise *noise_heat; - Noise *noise_humidity; - Noise *noise_heat_blend; - Noise *noise_humidity_blend; - - content_t c_stone; - content_t c_water_source; - content_t c_lava_source; - content_t c_desert_stone; - content_t c_ice; - content_t c_sandstone; - - content_t c_cobble; - content_t c_stair_cobble; - content_t c_mossycobble; - content_t c_sandstonebrick; - content_t c_stair_sandstonebrick; - - MapgenFractal(int mapgenid, MapgenParams *params, EmergeManager *emerge); - ~MapgenFractal(); - - virtual void makeChunk(BlockMakeData *data); - int getSpawnLevelAtPoint(v2s16 p); - void calculateNoise(); - bool getFractalAtPoint(s16 x, s16 y, s16 z); - s16 generateTerrain(); - MgStoneType generateBiomes(float *heat_map, float *humidity_map); - void dustTopNodes(); - void generateCaves(s16 max_stone_y); -}; - -struct MapgenFactoryFractal : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenFractal(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenFractalParams(); - }; }; #endif diff --git a/src/mapgen_singlenode.h b/src/mapgen_singlenode.h index 2c6496c00..07520134d 100644 --- a/src/mapgen_singlenode.h +++ b/src/mapgen_singlenode.h @@ -22,11 +22,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" -struct MapgenSinglenodeParams : public MapgenSpecificParams { - +struct MapgenSinglenodeParams : public MapgenParams { MapgenSinglenodeParams() {} ~MapgenSinglenodeParams() {} - + void readParams(const Settings *settings) {} void writeParams(Settings *settings) const {} }; @@ -39,19 +38,11 @@ public: MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge); ~MapgenSinglenode(); - + + virtual MapgenType getType() const { return MAPGEN_SINGLENODE; } + void makeChunk(BlockMakeData *data); int getSpawnLevelAtPoint(v2s16 p); }; -struct MapgenFactorySinglenode : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) { - return new MapgenSinglenode(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() { - return new MapgenSinglenodeParams(); - }; -}; - #endif diff --git a/src/mapgen_v5.cpp b/src/mapgen_v5.cpp index b98acb928..9f189e253 100644 --- a/src/mapgen_v5.cpp +++ b/src/mapgen_v5.cpp @@ -45,72 +45,23 @@ FlagDesc flagdesc_mapgen_v5[] = { }; -MapgenV5::MapgenV5(int mapgenid, MapgenParams *params, EmergeManager *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) { - this->m_emerge = emerge; - this->bmgr = emerge->biomemgr; - - // amount of elements to skip for the next index - // for noise/height/biome maps (not vmanip) - this->ystride = csize.X; - // 1-down overgeneration - this->zstride_1d = csize.X * (csize.Y + 1); - - 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; - this->cave_width = sp->cave_width; + this->spflags = params->spflags; + this->cave_width = params->cave_width; // Terrain noise - noise_filler_depth = new Noise(&sp->np_filler_depth, seed, csize.X, csize.Z); - noise_factor = new Noise(&sp->np_factor, seed, csize.X, csize.Z); - noise_height = new Noise(&sp->np_height, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z); + noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z); // 3D terrain noise // 1-up 1-down overgeneration - noise_ground = new Noise(&sp->np_ground, seed, csize.X, csize.Y + 2, csize.Z); - // 1-down overgeneraion - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 1, 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_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_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; + noise_ground = new Noise(¶ms->np_ground, seed, csize.X, csize.Y + 2, csize.Z); + + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; } @@ -119,17 +70,7 @@ MapgenV5::~MapgenV5() delete noise_filler_depth; delete noise_factor; delete noise_height; - delete noise_cave1; - delete noise_cave2; delete noise_ground; - - delete noise_heat; - delete noise_humidity; - delete noise_heat_blend; - delete noise_humidity_blend; - - delete[] heightmap; - delete[] biomemap; } @@ -239,69 +180,23 @@ void MapgenV5::makeChunk(BlockMakeData *data) // Create a block-specific seed blockseed = getBlockSeed2(full_node_min, seed); - // Make some noise - calculateNoise(); - // Generate base terrain s16 stone_surface_max_y = generateBaseTerrain(); // Create heightmap updateHeightmap(node_min, node_max); - // 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 - MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + MgStoneType stone_type = generateBiomes(); // Generate caves if ((flags & MG_CAVES) && (stone_surface_max_y >= node_min.Y)) - generateCaves(stone_surface_max_y); + generateCaves(stone_surface_max_y, MGV5_LARGE_CAVE_DEPTH); // Generate dungeons and desert temples - if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - 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); - } + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, stone_type); // Generate the registered decorations if (flags & MG_DECORATIONS) @@ -328,37 +223,6 @@ void MapgenV5::makeChunk(BlockMakeData *data) } -void MapgenV5::calculateNoise() -{ - //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); - s16 x = node_min.X; - s16 y = node_min.Y - 1; - s16 z = node_min.Z; - - noise_factor->perlinMap2D(x, z); - noise_height->perlinMap2D(x, z); - noise_ground->perlinMap3D(x, y, z); - - // Cave noises are calculated in generateCaves() - // only if solid terrain is present in mapchunk - - 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()); -} - - //bool is_cave(u32 index) { // double d1 = contour(noise_cave1->result[index]); // double d2 = contour(noise_cave2->result[index]); @@ -382,6 +246,10 @@ int MapgenV5::generateBaseTerrain() u32 index2d = 0; int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + noise_factor->perlinMap2D(node_min.X, node_min.Z); + noise_height->perlinMap2D(node_min.X, node_min.Z); + noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + for (s16 z=node_min.Z; z<=node_max.Z; z++) { for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) { u32 vi = vm->m_area.index(node_min.X, y, z); @@ -414,219 +282,3 @@ int MapgenV5::generateBaseTerrain() return stone_surface_max_y; } - - -MgStoneType MapgenV5::generateBiomes(float *heat_map, float *humidity_map) -{ - 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 = 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_MAX; - - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - 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_MAX; - - 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); - } - - air_above = false; - water_above = false; - } else if (c == c_water_source) { - 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) { - 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_MAX; // Disable top/filler placement - air_above = false; - water_above = false; - } - - vm->m_area.add_y(em, vi, -1); - } - } - - return stone_type; -} - - -void MapgenV5::dustTopNodes() -{ - if (node_max.Y < water_level) - return; - - v3s16 em = vm->m_area.getExtent(); - 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++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); - - if (biome->c_dust == CONTENT_IGNORE) - continue; - - 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; - - vm->m_area.add_y(em, vi, -1); - } - - content_t c = vm->m_data[vi].getContent(); - 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); - } - } -} - - -void MapgenV5::generateCaves(int max_stone_y) -{ - if (max_stone_y < node_min.Y) - return; - - noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - - v3s16 em = vm->m_area.getExtent(); - u32 index2d = 0; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - bool column_is_open = false; // Is column open to overground - bool is_tunnel = false; // Is tunnel or tunnel floor - // Indexes at column top (node_max.Y) - u32 vi = vm->m_area.index(x, node_max.Y, z); - u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + - (x - node_min.X); - // Biome of column - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index2d]); - - // Don't excavate the overgenerated stone at node_max.Y + 1, - // this creates a 'roof' over the tunnel, preventing light in - // tunnels at mapchunk borders when generating mapchunks upwards. - // This 'roof' is removed when the mapchunk above is generated. - for (s16 y = node_max.Y; y >= node_min.Y - 1; y--, - index3d -= ystride, - vm->m_area.add_y(em, vi, -1)) { - - content_t c = vm->m_data[vi].getContent(); - if (c == CONTENT_AIR || c == biome->c_water_top || - c == biome->c_water) { - column_is_open = true; - continue; - } - // Ground - float d1 = contour(noise_cave1->result[index3d]); - float d2 = contour(noise_cave2->result[index3d]); - - if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { - // In tunnel and ground content, excavate - vm->m_data[vi] = MapNode(CONTENT_AIR); - is_tunnel = true; - } else { - // Not in tunnel or not ground content - if (is_tunnel && column_is_open && - (c == biome->c_filler || c == biome->c_stone)) - // Tunnel entrance floor - vm->m_data[vi] = MapNode(biome->c_top); - - column_is_open = false; - is_tunnel = false; - } - } - } - - if (node_max.Y > MGV5_LARGE_CAVE_DEPTH) - return; - - PseudoRandom ps(blockseed + 21343); - u32 bruises_count = ps.range(0, 2); - for (u32 i = 0; i < bruises_count; i++) { - CaveV5 cave(this, &ps); - cave.makeCave(node_min, node_max, max_stone_y); - } -} diff --git a/src/mapgen_v5.h b/src/mapgen_v5.h index fd2f7f4d8..ddb090a9c 100644 --- a/src/mapgen_v5.h +++ b/src/mapgen_v5.h @@ -30,7 +30,7 @@ class BiomeManager; extern FlagDesc flagdesc_mapgen_v5[]; -struct MapgenV5Params : public MapgenSpecificParams { +struct MapgenV5Params : public MapgenParams { u32 spflags; float cave_width; NoiseParams np_filler_depth; @@ -48,69 +48,21 @@ struct MapgenV5Params : public MapgenSpecificParams { }; -class MapgenV5 : public Mapgen { +class MapgenV5 : public MapgenBasic { public: - EmergeManager *m_emerge; - BiomeManager *bmgr; - - int ystride; - int zstride_1d; - - v3s16 node_min; - v3s16 node_max; - v3s16 full_node_min; - v3s16 full_node_max; - - u32 spflags; - float cave_width; - Noise *noise_filler_depth; - Noise *noise_factor; - Noise *noise_height; - Noise *noise_cave1; - Noise *noise_cave2; - Noise *noise_ground; - - Noise *noise_heat; - Noise *noise_humidity; - Noise *noise_heat_blend; - Noise *noise_humidity_blend; - - content_t c_stone; - content_t c_water_source; - content_t c_lava_source; - content_t c_desert_stone; - content_t c_ice; - content_t c_sandstone; - - content_t c_cobble; - content_t c_stair_cobble; - content_t c_mossycobble; - content_t c_sandstonebrick; - content_t c_stair_sandstonebrick; - - MapgenV5(int mapgenid, MapgenParams *params, EmergeManager *emerge); + MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge); ~MapgenV5(); + virtual MapgenType getType() const { return MAPGEN_V5; } + virtual void makeChunk(BlockMakeData *data); int getSpawnLevelAtPoint(v2s16 p); - void calculateNoise(); int generateBaseTerrain(); - MgStoneType generateBiomes(float *heat_map, float *humidity_map); - void generateCaves(int max_stone_y); - void dustTopNodes(); -}; - -struct MapgenFactoryV5 : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenV5(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenV5Params(); - }; +private: + Noise *noise_factor; + Noise *noise_height; + Noise *noise_ground; }; #endif diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp index c389b2ed4..79617a830 100644 --- a/src/mapgen_v6.cpp +++ b/src/mapgen_v6.cpp @@ -53,7 +53,7 @@ FlagDesc flagdesc_mapgen_v6[] = { ///////////////////////////////////////////////////////////////////////////// -MapgenV6::MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge) +MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge) : Mapgen(mapgenid, params, emerge) { this->m_emerge = emerge; @@ -61,26 +61,25 @@ MapgenV6::MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge) this->heightmap = new s16[csize.X * csize.Z]; - MapgenV6Params *sp = (MapgenV6Params *)params->sparams; - this->spflags = sp->spflags; - this->freq_desert = sp->freq_desert; - this->freq_beach = sp->freq_beach; + this->spflags = params->spflags; + this->freq_desert = params->freq_desert; + this->freq_beach = params->freq_beach; - np_cave = &sp->np_cave; - np_humidity = &sp->np_humidity; - np_trees = &sp->np_trees; - np_apple_trees = &sp->np_apple_trees; + np_cave = ¶ms->np_cave; + np_humidity = ¶ms->np_humidity; + np_trees = ¶ms->np_trees; + np_apple_trees = ¶ms->np_apple_trees; //// Create noise objects - noise_terrain_base = new Noise(&sp->np_terrain_base, seed, csize.X, csize.Y); - noise_terrain_higher = new Noise(&sp->np_terrain_higher, seed, csize.X, csize.Y); - noise_steepness = new Noise(&sp->np_steepness, seed, csize.X, csize.Y); - 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, + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y); + noise_terrain_higher = new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y); + noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y); + noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Y); + noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y); + noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y); + noise_biome = new Noise(¶ms->np_biome, seed, csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); - noise_humidity = new Noise(&sp->np_humidity, seed, + noise_humidity = new Noise(¶ms->np_humidity, seed, csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); //// Resolve nodes to be used @@ -269,7 +268,7 @@ float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher, float MapgenV6::baseTerrainLevelFromNoise(v2s16 p) { - if ((spflags & MGV6_FLAT) || (flags & MG_FLAT)) + if (spflags & MGV6_FLAT) return water_level; float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np, @@ -295,7 +294,7 @@ float MapgenV6::baseTerrainLevelFromMap(v2s16 p) float MapgenV6::baseTerrainLevelFromMap(int index) { - if ((spflags & MGV6_FLAT) || (flags & MG_FLAT)) + if (spflags & MGV6_FLAT) return water_level; float terrain_base = noise_terrain_base->result[index]; @@ -403,7 +402,7 @@ bool MapgenV6::getHaveAppleTree(v2s16 p) float MapgenV6::getMudAmount(int index) { - if ((spflags & MGV6_FLAT) || (flags & MG_FLAT)) + if (spflags & MGV6_FLAT) return MGV6_AVERAGE_MUD_AMOUNT; /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin( @@ -559,34 +558,38 @@ void MapgenV6::makeChunk(BlockMakeData *data) if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { 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; + dp.seed = seed; + dp.c_water = c_water_source; + dp.c_river_water = c_water_source; + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; + dp.np_density = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); + dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { - dp.c_cobble = c_desert_stone; - dp.c_moss = c_desert_stone; - dp.c_stair = c_desert_stone; + dp.c_wall = c_desert_stone; + dp.c_alt_wall = CONTENT_IGNORE; + 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 { - dp.c_cobble = c_cobble; - dp.c_moss = c_mossycobble; - dp.c_stair = c_stair_cobble; + dp.c_wall = c_cobble; + dp.c_alt_wall = 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; } - DungeonGen dgen(this, &dp); - dgen.generate(blockseed, full_node_min, full_node_max); + DungeonGen dgen(ndef, &gennotify, &dp); + dgen.generate(vm, blockseed, full_node_min, full_node_max); } // Add top and bottom side of water to transforming_liquid queue @@ -596,7 +599,7 @@ void MapgenV6::makeChunk(BlockMakeData *data) growGrass(); // Generate some trees, and add grass, if a jungle - if ((spflags & MGV6_TREES) || (flags & MG_TREES)) + if (spflags & MGV6_TREES) placeTreesAndJungleGrass(); // Generate the registered decorations @@ -623,7 +626,7 @@ void MapgenV6::calculateNoise() int fx = full_node_min.X; int fz = full_node_min.Z; - if (!((spflags & MGV6_FLAT) || (flags & MG_FLAT))) { + if (!(spflags & MGV6_FLAT)) { noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5); noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5); noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5); @@ -1066,9 +1069,10 @@ void MapgenV6::generateCaves(int max_stone_y) } for (u32 i = 0; i < caves_count + bruises_count; i++) { - bool large_cave = (i >= caves_count); - CaveV6 cave(this, &ps, &ps2, large_cave); + CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source); - cave.makeCave(node_min, node_max, max_stone_y); + bool large_cave = (i >= caves_count); + cave.makeCave(vm, node_min, node_max, &ps, &ps2, + large_cave, max_stone_y, heightmap); } } diff --git a/src/mapgen_v6.h b/src/mapgen_v6.h index a55fc6d53..f018ffaca 100644 --- a/src/mapgen_v6.h +++ b/src/mapgen_v6.h @@ -53,7 +53,7 @@ enum BiomeV6Type }; -struct MapgenV6Params : public MapgenSpecificParams { +struct MapgenV6Params : public MapgenParams { u32 spflags; float freq_desert; float freq_beach; @@ -124,9 +124,11 @@ public: content_t c_mossycobble; content_t c_stair_cobble; - MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge); + MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge); ~MapgenV6(); + virtual MapgenType getType() const { return MAPGEN_V6; } + void makeChunk(BlockMakeData *data); int getGroundLevelAtPoint(v2s16 p); int getSpawnLevelAtPoint(v2s16 p); @@ -162,18 +164,4 @@ public: virtual void generateCaves(int max_stone_y); }; - -struct MapgenFactoryV6 : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenV6(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenV6Params(); - }; -}; - - #endif diff --git a/src/mapgen_v7.cpp b/src/mapgen_v7.cpp index 9fb65f577..04a9e3c16 100644 --- a/src/mapgen_v7.cpp +++ b/src/mapgen_v7.cpp @@ -41,89 +41,44 @@ with this program; if not, write to the Free Software Foundation, Inc., FlagDesc flagdesc_mapgen_v7[] = { - {"mountains", MGV7_MOUNTAINS}, - {"ridges", MGV7_RIDGES}, - {NULL, 0} + {"mountains", MGV7_MOUNTAINS}, + {"ridges", MGV7_RIDGES}, + {"floatlands", MGV7_FLOATLANDS}, + {NULL, 0} }; /////////////////////////////////////////////////////////////////////////////// -MapgenV7::MapgenV7(int mapgenid, MapgenParams *params, EmergeManager *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) { - this->m_emerge = emerge; - this->bmgr = emerge->biomemgr; - - //// amount of elements to skip for the next index - //// for noise/height/biome maps (not vmanip) - this->ystride = csize.X; - // 1-up 1-down overgeneration - this->zstride_1u1d = csize.X * (csize.Y + 2); - // 1-down overgeneration - this->zstride_1d = csize.X * (csize.Y + 1); - - 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; - - this->spflags = sp->spflags; - this->cave_width = sp->cave_width; + this->spflags = params->spflags; + this->cave_width = params->cave_width; + this->float_mount_density = params->float_mount_density; + this->float_mount_height = params->float_mount_height; + this->floatland_level = params->floatland_level; + this->shadow_limit = params->shadow_limit; //// Terrain noise - noise_terrain_base = new Noise(&sp->np_terrain_base, seed, csize.X, csize.Z); - noise_terrain_alt = new Noise(&sp->np_terrain_alt, seed, csize.X, csize.Z); - noise_terrain_persist = new Noise(&sp->np_terrain_persist, seed, csize.X, csize.Z); - noise_height_select = new Noise(&sp->np_height_select, seed, csize.X, csize.Z); - noise_filler_depth = new Noise(&sp->np_filler_depth, seed, csize.X, csize.Z); - noise_mount_height = new Noise(&sp->np_mount_height, seed, csize.X, csize.Z); - noise_ridge_uwater = new Noise(&sp->np_ridge_uwater, seed, csize.X, csize.Z); + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); + noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); + noise_terrain_persist = new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z); + noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_mount_height = new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); + noise_ridge_uwater = new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); + noise_floatland_base = new Noise(¶ms->np_floatland_base, seed, csize.X, csize.Z); + noise_float_base_height = new Noise(¶ms->np_float_base_height, seed, csize.X, csize.Z); //// 3d terrain noise // 1-up 1-down overgeneration - 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); - // 1-down overgeneraion - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 1, 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_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_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; + noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); + noise_ridge = new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); + + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; } @@ -136,76 +91,85 @@ MapgenV7::~MapgenV7() delete noise_filler_depth; delete noise_mount_height; delete noise_ridge_uwater; + delete noise_floatland_base; + delete noise_float_base_height; delete noise_mountain; delete noise_ridge; - delete noise_cave1; - delete noise_cave2; - - delete noise_heat; - delete noise_humidity; - delete noise_heat_blend; - delete noise_humidity_blend; - - delete[] ridge_heightmap; - delete[] heightmap; - delete[] biomemap; } MapgenV7Params::MapgenV7Params() { - spflags = MGV7_MOUNTAINS | MGV7_RIDGES; - cave_width = 0.3; - - 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(-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0); - np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); - np_mount_height = NoiseParams(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(96, 96, 96), 52534, 4, 0.5, 2.0); - np_cave2 = NoiseParams(0, 12, v3f(96, 96, 96), 10325, 4, 0.5, 2.0); + spflags = MGV7_MOUNTAINS | MGV7_RIDGES; + cave_width = 0.09; + float_mount_density = 0.6; + float_mount_height = 128.0; + floatland_level = 1280; + shadow_limit = 1024; + + 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(-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0); + np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); + np_mount_height = NoiseParams(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_floatland_base = NoiseParams(-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0); + np_float_base_height = NoiseParams(48, 24, v3f(300, 300, 300), 907, 4, 0.7, 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(61, 61, 61), 52534, 3, 0.5, 2.0); + np_cave2 = NoiseParams(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0); } void MapgenV7Params::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7); - settings->getFloatNoEx("mgv7_cave_width", cave_width); - - settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); - settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->getNoiseParams("mgv7_np_height_select", np_height_select); - settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->getNoiseParams("mgv7_np_mountain", np_mountain); - settings->getNoiseParams("mgv7_np_ridge", np_ridge); - settings->getNoiseParams("mgv7_np_cave1", np_cave1); - settings->getNoiseParams("mgv7_np_cave2", np_cave2); + settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7); + settings->getFloatNoEx("mgv7_cave_width", cave_width); + settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density); + settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height); + settings->getS16NoEx("mgv7_floatland_level", floatland_level); + settings->getS16NoEx("mgv7_shadow_limit", shadow_limit); + + settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->getNoiseParams("mgv7_np_height_select", np_height_select); + settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base); + settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height); + settings->getNoiseParams("mgv7_np_mountain", np_mountain); + settings->getNoiseParams("mgv7_np_ridge", np_ridge); + settings->getNoiseParams("mgv7_np_cave1", np_cave1); + settings->getNoiseParams("mgv7_np_cave2", np_cave2); } void MapgenV7Params::writeParams(Settings *settings) const { - settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX); - settings->setFloat("mgv7_cave_width", cave_width); - - settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); - settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->setNoiseParams("mgv7_np_height_select", np_height_select); - settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->setNoiseParams("mgv7_np_mountain", np_mountain); - settings->setNoiseParams("mgv7_np_ridge", np_ridge); - settings->setNoiseParams("mgv7_np_cave1", np_cave1); - settings->setNoiseParams("mgv7_np_cave2", np_cave2); + settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX); + settings->setFloat("mgv7_cave_width", cave_width); + settings->setFloat("mgv7_float_mount_density", float_mount_density); + settings->setFloat("mgv7_float_mount_height", float_mount_height); + settings->setS16("mgv7_floatland_level", floatland_level); + settings->setS16("mgv7_shadow_limit", shadow_limit); + + settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->setNoiseParams("mgv7_np_height_select", np_height_select); + settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base); + settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height); + settings->setNoiseParams("mgv7_np_mountain", np_mountain); + settings->setNoiseParams("mgv7_np_ridge", np_ridge); + settings->setNoiseParams("mgv7_np_cave1", np_cave1); + settings->setNoiseParams("mgv7_np_cave2", np_cave2); } @@ -217,17 +181,27 @@ int MapgenV7::getSpawnLevelAtPoint(v2s16 p) // Base terrain calculation s16 y = baseTerrainLevelAtPoint(p.X, p.Y); - // Ridge/river terrain calculation - float width = 0.2; - float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2; - // if inside a river this is an unsuitable spawn point - if (fabs(uwatern) <= width) - return MAX_MAP_GENERATION_LIMIT; + // If enabled, check if inside a river + if (spflags & MGV7_RIDGES) { + float width = 0.2; + float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2; + if (fabs(uwatern) <= width) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + } + + // If mountains are disabled, terrain level is base terrain level + // Avoids spawn on non-existant mountain terrain + if (!(spflags & MGV7_MOUNTAINS)) { + if (y <= water_level || y > water_level + 16) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + else + return y; + } // Mountain terrain calculation int iters = 128; while (iters--) { - if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) { // Air, y is ground level + if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) { // If air above if (y <= water_level || y > water_level + 16) return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point else @@ -236,7 +210,7 @@ int MapgenV7::getSpawnLevelAtPoint(v2s16 p) y++; } - // Unsuitable spawn point, no ground surface found + // Unsuitable spawn point, no mountain surface found return MAX_MAP_GENERATION_LIMIT; } @@ -267,70 +241,26 @@ void MapgenV7::makeChunk(BlockMakeData *data) blockseed = getBlockSeed2(full_node_min, seed); - // Make some noise - calculateNoise(); - - // Generate terrain and ridges with initial heightmaps + // Generate base and mountain terrain + // An initial heightmap is no longer created here for use in generateRidgeTerrain() s16 stone_surface_max_y = generateTerrain(); + // Generate rivers if (spflags & MGV7_RIDGES) generateRidgeTerrain(); - // Update heightmap to include mountain terrain + // Create heightmap updateHeightmap(node_min, node_max); - // 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 - MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + MgStoneType stone_type = generateBiomes(); if (flags & MG_CAVES) - generateCaves(stone_surface_max_y); - - if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - 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; - } + generateCaves(stone_surface_max_y, water_level); - DungeonGen dgen(this, &dp); - dgen.generate(blockseed, full_node_min, full_node_max); - } + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, stone_type); // Generate the registered decorations if (flags & MG_DECORATIONS) @@ -346,9 +276,13 @@ void MapgenV7::makeChunk(BlockMakeData *data) updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + // Limit floatland shadow + bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) && + node_min.Y <= shadow_limit && node_max.Y >= shadow_limit); + if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), - full_node_min, full_node_max); + full_node_min, full_node_max, propagate_shadow); //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF); @@ -357,62 +291,6 @@ void MapgenV7::makeChunk(BlockMakeData *data) } -void MapgenV7::calculateNoise() -{ - //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); - s16 x = node_min.X; - s16 y = node_min.Y - 1; - s16 z = node_min.Z; - - noise_terrain_persist->perlinMap2D(x, z); - float *persistmap = noise_terrain_persist->result; - - noise_terrain_base->perlinMap2D(x, z, persistmap); - noise_terrain_alt->perlinMap2D(x, z, persistmap); - noise_height_select->perlinMap2D(x, z); - - if (spflags & MGV7_MOUNTAINS) { - noise_mountain->perlinMap3D(x, y, z); - noise_mount_height->perlinMap2D(x, z); - } - - if ((spflags & MGV7_RIDGES) && node_max.Y >= water_level) { - noise_ridge->perlinMap3D(x, y, z); - noise_ridge_uwater->perlinMap2D(x, z); - } - - // Cave noises are calculated in generateCaves() - // only if solid terrain is present in mapchunk - - 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) + - 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); -} - - float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) { float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); @@ -466,26 +344,94 @@ bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) } +bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y) +{ + // Make rim 2 nodes thick to match floatland base terrain + float density_gradient = (y >= floatland_level) ? + -pow((float)(y - floatland_level) / float_mount_height, 0.75f) : + -pow((float)(floatland_level - 1 - y) / float_mount_height, 0.75f); + + float floatn = noise_mountain->result[idx_xyz] + float_mount_density; + + return floatn + density_gradient >= 0.0f; +} + + +void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz) +{ + // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT + s16 base_min = MAX_MAP_GENERATION_LIMIT + 1; + s16 base_max = MAX_MAP_GENERATION_LIMIT; + + float n_base = noise_floatland_base->result[idx_xz]; + if (n_base > 0.0f) { + float n_base_height = noise_float_base_height->result[idx_xz]; + float amp = n_base * n_base_height; + float ridge = n_base_height / 3.0f; + base_min = floatland_level - amp / 1.5f; + + if (amp > ridge * 2.0f) { + // Lake bed + base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f; + } else { + // Hills and ridges + float diff = fabs(amp - ridge) / ridge; + // Smooth ridges using the 'smoothstep function' + float smooth_diff = diff * diff * (3.0f - 2.0f * diff); + base_max = floatland_level + ridge - smooth_diff * ridge; + } + } + + *float_base_min = base_min; + *float_base_max = base_max; +} + + int MapgenV7::generateTerrain() { MapNode n_air(CONTENT_AIR); MapNode n_stone(c_stone); MapNode n_water(c_water_source); + //// Calculate noise for terrain generation + noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z); + float *persistmap = noise_terrain_persist->result; + + noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_height_select->perlinMap2D(node_min.X, node_min.Z); + + if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) { + noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + } + + if (spflags & MGV7_MOUNTAINS) { + noise_mount_height->perlinMap2D(node_min.X, node_min.Z); + } + + if (spflags & MGV7_FLOATLANDS) { + noise_floatland_base->perlinMap2D(node_min.X, node_min.Z); + noise_float_base_height->perlinMap2D(node_min.X, node_min.Z); + } + + //// Place nodes v3s16 em = vm->m_area.getExtent(); s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index2d = 0; - bool mountain_flag = spflags & MGV7_MOUNTAINS; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { s16 surface_y = baseTerrainLevelFromMap(index2d); - heightmap[index2d] = surface_y; // Create base terrain heightmap - ridge_heightmap[index2d] = surface_y; - if (surface_y > stone_surface_max_y) stone_surface_max_y = surface_y; + // Get extent of floatland base terrain + // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT + s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1; + s16 float_base_max = MAX_MAP_GENERATION_LIMIT; + if (spflags & MGV7_FLOATLANDS) + floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d); + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); @@ -493,13 +439,21 @@ int MapgenV7::generateTerrain() if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { if (y <= surface_y) { vm->m_data[vi] = n_stone; // Base terrain - } else if (mountain_flag && + } else if ((spflags & MGV7_MOUNTAINS) && getMountainTerrainFromMap(index3d, index2d, y)) { vm->m_data[vi] = n_stone; // Mountain terrain if (y > stone_surface_max_y) stone_surface_max_y = y; + } else if ((spflags & MGV7_FLOATLANDS) && + ((y >= float_base_min && y <= float_base_max) || + getFloatlandMountainFromMap(index3d, index2d, y))) { + vm->m_data[vi] = n_stone; // Floatland terrain + stone_surface_max_y = node_max.Y; } else if (y <= water_level) { - vm->m_data[vi] = n_water; + vm->m_data[vi] = n_water; // Ground level water + } else if ((spflags & MGV7_FLOATLANDS) && + (y >= float_base_max && y <= floatland_level)) { + vm->m_data[vi] = n_water; // Floatland water } else { vm->m_data[vi] = n_air; } @@ -515,9 +469,12 @@ int MapgenV7::generateTerrain() void MapgenV7::generateRidgeTerrain() { - if (node_max.Y < water_level - 16) + if ((node_max.Y < water_level - 16) || (node_max.Y > shadow_limit)) return; + noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z); + MapNode n_water(c_water_source); MapNode n_air(CONTENT_AIR); u32 index = 0; @@ -529,9 +486,6 @@ void MapgenV7::generateRidgeTerrain() 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); - if (heightmap[j] < water_level - 16) // Use base terrain heightmap - continue; - float uwatern = noise_ridge_uwater->result[j] * 2; if (fabs(uwatern) > width) continue; @@ -544,233 +498,19 @@ void MapgenV7::generateRidgeTerrain() if (nridge + width_mod * height_mod < 0.6) continue; - if (y < ridge_heightmap[j]) - ridge_heightmap[j] = y - 1; - vm->m_data[vi] = (y > water_level) ? n_air : n_water; } } } -MgStoneType MapgenV7::generateBiomes(float *heat_map, float *humidity_map) -{ - 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 = 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_MAX; - - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - 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_MAX; - - 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); - } - - air_above = false; - water_above = false; - } else if (c == c_water_source) { - 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) { - 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_MAX; // Disable top/filler placement - air_above = false; - water_above = false; - } - - vm->m_area.add_y(em, vi, -1); - } - } - - return stone_type; -} - - -void MapgenV7::dustTopNodes() -{ - if (node_max.Y < water_level) - return; - - v3s16 em = vm->m_area.getExtent(); - 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++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); - - if (biome->c_dust == CONTENT_IGNORE) - continue; - - 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; - - vm->m_area.add_y(em, vi, -1); - } - - content_t c = vm->m_data[vi].getContent(); - 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); - } - } -} - - -void MapgenV7::generateCaves(s16 max_stone_y) -{ - if (max_stone_y < node_min.Y) - return; - - noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - - v3s16 em = vm->m_area.getExtent(); - u32 index2d = 0; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { - bool column_is_open = false; // Is column open to overground - bool is_tunnel = false; // Is tunnel or tunnel floor - // Indexes at column top (node_max.Y) - u32 vi = vm->m_area.index(x, node_max.Y, z); - u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + - (x - node_min.X); - // Biome of column - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index2d]); - - // Don't excavate the overgenerated stone at node_max.Y + 1, - // this creates a 'roof' over the tunnel, preventing light in - // tunnels at mapchunk borders when generating mapchunks upwards. - // This 'roof' is removed when the mapchunk above is generated. - for (s16 y = node_max.Y; y >= node_min.Y - 1; y--, - index3d -= ystride, - vm->m_area.add_y(em, vi, -1)) { - - content_t c = vm->m_data[vi].getContent(); - if (c == CONTENT_AIR || c == biome->c_water_top || - c == biome->c_water) { - column_is_open = true; - continue; - } - // Ground - float d1 = contour(noise_cave1->result[index3d]); - float d2 = contour(noise_cave2->result[index3d]); - - if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { - // In tunnel and ground content, excavate - vm->m_data[vi] = MapNode(CONTENT_AIR); - is_tunnel = true; - } else { - // Not in tunnel or not ground content - if (is_tunnel && column_is_open && - (c == biome->c_filler || c == biome->c_stone)) - // Tunnel entrance floor - vm->m_data[vi] = MapNode(biome->c_top); - - column_is_open = false; - is_tunnel = false; - } - } - } - - if (node_min.Y >= water_level) - return; - - PseudoRandom ps(blockseed + 21343); - u32 bruises_count = ps.range(0, 2); - for (u32 i = 0; i < bruises_count; i++) { - CaveV7 cave(this, &ps); - cave.makeCave(node_min, node_max, max_stone_y); - } -} - - -/////////////////////////////////////////////////////////////// - +//////////////////////////////////////////////////////////////////////////////// +//// Code Boneyard +//// +//// Much of the stuff here has potential to become useful again at some point +//// in the future, but we don't want it to get lost or forgotten in version +//// control. +//// #if 0 int MapgenV7::generateMountainTerrain(s16 ymax) diff --git a/src/mapgen_v7.h b/src/mapgen_v7.h index c25220646..3972387a7 100644 --- a/src/mapgen_v7.h +++ b/src/mapgen_v7.h @@ -23,18 +23,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" -/////////////////// Mapgen V7 flags -#define MGV7_MOUNTAINS 0x01 -#define MGV7_RIDGES 0x02 +////////////// Mapgen V7 flags +#define MGV7_MOUNTAINS 0x01 +#define MGV7_RIDGES 0x02 +#define MGV7_FLOATLANDS 0x04 class BiomeManager; extern FlagDesc flagdesc_mapgen_v7[]; -struct MapgenV7Params : public MapgenSpecificParams { +struct MapgenV7Params : public MapgenParams { u32 spflags; float cave_width; + float float_mount_density; + float float_mount_height; + s16 floatland_level; + s16 shadow_limit; + NoiseParams np_terrain_base; NoiseParams np_terrain_alt; NoiseParams np_terrain_persist; @@ -42,6 +48,8 @@ struct MapgenV7Params : public MapgenSpecificParams { NoiseParams np_filler_depth; NoiseParams np_mount_height; NoiseParams np_ridge_uwater; + NoiseParams np_floatland_base; + NoiseParams np_float_base_height; NoiseParams np_mountain; NoiseParams np_ridge; NoiseParams np_cave1; @@ -54,87 +62,42 @@ struct MapgenV7Params : public MapgenSpecificParams { void writeParams(Settings *settings) const; }; -class MapgenV7 : public Mapgen { +class MapgenV7 : public MapgenBasic { public: - EmergeManager *m_emerge; - BiomeManager *bmgr; - - int ystride; - int zstride_1u1d; - int zstride_1d; - - v3s16 node_min; - v3s16 node_max; - v3s16 full_node_min; - v3s16 full_node_max; - - s16 *ridge_heightmap; - - u32 spflags; - float cave_width; - Noise *noise_terrain_base; - Noise *noise_terrain_alt; - Noise *noise_terrain_persist; - Noise *noise_height_select; - Noise *noise_filler_depth; - Noise *noise_mount_height; - Noise *noise_ridge_uwater; - Noise *noise_mountain; - Noise *noise_ridge; - Noise *noise_cave1; - Noise *noise_cave2; - - Noise *noise_heat; - Noise *noise_humidity; - Noise *noise_heat_blend; - Noise *noise_humidity_blend; - - content_t c_stone; - content_t c_water_source; - content_t c_lava_source; - content_t c_desert_stone; - content_t c_ice; - content_t c_sandstone; - - content_t c_cobble; - 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(int mapgenid, MapgenV7Params *params, EmergeManager *emerge); ~MapgenV7(); + virtual MapgenType getType() const { return MAPGEN_V7; } + virtual void makeChunk(BlockMakeData *data); int getSpawnLevelAtPoint(v2s16 p); - Biome *getBiomeAtPoint(v3s16 p); float baseTerrainLevelAtPoint(s16 x, s16 z); float baseTerrainLevelFromMap(int index); bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z); bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y); - - void calculateNoise(); + bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y); + void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz); int generateTerrain(); void generateRidgeTerrain(); - MgStoneType generateBiomes(float *heat_map, float *humidity_map); - void dustTopNodes(); +private: + float float_mount_density; + float float_mount_height; + s16 floatland_level; + s16 shadow_limit; - void generateCaves(s16 max_stone_y); -}; - -struct MapgenFactoryV7 : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenV7(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenV7Params(); - }; + Noise *noise_terrain_base; + Noise *noise_terrain_alt; + Noise *noise_terrain_persist; + Noise *noise_height_select; + Noise *noise_mount_height; + Noise *noise_ridge_uwater; + Noise *noise_floatland_base; + Noise *noise_float_base_height; + Noise *noise_mountain; + Noise *noise_ridge; }; #endif diff --git a/src/mapgen_valleys.cpp b/src/mapgen_valleys.cpp index 0ec5409cb..ce7a95329 100644 --- a/src/mapgen_valleys.cpp +++ b/src/mapgen_valleys.cpp @@ -64,64 +64,46 @@ static FlagDesc flagdesc_mapgen_valleys[] = { /////////////////////////////////////////////////////////////////////////////// -MapgenValleys::MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager *emerge) - : Mapgen(mapgenid, params, emerge) +MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) { - this->m_emerge = emerge; - this->bmgr = emerge->biomemgr; - - //// 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 + 2); - // 1-down overgeneration - this->zstride_1d = csize.X * (csize.Y + 1); - - this->biomemap = new u8[csize.X * csize.Z]; - this->heightmap = new s16[csize.X * csize.Z]; - this->heatmap = NULL; - this->humidmap = NULL; + // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal + this->m_bgen = (BiomeGenOriginal *)biomegen; this->map_gen_limit = MYMIN(MAX_MAP_GENERATION_LIMIT, g_settings->getU16("map_generation_limit")); - MapgenValleysParams *sp = (MapgenValleysParams *)params->sparams; + BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams; - this->spflags = sp->spflags; - this->altitude_chill = sp->altitude_chill; - this->large_cave_depth = sp->large_cave_depth; - this->lava_features_lim = rangelim(sp->lava_features, 0, 10); - this->massive_cave_depth = sp->massive_cave_depth; - this->river_depth_bed = sp->river_depth + 1.f; - this->river_size_factor = sp->river_size / 100.f; - this->water_features_lim = rangelim(sp->water_features, 0, 10); - this->cave_width = sp->cave_width; + this->spflags = params->spflags; + this->altitude_chill = params->altitude_chill; + this->large_cave_depth = params->large_cave_depth; + this->lava_features_lim = rangelim(params->lava_features, 0, 10); + this->massive_cave_depth = params->massive_cave_depth; + this->river_depth_bed = params->river_depth + 1.f; + this->river_size_factor = params->river_size / 100.f; + this->water_features_lim = rangelim(params->water_features, 0, 10); + this->cave_width = params->cave_width; //// 2D Terrain noise - noise_filler_depth = new Noise(&sp->np_filler_depth, seed, csize.X, csize.Z); - noise_inter_valley_slope = new Noise(&sp->np_inter_valley_slope, seed, csize.X, csize.Z); - noise_rivers = new Noise(&sp->np_rivers, seed, csize.X, csize.Z); - noise_terrain_height = new Noise(&sp->np_terrain_height, seed, csize.X, csize.Z); - noise_valley_depth = new Noise(&sp->np_valley_depth, seed, csize.X, csize.Z); - noise_valley_profile = new Noise(&sp->np_valley_profile, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_inter_valley_slope = new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z); + noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); + noise_terrain_height = new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z); + noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z); + noise_valley_profile = new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z); //// 3D Terrain noise // 1-up 1-down overgeneration - noise_inter_valley_fill = new Noise(&sp->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z); + noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z); // 1-down overgeneraion - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 1, csize.Z); - noise_massive_caves = new Noise(&sp->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z); - - //// Biome noise - noise_heat_blend = new Noise(¶ms->np_biome_heat_blend, seed, csize.X, csize.Z); - noise_heat = new Noise(¶ms->np_biome_heat, seed, csize.X, csize.Z); - noise_humidity_blend = new Noise(¶ms->np_biome_humidity_blend, seed, csize.X, csize.Z); - noise_humidity = new Noise(¶ms->np_biome_humidity, seed, csize.X, csize.Z); + noise_cave1 = new Noise(¶ms->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); + noise_cave2 = new Noise(¶ms->np_cave2, seed, csize.X, csize.Y + 1, csize.Z); + noise_massive_caves = new Noise(¶ms->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z); this->humid_rivers = (spflags & MGVALLEYS_HUMID_RIVERS); this->use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL); - this->humidity_adjust = params->np_biome_humidity.offset - 50.f; + this->humidity_adjust = bp->np_humidity.offset - 50.f; // a small chance of overflows if the settings are very high this->cave_water_max_height = water_level + MYMAX(0, water_features_lim - 4) * 50; @@ -129,35 +111,8 @@ MapgenValleys::MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager * tcave_cache = new float[csize.Y + 2]; - //// Resolve nodes to be used - INodeDefManager *ndef = emerge->ndef; - - c_cobble = ndef->getId("mapgen_cobble"); - c_desert_stone = ndef->getId("mapgen_desert_stone"); - c_dirt = ndef->getId("mapgen_dirt"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_mossycobble = ndef->getId("mapgen_mossycobble"); - c_river_water_source = ndef->getId("mapgen_river_water_source"); - c_sand = ndef->getId("mapgen_sand"); - c_sandstonebrick = ndef->getId("mapgen_sandstonebrick"); - c_sandstone = ndef->getId("mapgen_sandstone"); - c_stair_cobble = ndef->getId("mapgen_stair_cobble"); - c_stair_sandstonebrick = ndef->getId("mapgen_stair_sandstonebrick"); - c_stone = ndef->getId("mapgen_stone"); - c_water_source = ndef->getId("mapgen_water_source"); - - if (c_mossycobble == CONTENT_IGNORE) - c_mossycobble = c_cobble; - if (c_river_water_source == CONTENT_IGNORE) - c_river_water_source = c_water_source; - if (c_sand == CONTENT_IGNORE) - c_sand = c_stone; - if (c_sandstonebrick == CONTENT_IGNORE) - c_sandstonebrick = c_sandstone; - if (c_stair_cobble == CONTENT_IGNORE) - c_stair_cobble = c_cobble; - if (c_stair_sandstonebrick == CONTENT_IGNORE) - c_stair_sandstonebrick = c_sandstone; + // Resolve content to be used + c_lava_source = ndef->getId("mapgen_lava_source"); } @@ -174,13 +129,6 @@ MapgenValleys::~MapgenValleys() delete noise_valley_depth; delete noise_valley_profile; - delete noise_heat; - delete noise_heat_blend; - delete noise_humidity; - delete noise_humidity_blend; - - delete[] biomemap; - delete[] heightmap; delete[] tcave_cache; } @@ -195,10 +143,10 @@ MapgenValleysParams::MapgenValleysParams() river_depth = 4; // How deep to carve river channels. river_size = 5; // How wide to make rivers. water_features = 0; // How often water will occur in caves. - cave_width = 0.3; + cave_width = 0.09; - np_cave1 = NoiseParams(0, 12, v3f(96, 96, 96), 52534, 4, 0.5, 2.0); - np_cave2 = NoiseParams(0, 12, v3f(96, 96, 96), 10325, 4, 0.5, 2.0); + np_cave1 = NoiseParams(0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0); + np_cave2 = NoiseParams(0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0); np_filler_depth = NoiseParams(0.f, 1.2f, v3f(256, 256, 256), 1605, 3, 0.5f, 2.f); np_inter_valley_fill = NoiseParams(0.f, 1.f, v3f(256, 512, 256), 1993, 6, 0.8f, 2.f); np_inter_valley_slope = NoiseParams(0.5f, 0.5f, v3f(128, 128, 128), 746, 1, 1.f, 2.f); @@ -293,62 +241,24 @@ void MapgenValleys::makeChunk(BlockMakeData *data) // Generate noise maps and base terrain height. calculateNoise(); + // Generate biome noises. Note this must be executed strictly before + // generateTerrain, because generateTerrain depends on intermediate + // biome-related noises. + m_bgen->calcBiomeNoise(node_min); + // Generate base terrain with initial heightmaps s16 stone_surface_max_y = generateTerrain(); - // Create biomemap at heightmap surface - bmgr->calcBiomes(csize.X, csize.Z, heatmap, humidmap, heightmap, biomemap); - - // Actually place the biome-specific nodes - MgStoneType stone_type = generateBiomes(heatmap, humidmap); + // Place biome-specific nodes and build biomemap + MgStoneType stone_type = generateBiomes(); // Cave creation. if (flags & MG_CAVES) - generateCaves(stone_surface_max_y); + generateCaves(stone_surface_max_y, large_cave_depth); // Dungeon creation - if ((flags & MG_DUNGEONS) && node_max.Y < 50 && (stone_surface_max_y >= node_min.Y)) { - 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.f; - 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.f; - 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.f; - 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); - } + if ((flags & MG_DUNGEONS) && node_max.Y < 50) + generateDungeons(stone_surface_max_y, stone_type); // Generate the registered decorations if (flags & MG_DECORATIONS) @@ -390,11 +300,6 @@ void MapgenValleys::calculateNoise() //TimeTaker tcn("actualNoise"); - noise_filler_depth->perlinMap2D(x, z); - noise_heat_blend->perlinMap2D(x, z); - noise_heat->perlinMap2D(x, z); - noise_humidity_blend->perlinMap2D(x, z); - noise_humidity->perlinMap2D(x, z); noise_inter_valley_slope->perlinMap2D(x, z); noise_rivers->perlinMap2D(x, z); noise_terrain_height->perlinMap2D(x, z); @@ -418,9 +323,8 @@ void MapgenValleys::calculateNoise() } for (s32 index = 0; index < csize.X * csize.Z; index++) { - noise_heat->result[index] += noise_heat_blend->result[index] + heat_offset; - noise_humidity->result[index] *= humidity_scale; - noise_humidity->result[index] += noise_humidity_blend->result[index]; + m_bgen->heatmap[index] += heat_offset; + m_bgen->humidmap[index] *= humidity_scale; } TerrainNoise tn; @@ -450,9 +354,6 @@ void MapgenValleys::calculateNoise() float mount = terrainLevelFromNoise(&tn); noise_terrain_height->result[index] = mount; } - - heatmap = noise_heat->result; - humidmap = noise_humidity->result; } @@ -583,7 +484,6 @@ int MapgenValleys::generateTerrain() MapNode n_air(CONTENT_AIR); MapNode n_river_water(c_river_water_source); - MapNode n_sand(c_sand); MapNode n_stone(c_stone); MapNode n_water(c_water_source); @@ -596,7 +496,7 @@ int MapgenValleys::generateTerrain() float river_y = noise_rivers->result[index_2d]; float surface_y = noise_terrain_height->result[index_2d]; float slope = noise_inter_valley_slope->result[index_2d]; - float t_heat = noise_heat->result[index_2d]; + float t_heat = m_bgen->heatmap[index_2d]; heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT; @@ -610,14 +510,14 @@ int MapgenValleys::generateTerrain() t_heat -= alt_to_heat * MYMAX(surface_y, river_y) / altitude_chill; // If humidity is low or heat is high, lower the water table. - float delta = noise_humidity->result[index_2d] - 50.f; + float delta = m_bgen->humidmap[index_2d] - 50.f; if (delta < 0.f) { float t_evap = (t_heat - 32.f) / evaporation; river_y += delta * MYMAX(t_evap, 0.08f); } } - u32 index_3d = (z - node_min.Z) * zstride + (x - node_min.X); + u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); u32 index_data = vm->m_area.index(x, node_min.Y - 1, z); // Mapgens concern themselves with stone and water. @@ -627,10 +527,7 @@ int MapgenValleys::generateTerrain() float surface_delta = (float)y - surface_y; bool river = y + 1 < river_y; - if (fabs(surface_delta) <= 0.5f && y > water_level && river) { - // river bottom - vm->m_data[index_data] = n_sand; - } else if (slope * fill > surface_delta) { + if (slope * fill > surface_delta) { // ground vm->m_data[index_data] = n_stone; if (y > heightmap[index_2d]) @@ -643,7 +540,7 @@ int MapgenValleys::generateTerrain() } else if (river) { // river vm->m_data[index_data] = n_river_water; - } else { + } else { // air vm->m_data[index_data] = n_air; } } @@ -672,7 +569,7 @@ int MapgenValleys::generateTerrain() // Use base ground (water table) in a riverbed, to // avoid an unnatural rise in humidity. float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]); - float humid = noise_humidity->result[index_2d]; + float humid = m_bgen->humidmap[index_2d]; float water_depth = (t_alt - river_y) / humidity_dropoff; humid *= 1.f + pow(0.5f, MYMAX(water_depth, 1.f)); @@ -683,7 +580,7 @@ int MapgenValleys::generateTerrain() if (t_alt > 0.f) humid -= alt_to_humid * t_alt / altitude_chill; - noise_humidity->result[index_2d] = humid; + m_bgen->humidmap[index_2d] = humid; } // Assign the heat adjusted by any changed altitudes. @@ -693,175 +590,16 @@ int MapgenValleys::generateTerrain() float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]); if (humid_rivers && heightmap[index_2d] == (s16)myround(surface_y)) // The altitude hasn't changed. Use the first result. - noise_heat->result[index_2d] = t_heat; + m_bgen->heatmap[index_2d] = t_heat; else if (t_alt > 0.f) - noise_heat->result[index_2d] -= alt_to_heat * t_alt / altitude_chill; + m_bgen->heatmap[index_2d] -= alt_to_heat * t_alt / altitude_chill; } } return surface_max_y; } - -MgStoneType MapgenValleys::generateBiomes(float *heat_map, float *humidity_map) -{ - 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 = 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 || c_above == c_river_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_MAX; - - for (s16 y = node_max.Y; y >= node_min.Y; y--) { - 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 || c == c_river_water_source) - && (air_above || !biome))) { - // Both heat and humidity have already been adjusted for altitude. - 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.f); - 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 - || c_below == c_river_water_source) - nplaced = U16_MAX; - - 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); - } - - air_above = false; - water_above = false; - } else if (c == c_water_source) { - 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 == c_river_water_source) { - vm->m_data[vi] = MapNode(biome->c_river_water); - nplaced = depth_top; // Enable filler placement for next surface - air_above = false; - water_above = true; - } else if (c == CONTENT_AIR) { - 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_MAX; // Disable top/filler placement - air_above = false; - water_above = false; - } - - vm->m_area.add_y(em, vi, -1); - } - } - - return stone_type; -} - - -void MapgenValleys::dustTopNodes() -{ - if (node_max.Y < water_level) - return; - - v3s16 em = vm->m_area.getExtent(); - 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++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); - - if (biome->c_dust == CONTENT_IGNORE) - continue; - - 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; - - vm->m_area.add_y(em, vi, -1); - } - - content_t c = vm->m_data[vi].getContent(); - 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); - } - } -} - - -void MapgenValleys::generateCaves(s16 max_stone_y) +void MapgenValleys::generateCaves(s16 max_stone_y, s16 large_cave_depth) { if (max_stone_y < node_min.Y) return; @@ -925,8 +663,9 @@ void MapgenValleys::generateCaves(s16 max_stone_y) u32 index_2d = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) { - Biome *biome = (Biome *)bmgr->getRaw(biomemap[index_2d]); + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_2d]); bool tunnel_air_above = false; + bool is_under_river = false; bool underground = false; u32 index_data = vm->m_area.index(x, node_max.Y, z); u32 index_3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + (x - node_min.X); @@ -958,14 +697,13 @@ void MapgenValleys::generateCaves(s16 max_stone_y) } content_t c = vm->m_data[index_data].getContent(); + // Detect river water to place riverbed nodes in tunnels + if (c == biome->c_river_water) + is_under_river = true; + float d1 = contour(noise_cave1->result[index_3d]); float d2 = contour(noise_cave2->result[index_3d]); - // River water is not set as ground content - // in the default game. This can produce strange results - // when a tunnel undercuts a river. However, that's not for - // the mapgen to correct. Fix it in lua. - if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { // in a tunnel vm->m_data[index_data] = n_air; @@ -978,8 +716,10 @@ void MapgenValleys::generateCaves(s16 max_stone_y) vm->m_area.add_y(em, j, 1); if (sr > terrain - y) { - // Put dirt in tunnels near the surface. - if (underground) + // Put biome nodes in tunnels near the surface + if (is_under_river) + vm->m_data[index_data] = MapNode(biome->c_riverbed); + else if (underground) vm->m_data[index_data] = MapNode(biome->c_filler); else vm->m_data[index_data] = MapNode(biome->c_top); @@ -1010,8 +750,10 @@ void MapgenValleys::generateCaves(s16 max_stone_y) if (node_max.Y <= large_cave_depth && !made_a_big_one) { u32 bruises_count = ps.range(0, 2); for (u32 i = 0; i < bruises_count; i++) { - CaveV5 cave(this, &ps); - cave.makeCave(node_min, node_max, max_stone_y); + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, + c_water_source, c_lava_source); + + cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); } } } diff --git a/src/mapgen_valleys.h b/src/mapgen_valleys.h index 5224ea54b..6dd7ebc47 100644 --- a/src/mapgen_valleys.h +++ b/src/mapgen_valleys.h @@ -39,13 +39,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MYCUBE(x) (x) * (x) * (x) class BiomeManager; +class BiomeGenOriginal; // Global profiler //class Profiler; //extern Profiler *mapgen_profiler; -struct MapgenValleysParams : public MapgenSpecificParams { +struct MapgenValleysParams : public MapgenParams { u32 spflags; s16 large_cave_depth; s16 massive_cave_depth; @@ -84,24 +85,21 @@ struct TerrainNoise { float inter_valley_fill; }; -class MapgenValleys : public Mapgen { +class MapgenValleys : public MapgenBasic { public: - MapgenValleys(int mapgenid, MapgenParams *params, EmergeManager *emerge); + MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge); ~MapgenValleys(); + virtual MapgenType getType() const { return MAPGEN_VALLEYS; } + virtual void makeChunk(BlockMakeData *data); int getSpawnLevelAtPoint(v2s16 p); s16 large_cave_depth; private: - EmergeManager *m_emerge; - BiomeManager *bmgr; - - int ystride; - int zstride; - int zstride_1d; + BiomeGenOriginal *m_bgen; float map_gen_limit; @@ -111,12 +109,6 @@ private: s16 cave_water_max_height; s16 lava_max_height; - v3s16 node_min; - v3s16 node_max; - v3s16 full_node_min; - v3s16 full_node_max; - - u32 spflags; float altitude_chill; s16 lava_features_lim; s16 massive_cave_depth; @@ -124,37 +116,17 @@ private: float river_size_factor; float *tcave_cache; s16 water_features_lim; - float cave_width; - Noise *noise_filler_depth; - Noise *noise_cave1; - Noise *noise_cave2; Noise *noise_inter_valley_fill; Noise *noise_inter_valley_slope; Noise *noise_rivers; + Noise *noise_cave1; + Noise *noise_cave2; Noise *noise_massive_caves; Noise *noise_terrain_height; Noise *noise_valley_depth; Noise *noise_valley_profile; - Noise *noise_heat; - Noise *noise_heat_blend; - Noise *noise_humidity; - Noise *noise_humidity_blend; - - content_t c_cobble; - content_t c_desert_stone; - content_t c_dirt; - content_t c_ice; content_t c_lava_source; - content_t c_mossycobble; - content_t c_river_water_source; - content_t c_sand; - content_t c_sandstone; - content_t c_sandstonebrick; - content_t c_stair_cobble; - content_t c_stair_sandstonebrick; - content_t c_stone; - content_t c_water_source; float terrainLevelAtPoint(s16 x, s16 z); @@ -164,24 +136,7 @@ private: float terrainLevelFromNoise(TerrainNoise *tn); float adjustedTerrainLevelFromNoise(TerrainNoise *tn); - float humidityByTerrain(float humidity_base, float mount, float rivers, float valley); - - MgStoneType generateBiomes(float *heat_map, float *humidity_map); - void dustTopNodes(); - - void generateCaves(s16 max_stone_y); -}; - -struct MapgenFactoryValleys : public MapgenFactory { - Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) - { - return new MapgenValleys(mgid, params, emerge); - }; - - MapgenSpecificParams *createMapgenParams() - { - return new MapgenValleysParams(); - }; + virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth); }; #endif diff --git a/src/mapnode.cpp b/src/mapnode.cpp index eba47446d..5efebf3d8 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -54,10 +54,10 @@ MapNode::MapNode(INodeDefManager *ndef, const std::string &name, param2 = a_param2; } -void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr) +void MapNode::setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f) { // If node doesn't contain light data, ignore this - if(nodemgr->get(*this).param_type != CPT_LIGHT) + if(f.param_type != CPT_LIGHT) return; if(bank == LIGHTBANK_DAY) { @@ -73,6 +73,11 @@ void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr assert("Invalid light bank" == NULL); } +void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr) +{ + setLight(bank, a_light, nodemgr->get(*this)); +} + bool MapNode::isLightDayNightEq(INodeDefManager *nodemgr) const { const ContentFeatures &f = nodemgr->get(*this); @@ -103,6 +108,13 @@ u8 MapNode::getLight(enum LightBank bank, INodeDefManager *nodemgr) const return MYMAX(f.light_source, light); } +u8 MapNode::getLightRaw(enum LightBank bank, const ContentFeatures &f) const +{ + if(f.param_type == CPT_LIGHT) + return bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f; + return 0; +} + u8 MapNode::getLightNoChecks(enum LightBank bank, const ContentFeatures *f) const { return MYMAX(f->light_source, diff --git a/src/mapnode.h b/src/mapnode.h index 2f6224f02..0bd61c554 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -191,6 +191,8 @@ struct MapNode param2 = p; } + void setLight(enum LightBank bank, u8 a_light, const ContentFeatures &f); + void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr); /** @@ -202,6 +204,13 @@ struct MapNode u8 getLight(enum LightBank bank, INodeDefManager *nodemgr) const; + /*! + * Returns the node's light level from param1. + * If the node emits light, it is ignored. + * \param f the ContentFeatures of this node. + */ + u8 getLightRaw(enum LightBank bank, const ContentFeatures &f) const; + /** * This function differs from getLight(enum LightBank bank, INodeDefManager *nodemgr) * in that the ContentFeatures of the node in question are not retrieved by diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 1588a5962..410689f5e 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -42,9 +42,8 @@ void MapSector::deleteBlocks() m_block_cache = NULL; // Delete all - for(std::map<s16, MapBlock*>::iterator i = m_blocks.begin(); - i != m_blocks.end(); ++i) - { + for (UNORDERED_MAP<s16, MapBlock*>::iterator i = m_blocks.begin(); + i != m_blocks.end(); ++i) { delete i->second; } @@ -56,20 +55,13 @@ MapBlock * MapSector::getBlockBuffered(s16 y) { MapBlock *block; - if(m_block_cache != NULL && y == m_block_cache_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()) - { - block = NULL; - } - // If block exists, return it - else{ - block = n->second; - } + UNORDERED_MAP<s16, MapBlock*>::iterator n = m_blocks.find(y); + block = (n != m_blocks.end() ? n->second : NULL); // Cache the last result m_block_cache_y = y; @@ -135,18 +127,12 @@ void MapSector::deleteBlock(MapBlock *block) void MapSector::getBlocks(MapBlockVect &dest) { - for(std::map<s16, MapBlock*>::iterator bi = m_blocks.begin(); - bi != m_blocks.end(); ++bi) - { + for (UNORDERED_MAP<s16, MapBlock*>::iterator bi = m_blocks.begin(); + bi != m_blocks.end(); ++bi) { dest.push_back(bi->second); } } -bool MapSector::empty() -{ - return m_blocks.empty(); -} - /* ServerMapSector */ diff --git a/src/mapsector.h b/src/mapsector.h index 4c1ce86a3..c3bff3575 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -63,7 +63,7 @@ public: void getBlocks(MapBlockVect &dest); - bool empty(); + bool empty() const { return m_blocks.empty(); } // Always false at the moment, because sector contains no metadata. bool differs_from_disk; @@ -71,7 +71,7 @@ public: protected: // The pile of MapBlocks - std::map<s16, MapBlock*> m_blocks; + UNORDERED_MAP<s16, MapBlock*> m_blocks; Map *m_parent; // Position on parent (in MapBlock widths) diff --git a/src/mesh.cpp b/src/mesh.cpp index b5bf8660a..b68862d22 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -226,7 +226,27 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh, vertex->Color = colorY; else vertex->Color = colorZ; + } + } +} + +void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, + const video::SColor &color) +{ + if (!mesh) + return; + u16 mc = mesh->getMeshBufferCount(); + for (u16 j = 0; j < mc; j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride); + if (normal == vertex->Normal) { + vertex->Color = color; + } } } } diff --git a/src/mesh.h b/src/mesh.h index 8e1893773..10df97015 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -64,6 +64,10 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh, const video::SColor &colorX, const video::SColor &colorY, const video::SColor &colorZ); + +void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal, + const video::SColor &color); + /* Rotate the mesh by 6d facedir value. Method only for meshnodes, not suitable for entities. diff --git a/src/mg_biome.cpp b/src/mg_biome.cpp index 9ab8d06cc..78034bf6c 100644 --- a/src/mg_biome.cpp +++ b/src/mg_biome.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "util/mathconstants.h" #include "porting.h" +#include "settings.h" /////////////////////////////////////////////////////////////////////////////// @@ -45,6 +46,7 @@ BiomeManager::BiomeManager(IGameDef *gamedef) : b->depth_top = 0; b->depth_filler = -MAX_MAP_GENERATION_LIMIT; b->depth_water_top = 0; + b->depth_riverbed = 0; b->y_min = -MAX_MAP_GENERATION_LIMIT; b->y_max = MAX_MAP_GENERATION_LIMIT; b->heat_point = 0.0; @@ -56,6 +58,7 @@ BiomeManager::BiomeManager(IGameDef *gamedef) : 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("mapgen_stone"); b->m_nodenames.push_back("ignore"); m_ndef->pendNodeResolve(b); @@ -63,33 +66,152 @@ BiomeManager::BiomeManager(IGameDef *gamedef) : } - BiomeManager::~BiomeManager() { - //if (biomecache) - // delete[] biomecache; } +void BiomeManager::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); + deco->biomes.clear(); + } + + // Don't delete the first biome + for (size_t i = 1; i < m_objects.size(); i++) + delete (Biome *)m_objects[i]; + + m_objects.resize(1); +} + +//////////////////////////////////////////////////////////////////////////////// + + +void BiomeParamsOriginal::readParams(const Settings *settings) +{ + settings->getNoiseParams("mg_biome_np_heat", np_heat); + settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->getNoiseParams("mg_biome_np_humidity", np_humidity); + settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); +} + + +void BiomeParamsOriginal::writeParams(Settings *settings) const +{ + settings->setNoiseParams("mg_biome_np_heat", np_heat); + settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->setNoiseParams("mg_biome_np_humidity", np_humidity); + settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); +} + + +//////////////////////////////////////////////////////////////////////////////// + +BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, + BiomeParamsOriginal *params, v3s16 chunksize) +{ + m_bmgr = biomemgr; + m_params = params; + m_csize = chunksize; + + noise_heat = new Noise(¶ms->np_heat, + params->seed, m_csize.X, m_csize.Z); + noise_humidity = new Noise(¶ms->np_humidity, + params->seed, m_csize.X, m_csize.Z); + noise_heat_blend = new Noise(¶ms->np_heat_blend, + params->seed, m_csize.X, m_csize.Z); + noise_humidity_blend = new Noise(¶ms->np_humidity_blend, + params->seed, m_csize.X, m_csize.Z); + + heatmap = noise_heat->result; + humidmap = noise_humidity->result; + biomemap = new biome_t[m_csize.X * m_csize.Z]; +} + +BiomeGenOriginal::~BiomeGenOriginal() +{ + delete []biomemap; + + delete noise_heat; + delete noise_humidity; + delete noise_heat_blend; + delete noise_humidity_blend; +} + + +Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const +{ + float heat = + NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) + + NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed); + float humidity = + NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) + + NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed); + + return calcBiomeFromNoise(heat, humidity, pos.Y); +} + + +void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin) +{ + m_pmin = pmin; + + noise_heat->perlinMap2D(pmin.X, pmin.Z); + noise_humidity->perlinMap2D(pmin.X, pmin.Z); + noise_heat_blend->perlinMap2D(pmin.X, pmin.Z); + noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z); + + for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) { + noise_heat->result[i] += noise_heat_blend->result[i]; + noise_humidity->result[i] += noise_humidity_blend->result[i]; + } +} -// just a PoC, obviously needs optimization later on (precalculate this) -void BiomeManager::calcBiomes(s16 sx, s16 sy, float *heat_map, - float *humidity_map, s16 *height_map, u8 *biomeid_map) + +biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap) { - 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; + for (s32 i = 0; i != m_csize.X * m_csize.Z; i++) { + Biome *biome = calcBiomeFromNoise( + noise_heat->result[i], + noise_humidity->result[i], + heightmap[i]); + + biomemap[i] = biome->index; } + + return biomemap; +} + + +Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const +{ + return getBiomeAtIndex( + (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X), + pos.Y); +} + + +Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, s16 y) const +{ + return calcBiomeFromNoise( + noise_heat->result[index], + noise_humidity->result[index], + y); } -Biome *BiomeManager::getBiome(float heat, float humidity, s16 y) +Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, s16 y) const { Biome *b, *biome_closest = NULL; float dist_min = FLT_MAX; - for (size_t i = 1; i < m_objects.size(); i++) { - b = (Biome *)m_objects[i]; + for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) { + b = (Biome *)m_bmgr->getRaw(i); if (!b || y > b->y_max || y < b->y_min) continue; @@ -103,32 +225,11 @@ Biome *BiomeManager::getBiome(float heat, float humidity, s16 y) } } - return biome_closest ? biome_closest : (Biome *)m_objects[0]; + return biome_closest ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); } -void BiomeManager::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); - 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_objects.resize(1); -} - - -/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// void Biome::resolveNodeNames() { @@ -138,5 +239,6 @@ void Biome::resolveNodeNames() 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_riverbed, "mapgen_stone", CONTENT_AIR); getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE); } diff --git a/src/mg_biome.h b/src/mg_biome.h index 8d519f808..a10193bc3 100644 --- a/src/mg_biome.h +++ b/src/mg_biome.h @@ -22,14 +22,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "objdef.h" #include "nodedef.h" +#include "noise.h" -enum BiomeType -{ - BIOME_NORMAL, - BIOME_LIQUID, - BIOME_NETHER, - BIOME_AETHER, - BIOME_FLAT +class Settings; +class BiomeManager; + +//// +//// Biome +//// + +typedef u8 biome_t; + +#define BIOME_NONE ((biome_t)0) + +// TODO(hmmmm): Decide whether this is obsolete or will be used in the future +enum BiomeType { + BIOMETYPE_NORMAL, + BIOMETYPE_LIQUID, + BIOMETYPE_NETHER, + BIOMETYPE_AETHER, + BIOMETYPE_FLAT, }; class Biome : public ObjDef, public NodeResolver { @@ -42,11 +54,13 @@ public: content_t c_water_top; content_t c_water; content_t c_river_water; + content_t c_riverbed; content_t c_dust; s16 depth_top; s16 depth_filler; s16 depth_water_top; + s16 depth_riverbed; s16 y_min; s16 y_max; @@ -56,10 +70,122 @@ public: virtual void resolveNodeNames(); }; -class BiomeManager : public ObjDefManager { + +//// +//// BiomeGen +//// + +enum BiomeGenType { + BIOMEGEN_ORIGINAL, +}; + +struct BiomeParams { + virtual void readParams(const Settings *settings) = 0; + virtual void writeParams(Settings *settings) const = 0; + virtual ~BiomeParams() {} + + s32 seed; +}; + +class BiomeGen { +public: + virtual ~BiomeGen() {} + virtual BiomeGenType getType() const = 0; + + // Calculates the biome at the exact position provided. This function can + // be called at any time, but may be less efficient than the latter methods, + // depending on implementation. + virtual Biome *calcBiomeAtPoint(v3s16 pos) const = 0; + + // Computes any intermediate results needed for biome generation. Must be + // called before using any of: getBiomes, getBiomeAtPoint, or getBiomeAtIndex. + // Calling this invalidates the previous results stored in biomemap. + virtual void calcBiomeNoise(v3s16 pmin) = 0; + + // Gets all biomes in current chunk using each corresponding element of + // heightmap as the y position, then stores the results by biome index in + // biomemap (also returned) + virtual biome_t *getBiomes(s16 *heightmap) = 0; + + // Gets a single biome at the specified position, which must be contained + // in the region formed by m_pmin and (m_pmin + m_csize - 1). + virtual Biome *getBiomeAtPoint(v3s16 pos) const = 0; + + // Same as above, but uses a raw numeric index correlating to the (x,z) position. + virtual Biome *getBiomeAtIndex(size_t index, s16 y) const = 0; + + // Result of calcBiomes bulk computation. + biome_t *biomemap; + +protected: + BiomeManager *m_bmgr; + v3s16 m_pmin; + v3s16 m_csize; +}; + + +//// +//// BiomeGen implementations +//// + +// +// Original biome algorithm (Whittaker's classification + surface height) +// + +struct BiomeParamsOriginal : public BiomeParams { + BiomeParamsOriginal() : + np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0), + np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0), + np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0), + np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0) + { + } + + virtual void readParams(const Settings *settings); + virtual void writeParams(Settings *settings) const; + + NoiseParams np_heat; + NoiseParams np_humidity; + NoiseParams np_heat_blend; + NoiseParams np_humidity_blend; +}; + +class BiomeGenOriginal : public BiomeGen { public: - static const char *OBJECT_TITLE; + BiomeGenOriginal(BiomeManager *biomemgr, + BiomeParamsOriginal *params, v3s16 chunksize); + virtual ~BiomeGenOriginal(); + BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; } + + Biome *calcBiomeAtPoint(v3s16 pos) const; + void calcBiomeNoise(v3s16 pmin); + + biome_t *getBiomes(s16 *heightmap); + Biome *getBiomeAtPoint(v3s16 pos) const; + Biome *getBiomeAtIndex(size_t index, s16 y) const; + + Biome *calcBiomeFromNoise(float heat, float humidity, s16 y) const; + + float *heatmap; + float *humidmap; + +private: + BiomeParamsOriginal *m_params; + + Noise *noise_heat; + Noise *noise_humidity; + Noise *noise_heat_blend; + Noise *noise_humidity_blend; +}; + + +//// +//// BiomeManager +//// + +class BiomeManager : public ObjDefManager { +public: BiomeManager(IGameDef *gamedef); virtual ~BiomeManager(); @@ -73,15 +199,33 @@ public: return new Biome; } - virtual void clear(); + BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize) + { + switch (type) { + case BIOMEGEN_ORIGINAL: + return new BiomeGenOriginal(this, + (BiomeParamsOriginal *)params, chunksize); + default: + return NULL; + } + } - 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); + static BiomeParams *createBiomeParams(BiomeGenType type) + { + switch (type) { + case BIOMEGEN_ORIGINAL: + return new BiomeParamsOriginal; + default: + return NULL; + } + } + + virtual void clear(); private: IGameDef *m_gamedef; + }; -#endif +#endif diff --git a/src/mg_decoration.cpp b/src/mg_decoration.cpp index 8b6abb5d5..51e4fbbcc 100644 --- a/src/mg_decoration.cpp +++ b/src/mg_decoration.cpp @@ -26,12 +26,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" FlagDesc flagdesc_deco[] = { - {"place_center_x", DECO_PLACE_CENTER_X}, - {"place_center_y", DECO_PLACE_CENTER_Y}, - {"place_center_z", DECO_PLACE_CENTER_Z}, + {"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}, - {"liquid_surface", DECO_LIQUID_SURFACE}, - {NULL, 0} + {"liquid_surface", DECO_LIQUID_SURFACE}, + {NULL, 0} }; @@ -82,6 +82,56 @@ Decoration::~Decoration() void Decoration::resolveNodeNames() { getIdsFromNrBacklog(&c_place_on); + getIdsFromNrBacklog(&c_spawnby); +} + + +bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) +{ + // Check if the decoration can be placed on this node + u32 vi = vm->m_area.index(p); + if (!CONTAINS(c_place_on, vm->m_data[vi].getContent())) + return false; + + // Don't continue if there are no spawnby constraints + if (nspawnby == -1) + return true; + + int nneighs = 0; + static const v3s16 dirs[16] = { + v3s16( 0, 0, 1), + v3s16( 0, 0, -1), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + 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 these 16 neighbouring nodes for enough spawnby nodes + for (size_t i = 0; i != ARRLEN(dirs); i++) { + u32 index = vm->m_area.index(p + dirs[i]); + if (!vm->m_area.contains(index)) + continue; + + if (CONTAINS(c_spawnby, vm->m_data[index].getContent())) + nneighs++; + } + + if (nneighs < nspawnby) + return false; + + return true; } @@ -145,7 +195,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) y < y_min || y > y_max) continue; - if (y + getHeight() >= mg->vm->m_area.MaxEdge.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); @@ -156,7 +206,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) } if (mg->biomemap) { - std::set<u8>::iterator iter; + UNORDERED_SET<u8>::iterator iter; if (!biomes.empty()) { iter = biomes.find(mg->biomemap[mapindex]); @@ -236,66 +286,15 @@ void DecoSimple::resolveNodeNames() { Decoration::resolveNodeNames(); getIdsFromNrBacklog(&c_decos); - getIdsFromNrBacklog(&c_spawnby); } -bool DecoSimple::canPlaceDecoration(MMVManip *vm, v3s16 p) +size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p) { // Don't bother if there aren't any decorations to place if (c_decos.size() == 0) - return false; - - u32 vi = vm->m_area.index(p); - - // Check if the decoration can be placed on this node - if (!CONTAINS(c_place_on, vm->m_data[vi].getContent())) - return false; - - // Don't continue if there are no spawnby constraints - if (nspawnby == -1) - return true; - - int nneighs = 0; - v3s16 dirs[16] = { - v3s16( 0, 0, 1), - v3s16( 0, 0, -1), - v3s16( 1, 0, 0), - v3s16(-1, 0, 0), - 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 - for (size_t i = 0; i != ARRLEN(dirs); i++) { - u32 index = vm->m_area.index(p + dirs[i]); - if (!vm->m_area.contains(index)) - continue; - - if (CONTAINS(c_spawnby, vm->m_data[index].getContent())) - nneighs++; - } - - if (nneighs < nspawnby) - return false; - - return true; -} - + return 0; -size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p) -{ if (!canPlaceDecoration(vm, p)) return 0; @@ -316,7 +315,7 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p) !force_placement) break; - vm->m_data[vi] = MapNode(c_place); + vm->m_data[vi] = MapNode(c_place, 0, deco_param2); } return 1; @@ -345,9 +344,7 @@ size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p) if (schematic == NULL) return 0; - u32 vi = vm->m_area.index(p); - content_t c = vm->m_data[vi].getContent(); - if (!CONTAINS(c_place_on, c)) + if (!canPlaceDecoration(vm, p)) return 0; if (flags & DECO_PLACE_CENTER_X) @@ -370,5 +367,9 @@ size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p) int DecoSchematic::getHeight() { - return schematic->size.Y; + // Account for a schematic being sunk into the ground by flag. + // When placed normally account for how a schematic is placed + // sunk 1 node into the ground. + return (flags & DECO_PLACE_CENTER_Y) ? + (schematic->size.Y - 1) / 2 : schematic->size.Y - 1; } diff --git a/src/mg_decoration.h b/src/mg_decoration.h index ba3e9d3b2..986328ec3 100644 --- a/src/mg_decoration.h +++ b/src/mg_decoration.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MG_DECORATION_HEADER #define MG_DECORATION_HEADER -#include <set> +#include "util/cpp11_container.h" #include "objdef.h" #include "noise.h" #include "nodedef.h" @@ -68,6 +68,7 @@ public: virtual void resolveNodeNames(); + bool canPlaceDecoration(MMVManip *vm, v3s16 p); size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); //size_t placeCutoffs(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); @@ -82,25 +83,24 @@ public: s16 y_max; float fill_ratio; NoiseParams np; + std::vector<content_t> c_spawnby; + s16 nspawnby; - std::set<u8> biomes; + UNORDERED_SET<u8> biomes; //std::list<CutoffData> cutoffs; //Mutex cutoff_mutex; }; class DecoSimple : public Decoration { public: + virtual void resolveNodeNames(); virtual size_t generate(MMVManip *vm, PcgRandom *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; + u8 deco_param2; }; class DecoSchematic : public Decoration { diff --git a/src/mg_ore.cpp b/src/mg_ore.cpp index 257901614..d840d745a 100644 --- a/src/mg_ore.cpp +++ b/src/mg_ore.cpp @@ -148,7 +148,7 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, if (biomemap && !biomes.empty()) { u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X); - std::set<u8>::iterator it = biomes.find(biomemap[index]); + UNORDERED_SET<u8>::iterator it = biomes.find(biomemap[index]); if (it == biomes.end()) continue; } @@ -202,7 +202,7 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, continue; if (biomemap && !biomes.empty()) { - std::set<u8>::iterator it = biomes.find(biomemap[index]); + UNORDERED_SET<u8>::iterator it = biomes.find(biomemap[index]); if (it == biomes.end()) continue; } @@ -270,7 +270,7 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, continue; if (biomemap && !biomes.empty()) { - std::set<u8>::iterator it = biomes.find(biomemap[index]); + UNORDERED_SET<u8>::iterator it = biomes.find(biomemap[index]); if (it == biomes.end()) continue; } @@ -338,7 +338,7 @@ void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, if (biomemap && !biomes.empty()) { u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X); - std::set<u8>::iterator it = biomes.find(biomemap[bmapidx]); + UNORDERED_SET<u8>::iterator it = biomes.find(biomemap[bmapidx]); if (it == biomes.end()) continue; } @@ -422,7 +422,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, if (biomemap && !biomes.empty()) { u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X); - std::set<u8>::iterator it = biomes.find(biomemap[bmapidx]); + UNORDERED_SET<u8>::iterator it = biomes.find(biomemap[bmapidx]); if (it == biomes.end()) continue; } diff --git a/src/mg_ore.h b/src/mg_ore.h index 2e065cee3..e95fdd330 100644 --- a/src/mg_ore.h +++ b/src/mg_ore.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MG_ORE_HEADER #define MG_ORE_HEADER +#include "util/cpp11_container.h" #include "objdef.h" #include "noise.h" #include "nodedef.h" @@ -64,7 +65,7 @@ public: float nthresh; // threshold for noise at which an ore is placed NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering) Noise *noise; - std::set<u8> biomes; + UNORDERED_SET<u8> biomes; Ore(); virtual ~Ore(); diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index 0b95fa267..e028215dc 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -564,14 +564,14 @@ void Schematic::applyProbabilities(v3s16 p0, 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; + UNORDERED_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); + UNORDERED_MAP<content_t, content_t>::const_iterator it = nodeidmap.find(c); if (it == nodeidmap.end()) { id = numids; numids++; diff --git a/src/mods.h b/src/mods.h index 12576516d..af7777d18 100644 --- a/src/mods.h +++ b/src/mods.h @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <string> #include <map> -#include "json/json.h" +#include <json/json.h> #include "config.h" #define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" diff --git a/src/nameidmapping.cpp b/src/nameidmapping.cpp index ed59ddd16..2af8befff 100644 --- a/src/nameidmapping.cpp +++ b/src/nameidmapping.cpp @@ -25,7 +25,7 @@ void NameIdMapping::serialize(std::ostream &os) const { writeU8(os, 0); // version writeU16(os, m_id_to_name.size()); - for(std::map<u16, std::string>::const_iterator + for(UNORDERED_MAP<u16, std::string>::const_iterator i = m_id_to_name.begin(); i != m_id_to_name.end(); ++i){ writeU16(os, i->first); diff --git a/src/nameidmapping.h b/src/nameidmapping.h index 417c441d2..23838c8ff 100644 --- a/src/nameidmapping.h +++ b/src/nameidmapping.h @@ -23,15 +23,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include <iostream> #include <set> -#include <map> #include "irrlichttypes_bloated.h" +#include "util/cpp11_container.h" class NameIdMapping { public: void serialize(std::ostream &os) const; void deSerialize(std::istream &is); - + void clear(){ m_id_to_name.clear(); m_name_to_id.clear(); @@ -55,7 +55,7 @@ public: m_name_to_id.erase(name); } bool getName(u16 id, std::string &result) const{ - std::map<u16, std::string>::const_iterator i; + UNORDERED_MAP<u16, std::string>::const_iterator i; i = m_id_to_name.find(id); if(i == m_id_to_name.end()) return false; @@ -63,7 +63,7 @@ public: return true; } bool getId(const std::string &name, u16 &result) const{ - std::map<std::string, u16>::const_iterator i; + UNORDERED_MAP<std::string, u16>::const_iterator i; i = m_name_to_id.find(name); if(i == m_name_to_id.end()) return false; @@ -74,8 +74,8 @@ public: return m_id_to_name.size(); } private: - std::map<u16, std::string> m_id_to_name; - std::map<std::string, u16> m_name_to_id; + UNORDERED_MAP<u16, std::string> m_id_to_name; + UNORDERED_MAP<std::string, u16> m_name_to_id; }; #endif diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 0498f4048..411982f69 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -110,7 +110,7 @@ void Client::handleCommand_AuthAccept(NetworkPacket* pkt) playerpos -= v3f(0, BS / 2, 0); // Set player position - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); player->setPosition(playerpos); @@ -176,7 +176,7 @@ void Client::handleCommand_InitLegacy(NetworkPacket* pkt) // Set player position - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); player->setPosition(playerpos_f); @@ -333,7 +333,7 @@ void Client::handleCommand_Inventory(NetworkPacket* pkt) std::string datastring(pkt->getString(0), pkt->getSize()); std::istringstream is(datastring, std::ios_base::binary); - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); player->inventory.deSerialize(is); @@ -486,7 +486,7 @@ void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt) void Client::handleCommand_Movement(NetworkPacket* pkt) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); float mad, maa, maf, msw, mscr, msf, mscl, msj, lf, lfs, ls, g; @@ -511,7 +511,7 @@ void Client::handleCommand_Movement(NetworkPacket* pkt) void Client::handleCommand_HP(NetworkPacket* pkt) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); u8 oldhp = player->hp; @@ -532,7 +532,7 @@ void Client::handleCommand_HP(NetworkPacket* pkt) void Client::handleCommand_Breath(NetworkPacket* pkt) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); u16 breath; @@ -544,7 +544,7 @@ void Client::handleCommand_Breath(NetworkPacket* pkt) void Client::handleCommand_MovePlayer(NetworkPacket* pkt) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); v3f pos; @@ -634,7 +634,6 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addFile(name, sha1_raw); } - std::vector<std::string> remote_media; try { std::string str; @@ -812,9 +811,7 @@ void Client::handleCommand_StopSound(NetworkPacket* pkt) *pkt >> server_id; - std::map<s32, int>::iterator i = - m_sounds_server_to_client.find(server_id); - + UNORDERED_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); @@ -842,7 +839,7 @@ void Client::handleCommand_Privileges(NetworkPacket* pkt) void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt) { - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); // Store formspec in LocalPlayer @@ -898,8 +895,10 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) bool collisiondetection = readU8(is); std::string texture = deSerializeLongString(is); bool vertical = false; + bool collision_removal = false; try { vertical = readU8(is); + collision_removal = readU8(is); } catch (...) {} ClientEvent event; @@ -910,6 +909,7 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) event.spawn_particle.expirationtime = expirationtime; event.spawn_particle.size = size; event.spawn_particle.collisiondetection = collisiondetection; + event.spawn_particle.collision_removal = collision_removal; event.spawn_particle.vertical = vertical; event.spawn_particle.texture = new std::string(texture); @@ -942,8 +942,13 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) *pkt >> id; bool vertical = false; + bool collision_removal = false; + u16 attached_id = 0; try { *pkt >> vertical; + *pkt >> collision_removal; + *pkt >> attached_id; + } catch (...) {} ClientEvent event; @@ -961,6 +966,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) event.add_particlespawner.minsize = minsize; event.add_particlespawner.maxsize = maxsize; event.add_particlespawner.collisiondetection = collisiondetection; + event.add_particlespawner.collision_removal = collision_removal; + event.add_particlespawner.attached_id = attached_id; event.add_particlespawner.vertical = vertical; event.add_particlespawner.texture = new std::string(texture); event.add_particlespawner.id = id; @@ -1093,7 +1100,7 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) *pkt >> flags >> mask; - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); bool was_minimap_visible = player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE; @@ -1117,7 +1124,7 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt) *pkt >> param >> value; - Player *player = m_env.getLocalPlayer(); + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); if (param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) { @@ -1126,10 +1133,10 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt) player->hud_hotbar_itemcount = hotbar_itemcount; } else if (param == HUD_PARAM_HOTBAR_IMAGE) { - ((LocalPlayer *) player)->hotbar_image = value; + player->hotbar_image = value; } else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { - ((LocalPlayer *) player)->hotbar_selected_image = value; + player->hotbar_selected_image = value; } } diff --git a/src/network/connection.cpp b/src/network/connection.cpp index f7452d8e4..b711cae11 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -71,6 +71,9 @@ static inline float CALC_DTIME(unsigned int lasttime, unsigned int curtime) { #define PING_TIMEOUT 5.0 +/* maximum number of retries for reliable packets */ +#define MAX_RELIABLE_RETRY 5 + static u16 readPeerId(u8 *packetdata) { return readU16(&packetdata[4]); @@ -1399,6 +1402,7 @@ void ConnectionSendThread::runTimeouts(float dtime) } float resend_timeout = dynamic_cast<UDPPeer*>(&peer)->getResendTimeout(); + bool retry_count_exceeded = false; for(u16 i=0; i<CHANNEL_COUNT; i++) { std::list<BufferedPacket> timed_outs; @@ -1438,6 +1442,13 @@ void ConnectionSendThread::runTimeouts(float dtime) channel->UpdateBytesLost(k->data.getSize()); k->resend_count++; + if (k-> resend_count > MAX_RELIABLE_RETRY) { + retry_count_exceeded = true; + timeouted_peers.push_back(peer->id); + /* no need to check additional packets if a single one did timeout*/ + break; + } + LOG(derr_con<<m_connection->getDesc() <<"RE-SENDING timed-out RELIABLE to " << k->address.serializeString() @@ -1452,9 +1463,18 @@ void ConnectionSendThread::runTimeouts(float dtime) // do not handle rtt here as we can't decide if this packet was // lost or really takes more time to transmit } + + if (retry_count_exceeded) { + break; /* no need to check other channels if we already did timeout */ + } + channel->UpdateTimers(dtime,dynamic_cast<UDPPeer*>(&peer)->getLegacyPeer()); } + /* skip to next peer if we did timeout */ + if (retry_count_exceeded) + continue; + /* send ping if necessary */ if (dynamic_cast<UDPPeer*>(&peer)->Ping(dtime,data)) { LOG(dout_con<<m_connection->getDesc() @@ -2167,12 +2187,12 @@ void ConnectionReceiveThread::receive() throw InvalidIncomingDataException("Channel doesn't exist"); } - /* 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); + // We do not have to remind the peer of its + // peer id as the CONTROLTYPE_SET_PEER_ID + // command was sent reliably. } /* The peer was not found in our lists. Add it. */ @@ -2214,11 +2234,6 @@ void ConnectionReceiveThread::receive() } } - - /* mark peer as seen with id */ - if (!(packet_peer_id == PEER_ID_INEXISTENT)) - peer->setSentWithID(); - peer->ResetTimeout(); Channel *channel = 0; @@ -2756,7 +2771,7 @@ u16 Connection::lookupPeer(Address& sender) for(; j != m_peers.end(); ++j) { Peer *peer = j->second; - if (peer->isActive()) + if (peer->isPendingDeletion()) continue; Address tocheck; diff --git a/src/network/connection.h b/src/network/connection.h index fe2c9819d..5ee53b9d4 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -663,8 +663,7 @@ class Peer { m_last_rtt(-1.0), m_usage(0), m_timeout_counter(0.0), - m_last_timeout_check(porting::getTimeMs()), - m_has_sent_with_id(false) + m_last_timeout_check(porting::getTimeMs()) { m_rtt.avg_rtt = -1.0; m_rtt.jitter_avg = -1.0; @@ -687,21 +686,16 @@ class Peer { virtual void PutReliableSendCommand(ConnectionCommand &c, unsigned int max_packet_size) {}; - virtual bool isActive() { return false; }; - virtual bool getAddress(MTProtocols type, Address& toset) = 0; + bool isPendingDeletion() + { MutexAutoLock lock(m_exclusive_access_mutex); return m_pending_deletion; }; + void ResetTimeout() {MutexAutoLock lock(m_exclusive_access_mutex); m_timeout_counter=0.0; }; bool isTimedOut(float timeout); - void setSentWithID() - { MutexAutoLock lock(m_exclusive_access_mutex); m_has_sent_with_id = true; }; - - bool hasSentWithID() - { MutexAutoLock lock(m_exclusive_access_mutex); return m_has_sent_with_id; }; - unsigned int m_increment_packets_remaining; unsigned int m_increment_bytes_remaining; @@ -776,8 +770,6 @@ class Peer { float m_timeout_counter; u32 m_last_timeout_check; - - bool m_has_sent_with_id; }; class UDPPeer : public Peer @@ -795,9 +787,6 @@ public: void PutReliableSendCommand(ConnectionCommand &c, unsigned int max_packet_size); - bool isActive() - { return ((hasSentWithID()) && (!m_pending_deletion)); }; - bool getAddress(MTProtocols type, Address& toset); void setNonLegacyPeer(); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 72f8cabe2..524470999 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -40,6 +40,7 @@ public: u32 getSize() { return m_datasize; } u16 getPeerId() { return m_peer_id; } u16 getCommand() { return m_command; } + const u32 getRemainingBytes() const { return m_datasize - m_read_offset; } // Returns a c-string without copying. // A better name for this would be getRawString() diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 177b97680..018b392b6 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -136,9 +136,11 @@ with this program; if not, write to the Free Software Foundation, Inc., backface_culling: backwards compatibility for playing with newer client on pre-27 servers. Add nodedef v3 - connected nodeboxes + PROTOCOL_VERSION 28: + CPT2_MESHOPTIONS */ -#define LATEST_PROTOCOL_VERSION 27 +#define LATEST_PROTOCOL_VERSION 28 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 13 @@ -474,6 +476,7 @@ enum ToClientCommand u8 bool vertical u32 len u8[len] texture + u8 collision_removal */ TOCLIENT_ADD_PARTICLESPAWNER = 0x47, @@ -495,6 +498,7 @@ enum ToClientCommand u32 len u8[len] texture u32 id + u8 collision_removal */ TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY = 0x48, @@ -647,6 +651,8 @@ enum ToServerCommand [2+12+12] s32 pitch*100 [2+12+12+4] s32 yaw*100 [2+12+12+4+4] u32 keyPressed + [2+12+12+4+4+1] u8 fov*80 + [2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) */ TOSERVER_GOTBLOCKS = 0x24, diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 1bcb78a8a..dca9aabc4 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -774,13 +774,15 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) } } -void Server::handleCommand_PlayerPos(NetworkPacket* pkt) +void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, + NetworkPacket *pkt) { - if (pkt->getSize() < 12 + 12 + 4 + 4) + if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4) return; v3s32 ps, ss; s32 f32pitch, f32yaw; + u8 f32fov; *pkt >> ps; *pkt >> ss; @@ -791,8 +793,18 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) f32 yaw = (f32)f32yaw / 100.0; u32 keyPressed = 0; - if (pkt->getSize() >= 12 + 12 + 4 + 4 + 4) + // default behavior (in case an old client doesn't send these) + f32 fov = 0; + u8 wanted_range = 0; + + if (pkt->getRemainingBytes() >= 4) *pkt >> keyPressed; + if (pkt->getRemainingBytes() >= 1) { + *pkt >> f32fov; + fov = (f32)f32fov / 80.0; + } + if (pkt->getRemainingBytes() >= 1) + *pkt >> wanted_range; 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); @@ -800,7 +812,33 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) pitch = modulo360f(pitch); yaw = modulo360f(yaw); - Player *player = m_env->getPlayer(pkt->getPeerId()); + playersao->setBasePosition(position); + player->setSpeed(speed); + playersao->setPitch(pitch); + playersao->setYaw(yaw); + playersao->setFov(fov); + playersao->setWantedRange(wanted_range); + 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_PlayerPos(NetworkPacket* pkt) +{ + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -809,13 +847,6 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) 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: " @@ -825,26 +856,14 @@ void Server::handleCommand_PlayerPos(NetworkPacket* pkt) 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()); + // If player is dead we don't care of this packet + if (playersao->isDead()) { + verbosestream << "TOSERVER_PLAYERPOS: " << player->getName() + << " is dead. Ignoring packet"; + return; } + + process_PlayerPos(player, playersao, pkt); } void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) @@ -879,7 +898,8 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) void Server::handleCommand_InventoryAction(NetworkPacket* pkt) { - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1051,7 +1071,7 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) message += (wchar_t)tmp_wchar; } - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1065,7 +1085,7 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) std::wstring wname = narrow_to_wide(name); std::wstring answer_to_sender = handleChat(name, wname, message, - true, pkt->getPeerId()); + true, dynamic_cast<RemotePlayer *>(player)); if (!answer_to_sender.empty()) { // Send the answer to sender SendChatMessage(pkt->getPeerId(), answer_to_sender); @@ -1078,7 +1098,8 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) *pkt >> damage; - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1098,7 +1119,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) if (g_settings->getBool("enable_damage")) { actionstream << player->getName() << " damaged by " - << (int)damage << " hp at " << PP(player->getPosition() / BS) + << (int)damage << " hp at " << PP(playersao->getBasePosition() / BS) << std::endl; playersao->setHP(playersao->getHP() - damage); @@ -1112,7 +1133,8 @@ void Server::handleCommand_Breath(NetworkPacket* pkt) *pkt >> breath; - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1121,16 +1143,6 @@ void Server::handleCommand_Breath(NetworkPacket* pkt) 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) { @@ -1141,6 +1153,16 @@ void Server::handleCommand_Breath(NetworkPacket* pkt) return; } + /* + * If player is dead, we don't need to update the breath + * He is dead ! + */ + if (playersao->isDead()) { + verbosestream << "TOSERVER_BREATH: " << player->getName() + << " is dead. Ignoring packet"; + return; + } + playersao->setBreath(breath); SendPlayerBreath(pkt->getPeerId()); } @@ -1176,7 +1198,7 @@ void Server::handleCommand_Password(NetworkPacket* pkt) newpwd += c; } - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1224,7 +1246,8 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) if (pkt->getSize() < 2) return; - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1251,7 +1274,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) void Server::handleCommand_Respawn(NetworkPacket* pkt) { - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1260,13 +1283,16 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt) return; } - if (!player->isDead()) + PlayerSAO *playersao = player->getPlayerSAO(); + assert(playersao); + + if (!playersao->isDead()) return; RespawnPlayer(pkt->getPeerId()); actionstream << player->getName() << " respawns at " - << PP(player->getPosition()/BS) << std::endl; + << PP(playersao->getBasePosition() / BS) << std::endl; // ActiveObject is added to environment in AsyncRunStep after // the previous addition has been successfully removed @@ -1274,15 +1300,13 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt) 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 + [5] u32 length of the next item (plen) [9] serialized PointedThing + [9 + plen] player position information actions: 0: start digging (from undersurface) or use 1: stop digging (all parameters ignored) @@ -1290,16 +1314,19 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) 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); + u8 action; + u16 item_i; + *pkt >> action; + *pkt >> item_i; + std::istringstream tmp_is(pkt->readLongString(), 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()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1317,12 +1344,14 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) return; } - if (player->isDead()) { + if (playersao->isDead()) { verbosestream << "TOSERVER_INTERACT: " << player->getName() - << " is dead. Ignoring packet"; + << " is dead. Ignoring packet"; return; } + process_PlayerPos(player, playersao, pkt); + v3f player_pos = playersao->getLastGoodPosition(); // Update wielded item @@ -1450,7 +1479,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) ToolCapabilities toolcap = punchitem.getToolCapabilities(m_itemdef); v3f dir = (pointed_object->getBasePosition() - - (player->getPosition() + player->getEyeOffset()) + (playersao->getBasePosition() + playersao->getEyeOffset()) ).normalize(); float time_from_last_punch = playersao->resetTimeFromLastPunch(); @@ -1513,10 +1542,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) 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()); + ItemStack playeritem = playersao->getWieldedItem(); ToolCapabilities playeritem_toolcap = playeritem.getToolCapabilities(m_itemdef); // Get diggability and expected digging time @@ -1656,16 +1682,16 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) } } // action == 4 - + /* 5: rightclick air */ else if (action == 5) { ItemStack item = playersao->getWieldedItem(); - - actionstream << player->getName() << " activates " + + actionstream << player->getName() << " activates " << item.name << std::endl; - + if (m_script->item_OnSecondaryUse( item, playersao)) { if( playersao->setWieldedItem(item)) { @@ -1693,9 +1719,7 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) *pkt >> id; - std::map<s32, ServerPlayingSound>::iterator i = - m_playing_sounds.find(id); - + UNORDERED_MAP<s32, ServerPlayingSound>::iterator i = m_playing_sounds.find(id); if (i == m_playing_sounds.end()) continue; @@ -1721,7 +1745,8 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) fields[fieldname] = pkt->readLongString(); } - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() @@ -1771,7 +1796,8 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) fields[fieldname] = pkt->readLongString(); } - Player *player = m_env->getPlayer(pkt->getPeerId()); + RemotePlayer *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: " "No player for peer_id=" << pkt->getPeerId() diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 3a2cb00b1..ccbb42c66 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -247,6 +247,28 @@ static void deSerializeSimpleSoundSpec(SimpleSoundSpec &ss, std::istream &is) ss.gain = readF1000(is); } +void TextureSettings::readSettings() +{ + connected_glass = g_settings->getBool("connected_glass"); + 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"); + enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); + enable_minimap = g_settings->getBool("enable_minimap"); + std::string leaves_style_str = g_settings->get("leaves_style"); + + use_normal_texture = enable_shaders && + (enable_bumpmapping || enable_parallax_occlusion); + if (leaves_style_str == "fancy") { + leaves_style = LEAVES_FANCY; + } else if (leaves_style_str == "simple") { + leaves_style = LEAVES_SIMPLE; + } else { + leaves_style = LEAVES_OPAQUE; + } +} + /* ContentFeatures */ @@ -365,7 +387,10 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, post_effect_color.getGreen()); writeU8(os, post_effect_color.getBlue()); writeU8(os, param_type); - writeU8(os, param_type_2); + if ((protocol_version < 28) && (param_type_2 == CPT2_MESHOPTIONS)) + writeU8(os, CPT2_NONE); + else + writeU8(os, param_type_2); writeU8(os, is_ground_content); writeU8(os, light_propagates); writeU8(os, sunlight_propagates); @@ -457,6 +482,7 @@ void ContentFeatures::deSerialize(std::istream &is) liquid_viscosity = readU8(is); liquid_renewable = readU8(is); light_source = readU8(is); + light_source = MYMIN(light_source, LIGHT_MAX); damage_per_second = readU32(is); node_box.deSerialize(is); selection_box.deSerialize(is); @@ -486,6 +512,254 @@ void ContentFeatures::deSerialize(std::istream &is) }catch(SerializationError &e) {}; } +#ifndef SERVER +void ContentFeatures::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, + TileDef *tiledef, u32 shader_id, bool use_normal_texture, + bool backface_culling, u8 alpha, u8 material_type) +{ + tile->shader_id = shader_id; + tile->texture = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id); + tile->alpha = alpha; + tile->material_type = material_type; + + // 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; + if (backface_culling) + 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; + if (tile->material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) { + // Get texture size to determine frame count by aspect ratio + v2u32 size = tile->texture->getOriginalSize(); + int frame_height = (float)size.X / + (float)tiledef->animation.aspect_w * + (float)tiledef->animation.aspect_h; + frame_count = size.Y / frame_height; + int frame_length_ms = 1000.0 * tiledef->animation.length / frame_count; + tile->animation_frame_count = frame_count; + tile->animation_frame_length_ms = frame_length_ms; + } + + if (frame_count == 1) { + tile->material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES; + } else { + std::ostringstream os(std::ios::binary); + tile->frames.resize(frame_count); + + for (int i = 0; i < frame_count; i++) { + + FrameSpec frame; + + os.str(""); + os << tiledef->name << "^[verticalframe:" + << frame_count << ":" << i; + + 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; + } + } +} +#endif + +#ifndef SERVER +void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, + scene::ISceneManager *smgr, scene::IMeshManipulator *meshmanip, + IGameDef *gamedef, const TextureSettings &tsettings) +{ + // minimap pixel color - the average color of a texture + if (tsettings.enable_minimap && tiledef[0].name != "") + minimap_color = tsrc->getTextureAverageColor(tiledef[0].name); + + // Figure out the actual tiles to use + TileDef tdef[6]; + for (u32 j = 0; j < 6; j++) { + tdef[j] = tiledef[j]; + if (tdef[j].name == "") + tdef[j].name = "unknown_node.png"; + } + + bool is_liquid = false; + bool is_water_surface = false; + + u8 material_type = (alpha == 255) ? + TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA; + + switch (drawtype) { + default: + case NDT_NORMAL: + solidness = 2; + break; + case NDT_AIRLIKE: + solidness = 0; + break; + case NDT_LIQUID: + assert(liquid_type == LIQUID_SOURCE); + if (tsettings.opaque_water) + alpha = 255; + solidness = 1; + is_liquid = true; + break; + case NDT_FLOWINGLIQUID: + assert(liquid_type == LIQUID_FLOWING); + solidness = 0; + if (tsettings.opaque_water) + alpha = 255; + is_liquid = true; + break; + case NDT_GLASSLIKE: + solidness = 0; + visual_solidness = 1; + break; + case NDT_GLASSLIKE_FRAMED: + solidness = 0; + visual_solidness = 1; + break; + case NDT_GLASSLIKE_FRAMED_OPTIONAL: + solidness = 0; + visual_solidness = 1; + drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE; + break; + case NDT_ALLFACES: + solidness = 0; + visual_solidness = 1; + break; + case NDT_ALLFACES_OPTIONAL: + if (tsettings.leaves_style == LEAVES_FANCY) { + drawtype = NDT_ALLFACES; + solidness = 0; + visual_solidness = 1; + } else if (tsettings.leaves_style == LEAVES_SIMPLE) { + for (u32 j = 0; j < 6; j++) { + if (tiledef_special[j].name != "") + tdef[j].name = tiledef_special[j].name; + } + drawtype = NDT_GLASSLIKE; + solidness = 0; + visual_solidness = 1; + } else { + drawtype = NDT_NORMAL; + solidness = 2; + for (u32 i = 0; i < 6; i++) + tdef[i].name += std::string("^[noalpha"); + } + if (waving == 1) + material_type = TILE_MATERIAL_WAVING_LEAVES; + break; + case NDT_PLANTLIKE: + solidness = 0; + if (waving == 1) + material_type = TILE_MATERIAL_WAVING_PLANTS; + break; + case NDT_FIRELIKE: + solidness = 0; + break; + case NDT_MESH: + solidness = 0; + break; + case NDT_TORCHLIKE: + case NDT_SIGNLIKE: + case NDT_FENCELIKE: + case NDT_RAILLIKE: + case NDT_NODEBOX: + solidness = 0; + break; + } + + if (is_liquid) { + material_type = (alpha == 255) ? + TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT; + if (name == "default:water_source") + is_water_surface = true; + } + + u32 tile_shader[6]; + for (u16 j = 0; j < 6; j++) { + tile_shader[j] = shdsrc->getShader("nodes_shader", + material_type, drawtype); + } + + if (is_water_surface) { + tile_shader[0] = shdsrc->getShader("water_surface_shader", + material_type, drawtype); + } + + // Tiles (fill in f->tiles[]) + for (u16 j = 0; j < 6; j++) { + fillTileAttribs(tsrc, &tiles[j], &tdef[j], tile_shader[j], + tsettings.use_normal_texture, + tiledef[j].backface_culling, alpha, material_type); + } + + // Special tiles (fill in f->special_tiles[]) + for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { + fillTileAttribs(tsrc, &special_tiles[j], &tiledef_special[j], + tile_shader[j], tsettings.use_normal_texture, + tiledef_special[j].backface_culling, alpha, material_type); + } + + if ((drawtype == NDT_MESH) && (mesh != "")) { + // Meshnode drawtype + // Read the mesh and apply scale + mesh_ptr[0] = gamedef->getMesh(mesh); + if (mesh_ptr[0]){ + v3f scale = v3f(1.0, 1.0, 1.0) * BS * visual_scale; + scaleMesh(mesh_ptr[0], scale); + recalculateBoundingBox(mesh_ptr[0]); + meshmanip->recalculateNormals(mesh_ptr[0], true, false); + } + } else if ((drawtype == NDT_NODEBOX) && + ((node_box.type == NODEBOX_REGULAR) || + (node_box.type == NODEBOX_FIXED)) && + (!node_box.fixed.empty())) { + //Convert regular nodebox nodes to meshnodes + //Change the drawtype and apply scale + drawtype = NDT_MESH; + mesh_ptr[0] = convertNodeboxesToMesh(node_box.fixed); + v3f scale = v3f(1.0, 1.0, 1.0) * visual_scale; + scaleMesh(mesh_ptr[0], scale); + recalculateBoundingBox(mesh_ptr[0]); + meshmanip->recalculateNormals(mesh_ptr[0], true, false); + } + + //Cache 6dfacedir and wallmounted rotated clones of meshes + if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_FACEDIR)) { + for (u16 j = 1; j < 24; j++) { + mesh_ptr[j] = cloneMesh(mesh_ptr[0]); + rotateMeshBy6dFacedir(mesh_ptr[j], j); + recalculateBoundingBox(mesh_ptr[j]); + meshmanip->recalculateNormals(mesh_ptr[j], true, false); + } + } else if (tsettings.enable_mesh_cache && mesh_ptr[0] && (param_type_2 == CPT2_WALLMOUNTED)) { + static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2}; + for (u16 j = 1; j < 6; j++) { + mesh_ptr[j] = cloneMesh(mesh_ptr[0]); + rotateMeshBy6dFacedir(mesh_ptr[j], wm_to_6d[j]); + recalculateBoundingBox(mesh_ptr[j]); + meshmanip->recalculateNormals(mesh_ptr[j], true, false); + } + rotateMeshBy6dFacedir(mesh_ptr[0], wm_to_6d[0]); + recalculateBoundingBox(mesh_ptr[0]); + meshmanip->recalculateNormals(mesh_ptr[0], true, false); + } +} +#endif + /* CNodeDefManager */ @@ -505,6 +779,7 @@ public: content_t allocateId(); virtual content_t set(const std::string &name, const ContentFeatures &def); virtual content_t allocateDummy(const std::string &name); + virtual void removeNode(const std::string &name); virtual void updateAliases(IItemDefManager *idef); virtual void applyTextureOverrides(const std::string &override_filepath); virtual void updateTextures(IGameDef *gamedef, @@ -525,11 +800,6 @@ public: private: void addNameIdMapping(content_t i, std::string name); -#ifndef SERVER - void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef, - u32 shader_id, bool use_normal_texture, bool backface_culling, - u8 alpha, u8 material_type); -#endif // Features indexed by id std::vector<ContentFeatures> m_content_features; @@ -541,12 +811,12 @@ private: // item aliases too. Updated by updateAliases() // Note: Not serialized. - std::map<std::string, content_t> m_name_id_mapping_with_aliases; + UNORDERED_MAP<std::string, content_t> m_name_id_mapping_with_aliases; // A mapping from groups to a list of content_ts (and their levels) // that belong to it. Necessary for a direct lookup in getIds(). // Note: Not serialized. - std::map<std::string, GroupItems> m_group_to_items; + UNORDERED_MAP<std::string, GroupItems> m_group_to_items; // Next possibly free id content_t m_next_id; @@ -669,7 +939,7 @@ inline const ContentFeatures& CNodeDefManager::get(const MapNode &n) const bool CNodeDefManager::getId(const std::string &name, content_t &result) const { - std::map<std::string, content_t>::const_iterator + UNORDERED_MAP<std::string, content_t>::const_iterator i = m_name_id_mapping_with_aliases.find(name); if(i == m_name_id_mapping_with_aliases.end()) return false; @@ -699,7 +969,7 @@ bool CNodeDefManager::getIds(const std::string &name, } std::string group = name.substr(6); - std::map<std::string, GroupItems>::const_iterator + UNORDERED_MAP<std::string, GroupItems>::const_iterator i = m_group_to_items.find(group); if (i == m_group_to_items.end()) return true; @@ -781,7 +1051,7 @@ content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &d i != def.groups.end(); ++i) { std::string group_name = i->first; - std::map<std::string, GroupItems>::iterator + UNORDERED_MAP<std::string, GroupItems>::iterator j = m_group_to_items.find(group_name); if (j == m_group_to_items.end()) { m_group_to_items[group_name].push_back( @@ -804,6 +1074,40 @@ content_t CNodeDefManager::allocateDummy(const std::string &name) } +void CNodeDefManager::removeNode(const std::string &name) +{ + // Pre-condition + assert(name != ""); + + // Erase name from name ID mapping + content_t id = CONTENT_IGNORE; + if (m_name_id_mapping.getId(name, id)) { + m_name_id_mapping.eraseName(name); + m_name_id_mapping_with_aliases.erase(name); + } + + // Erase node content from all groups it belongs to + for (UNORDERED_MAP<std::string, GroupItems>::iterator iter_groups = + m_group_to_items.begin(); + iter_groups != m_group_to_items.end();) { + GroupItems &items = iter_groups->second; + for (GroupItems::iterator iter_groupitems = items.begin(); + iter_groupitems != items.end();) { + if (iter_groupitems->first == id) + items.erase(iter_groupitems++); + else + iter_groupitems++; + } + + // Check if group is empty + if (items.size() == 0) + m_group_to_items.erase(iter_groups++); + else + iter_groups++; + } +} + + void CNodeDefManager::updateAliases(IItemDefManager *idef) { std::set<std::string> all = idef->getAll(); @@ -841,13 +1145,8 @@ void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath } 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; - } + if (!getId(splitted[0], id)) + continue; // Ignore unknown node ContentFeatures &nodedef = m_content_features[id]; @@ -890,271 +1189,18 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef, IShaderSource *shdsrc = gamedef->getShaderSource(); scene::ISceneManager* smgr = gamedef->getSceneManager(); scene::IMeshManipulator* meshmanip = smgr->getMeshManipulator(); - - 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); + TextureSettings tsettings; + tsettings.readSettings(); 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++) { - tiledef[j] = f->tiledef[j]; - if (tiledef[j].name == "") - tiledef[j].name = "unknown_node.png"; - } - - bool is_liquid = false; - bool is_water_surface = false; - - u8 material_type = (f->alpha == 255) ? - TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA; - - switch (f->drawtype) { - default: - case NDT_NORMAL: - f->solidness = 2; - break; - case NDT_AIRLIKE: - f->solidness = 0; - break; - case NDT_LIQUID: - assert(f->liquid_type == LIQUID_SOURCE); - if (opaque_water) - f->alpha = 255; - f->solidness = 1; - is_liquid = true; - break; - case NDT_FLOWINGLIQUID: - assert(f->liquid_type == LIQUID_FLOWING); - f->solidness = 0; - if (opaque_water) - f->alpha = 255; - is_liquid = true; - break; - case NDT_GLASSLIKE: - f->solidness = 0; - f->visual_solidness = 1; - break; - case NDT_GLASSLIKE_FRAMED: - f->solidness = 0; - f->visual_solidness = 1; - break; - case NDT_GLASSLIKE_FRAMED_OPTIONAL: - f->solidness = 0; - f->visual_solidness = 1; - f->drawtype = connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE; - break; - case NDT_ALLFACES: - f->solidness = 0; - f->visual_solidness = 1; - break; - case NDT_ALLFACES_OPTIONAL: - 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; - for (u32 i = 0; i < 6; i++) - tiledef[i].name += std::string("^[noalpha"); - } - if (f->waving == 1) - material_type = TILE_MATERIAL_WAVING_LEAVES; - break; - case NDT_PLANTLIKE: - f->solidness = 0; - if (f->waving == 1) - material_type = TILE_MATERIAL_WAVING_PLANTS; - break; - case NDT_FIRELIKE: - f->solidness = 0; - break; - case NDT_MESH: - f->solidness = 0; - break; - case NDT_TORCHLIKE: - case NDT_SIGNLIKE: - case NDT_FENCELIKE: - case NDT_RAILLIKE: - case NDT_NODEBOX: - f->solidness = 0; - break; - } - - if (is_liquid) { - material_type = (f->alpha == 255) ? - TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT; - if (f->name == "default:water_source") - is_water_surface = true; - } - - u32 tile_shader[6]; - for (u16 j = 0; j < 6; j++) { - tile_shader[j] = shdsrc->getShader("nodes_shader", - material_type, f->drawtype); - } - - if (is_water_surface) { - tile_shader[0] = shdsrc->getShader("water_surface_shader", - material_type, f->drawtype); - } - - // Tiles (fill in f->tiles[]) - for (u16 j = 0; j < 6; j++) { - fillTileAttribs(tsrc, &f->tiles[j], &tiledef[j], tile_shader[j], - use_normal_texture, f->tiledef[j].backface_culling, f->alpha, material_type); - } - - // Special tiles (fill in f->special_tiles[]) - for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { - fillTileAttribs(tsrc, &f->special_tiles[j], &f->tiledef_special[j], - tile_shader[j], use_normal_texture, - f->tiledef_special[j].backface_culling, f->alpha, material_type); - } - - if ((f->drawtype == NDT_MESH) && (f->mesh != "")) { - // Meshnode drawtype - // Read the mesh and apply scale - f->mesh_ptr[0] = gamedef->getMesh(f->mesh); - if (f->mesh_ptr[0]){ - v3f scale = v3f(1.0, 1.0, 1.0) * BS * f->visual_scale; - scaleMesh(f->mesh_ptr[0], scale); - recalculateBoundingBox(f->mesh_ptr[0]); - meshmanip->recalculateNormals(f->mesh_ptr[0], true, false); - } - } else if ((f->drawtype == NDT_NODEBOX) && - ((f->node_box.type == NODEBOX_REGULAR) || - (f->node_box.type == NODEBOX_FIXED)) && - (!f->node_box.fixed.empty())) { - //Convert regular nodebox nodes to meshnodes - //Change the drawtype and apply scale - f->drawtype = NDT_MESH; - f->mesh_ptr[0] = convertNodeboxesToMesh(f->node_box.fixed); - v3f scale = v3f(1.0, 1.0, 1.0) * f->visual_scale; - scaleMesh(f->mesh_ptr[0], scale); - recalculateBoundingBox(f->mesh_ptr[0]); - meshmanip->recalculateNormals(f->mesh_ptr[0], true, false); - } - - //Cache 6dfacedir and wallmounted rotated clones of meshes - if (enable_mesh_cache && f->mesh_ptr[0] && (f->param_type_2 == CPT2_FACEDIR)) { - for (u16 j = 1; j < 24; j++) { - f->mesh_ptr[j] = cloneMesh(f->mesh_ptr[0]); - rotateMeshBy6dFacedir(f->mesh_ptr[j], j); - recalculateBoundingBox(f->mesh_ptr[j]); - meshmanip->recalculateNormals(f->mesh_ptr[j], true, false); - } - } else if (enable_mesh_cache && f->mesh_ptr[0] && (f->param_type_2 == CPT2_WALLMOUNTED)) { - static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2}; - for (u16 j = 1; j < 6; j++) { - f->mesh_ptr[j] = cloneMesh(f->mesh_ptr[0]); - rotateMeshBy6dFacedir(f->mesh_ptr[j], wm_to_6d[j]); - recalculateBoundingBox(f->mesh_ptr[j]); - meshmanip->recalculateNormals(f->mesh_ptr[j], true, false); - } - rotateMeshBy6dFacedir(f->mesh_ptr[0], wm_to_6d[0]); - recalculateBoundingBox(f->mesh_ptr[0]); - meshmanip->recalculateNormals(f->mesh_ptr[0], true, false); - } - + m_content_features[i].updateTextures(tsrc, shdsrc, smgr, meshmanip, gamedef, tsettings); progress_callback(progress_callback_args, i, size); } #endif } - -#ifndef SERVER -void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, - TileDef *tiledef, u32 shader_id, bool use_normal_texture, - bool backface_culling, u8 alpha, u8 material_type) -{ - tile->shader_id = shader_id; - tile->texture = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id); - tile->alpha = alpha; - tile->material_type = material_type; - - // 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; - if (backface_culling) - 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; - if (tile->material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) { - // Get texture size to determine frame count by aspect ratio - v2u32 size = tile->texture->getOriginalSize(); - int frame_height = (float)size.X / - (float)tiledef->animation.aspect_w * - (float)tiledef->animation.aspect_h; - frame_count = size.Y / frame_height; - int frame_length_ms = 1000.0 * tiledef->animation.length / frame_count; - tile->animation_frame_count = frame_count; - tile->animation_frame_length_ms = frame_length_ms; - } - - if (frame_count == 1) { - tile->material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES; - } else { - std::ostringstream os(std::ios::binary); - tile->frames.resize(frame_count); - - for (int i = 0; i < frame_count; i++) { - - FrameSpec frame; - - os.str(""); - os << tiledef->name << "^[verticalframe:" - << frame_count << ":" << i; - - 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; - } - } -} -#endif - - void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) const { writeU8(os, 1); // version @@ -1397,6 +1443,7 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version) liquid_alternative_source = deSerializeString(is); liquid_viscosity = readU8(is); light_source = readU8(is); + light_source = MYMIN(light_source, LIGHT_MAX); damage_per_second = readU32(is); node_box.deSerialize(is); selection_box.deSerialize(is); diff --git a/src/nodedef.h b/src/nodedef.h index 58d0faffa..80396f992 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -65,6 +65,8 @@ enum ContentParamType2 CPT2_LEVELED, // 2D rotation for things like plants CPT2_DEGROTATE, + // Mesh options for plants + CPT2_MESHOPTIONS }; enum LiquidType @@ -112,6 +114,26 @@ struct NodeBox struct MapNode; class NodeMetadata; +enum LeavesStyle { + LEAVES_FANCY, + LEAVES_SIMPLE, + LEAVES_OPAQUE, +}; + +class TextureSettings { +public: + LeavesStyle leaves_style; + bool opaque_water; + bool connected_glass; + bool use_normal_texture; + bool enable_mesh_cache; + bool enable_minimap; + + TextureSettings() {} + + void readSettings(); +}; + enum NodeDrawType { NDT_NORMAL, // A basic solid block @@ -304,6 +326,15 @@ struct ContentFeatures if(!isLiquid() || !f.isLiquid()) return false; return (liquid_alternative_flowing == f.liquid_alternative_flowing); } + +#ifndef SERVER + void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef, + u32 shader_id, bool use_normal_texture, bool backface_culling, + u8 alpha, u8 material_type); + void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, + scene::ISceneManager *smgr, scene::IMeshManipulator *meshmanip, + IGameDef *gamedef, const TextureSettings &tsettings); +#endif }; class INodeDefManager { @@ -353,6 +384,8 @@ public: const ContentFeatures &def)=0; // If returns CONTENT_IGNORE, could not allocate id virtual content_t allocateDummy(const std::string &name)=0; + // Remove a node + virtual void removeNode(const std::string &name)=0; /* Update item alias mapping. diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 126889ecf..0801a028b 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -74,6 +74,11 @@ void NodeMetadata::clear() m_inventory->clear(); } +bool NodeMetadata::empty() const +{ + return m_stringvars.size() == 0 && m_inventory->getLists().size() == 0; +} + /* NodeMetadataList */ @@ -84,14 +89,13 @@ void NodeMetadataList::serialize(std::ostream &os) const Version 0 is a placeholder for "nothing to see here; go away." */ - if(m_data.empty()){ + u16 count = countNonEmpty(); + if (count == 0) { writeU8(os, 0); // version return; } writeU8(os, 1); // version - - u16 count = m_data.size(); writeU16(os, count); for(std::map<v3s16, NodeMetadata*>::const_iterator @@ -100,6 +104,8 @@ void NodeMetadataList::serialize(std::ostream &os) const { v3s16 p = i->first; NodeMetadata *data = i->second; + if (data->empty()) + continue; u16 p16 = p.Z * MAP_BLOCKSIZE * MAP_BLOCKSIZE + p.Y * MAP_BLOCKSIZE + p.X; writeU16(os, p16); @@ -200,6 +206,17 @@ void NodeMetadataList::clear() m_data.clear(); } +int NodeMetadataList::countNonEmpty() const +{ + int n = 0; + std::map<v3s16, NodeMetadata*>::const_iterator it; + for (it = m_data.begin(); it != m_data.end(); ++it) { + if (!it->second->empty()) + n++; + } + return n; +} + std::string NodeMetadata::getString(const std::string &name, unsigned short recursion) const { diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 8d1298212..da1bf595d 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -47,6 +47,7 @@ public: void deSerialize(std::istream &is); void clear(); + bool empty() const; // Generic key/value store std::string getString(const std::string &name, unsigned short recursion = 0) const; @@ -94,6 +95,8 @@ public: void clear(); private: + int countNonEmpty() const; + std::map<v3s16, NodeMetadata *> m_data; }; diff --git a/src/nodetimer.cpp b/src/nodetimer.cpp index 350940546..003d08782 100644 --- a/src/nodetimer.cpp +++ b/src/nodetimer.cpp @@ -47,36 +47,38 @@ void NodeTimerList::serialize(std::ostream &os, u8 map_format_version) const { if (map_format_version == 24) { // Version 0 is a placeholder for "nothing to see here; go away." - if (m_data.empty()) { + if (m_timers.empty()) { writeU8(os, 0); // version return; } writeU8(os, 1); // version - writeU16(os, m_data.size()); + writeU16(os, m_timers.size()); } if (map_format_version >= 25) { writeU8(os, 2 + 4 + 4); // length of the data for a single timer - writeU16(os, m_data.size()); + writeU16(os, m_timers.size()); } - for (std::map<v3s16, NodeTimer>::const_iterator - i = m_data.begin(); - i != m_data.end(); ++i) { - v3s16 p = i->first; + for (std::multimap<double, NodeTimer>::const_iterator + i = m_timers.begin(); + i != m_timers.end(); ++i) { NodeTimer t = i->second; + NodeTimer nt = NodeTimer(t.timeout, + t.timeout - (f32)(i->first - m_time), t.position); + v3s16 p = t.position; u16 p16 = p.Z * MAP_BLOCKSIZE * MAP_BLOCKSIZE + p.Y * MAP_BLOCKSIZE + p.X; writeU16(os, p16); - t.serialize(os); + nt.serialize(os); } } void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) { - m_data.clear(); + clear(); - if(map_format_version == 24){ + if (map_format_version == 24) { u8 timer_version = readU8(is); if(timer_version == 0) return; @@ -84,7 +86,7 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) throw SerializationError("unsupported NodeTimerList version"); } - if(map_format_version >= 25){ + if (map_format_version >= 25) { u8 timer_data_len = readU8(is); if(timer_data_len != 2+4+4) throw SerializationError("unsupported NodeTimer data length"); @@ -92,8 +94,7 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) u16 count = readU16(is); - for(u16 i=0; i<count; i++) - { + for (u16 i = 0; i < count; i++) { u16 p16 = readU16(is); v3s16 p; @@ -103,11 +104,10 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) p16 &= MAP_BLOCKSIZE - 1; p.X = p16; - NodeTimer t; + NodeTimer t(p); t.deSerialize(is); - if(t.timeout <= 0) - { + if (t.timeout <= 0) { warningstream<<"NodeTimerList::deSerialize(): " <<"invalid data at position" <<"("<<p.X<<","<<p.Y<<","<<p.Z<<"): Ignoring." @@ -115,8 +115,7 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) continue; } - if(m_data.find(p) != m_data.end()) - { + if (m_iterators.find(p) != m_iterators.end()) { warningstream<<"NodeTimerList::deSerialize(): " <<"already set data at position" <<"("<<p.X<<","<<p.Y<<","<<p.Z<<"): Ignoring." @@ -124,31 +123,30 @@ void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version) continue; } - m_data.insert(std::make_pair(p, t)); + insert(t); } } -std::map<v3s16, NodeTimer> NodeTimerList::step(float dtime) +std::vector<NodeTimer> NodeTimerList::step(float dtime) { - std::map<v3s16, NodeTimer> elapsed_timers; - // Increment timers - for(std::map<v3s16, NodeTimer>::iterator - i = m_data.begin(); - i != m_data.end(); ++i){ - v3s16 p = i->first; + std::vector<NodeTimer> elapsed_timers; + m_time += dtime; + if (m_next_trigger_time == -1. || m_time < m_next_trigger_time) { + return elapsed_timers; + } + std::multimap<double, NodeTimer>::iterator i = m_timers.begin(); + // Process timers + for (; i != m_timers.end() && i->first <= m_time; ++i) { NodeTimer t = i->second; - t.elapsed += dtime; - if(t.elapsed >= t.timeout) - elapsed_timers.insert(std::make_pair(p, t)); - else - i->second = t; + t.elapsed = t.timeout + (f32)(m_time - i->first); + elapsed_timers.push_back(t); + m_iterators.erase(t.position); } // Delete elapsed timers - for(std::map<v3s16, NodeTimer>::const_iterator - i = elapsed_timers.begin(); - i != elapsed_timers.end(); ++i){ - v3s16 p = i->first; - m_data.erase(p); - } + m_timers.erase(m_timers.begin(), i); + if (m_timers.empty()) + m_next_trigger_time = -1.; + else + m_next_trigger_time = m_timers.begin()->first; return elapsed_timers; } diff --git a/src/nodetimer.h b/src/nodetimer.h index 9fb56edec..0fd43b2a8 100644 --- a/src/nodetimer.h +++ b/src/nodetimer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_v3d.h" #include <iostream> #include <map> +#include <vector> /* NodeTimer provides per-node timed callback functionality. @@ -36,8 +37,10 @@ class NodeTimer { public: NodeTimer(): timeout(0.), elapsed(0.) {} - NodeTimer(f32 timeout_, f32 elapsed_): - timeout(timeout_), elapsed(elapsed_) {} + NodeTimer(const v3s16 &position_): + timeout(0.), elapsed(0.), position(position_) {} + NodeTimer(f32 timeout_, f32 elapsed_, v3s16 position_): + timeout(timeout_), elapsed(elapsed_), position(position_) {} ~NodeTimer() {} void serialize(std::ostream &os) const; @@ -45,6 +48,7 @@ public: f32 timeout; f32 elapsed; + v3s16 position; }; /* @@ -54,37 +58,78 @@ public: class NodeTimerList { public: - NodeTimerList() {} + NodeTimerList(): m_next_trigger_time(-1.), m_time(0.) {} ~NodeTimerList() {} void serialize(std::ostream &os, u8 map_format_version) const; void deSerialize(std::istream &is, u8 map_format_version); // Get timer - NodeTimer get(v3s16 p){ - std::map<v3s16, NodeTimer>::iterator n = m_data.find(p); - if(n == m_data.end()) + NodeTimer get(const v3s16 &p) { + std::map<v3s16, std::multimap<double, NodeTimer>::iterator>::iterator n = + m_iterators.find(p); + if (n == m_iterators.end()) return NodeTimer(); - return n->second; + NodeTimer t = n->second->second; + t.elapsed = t.timeout - (n->second->first - m_time); + return t; } // Deletes timer - void remove(v3s16 p){ - m_data.erase(p); + void remove(v3s16 p) { + std::map<v3s16, std::multimap<double, NodeTimer>::iterator>::iterator n = + m_iterators.find(p); + if(n != m_iterators.end()) { + double removed_time = n->second->first; + m_timers.erase(n->second); + m_iterators.erase(n); + // Yes, this is float equality, but it is not a problem + // since we only test equality of floats as an ordered type + // and thus we never lose precision + if (removed_time == m_next_trigger_time) { + if (m_timers.empty()) + m_next_trigger_time = -1.; + else + m_next_trigger_time = m_timers.begin()->first; + } + } + } + // Undefined behaviour if there already is a timer + void insert(NodeTimer timer) { + v3s16 p = timer.position; + double trigger_time = m_time + (double)(timer.timeout - timer.elapsed); + std::multimap<double, NodeTimer>::iterator it = + m_timers.insert(std::pair<double, NodeTimer>( + trigger_time, timer + )); + m_iterators.insert( + std::pair<v3s16, std::multimap<double, NodeTimer>::iterator>(p, it)); + if (m_next_trigger_time == -1. || trigger_time < m_next_trigger_time) + m_next_trigger_time = trigger_time; } // Deletes old timer and sets a new one - void set(v3s16 p, NodeTimer t){ - m_data[p] = t; + inline void set(const NodeTimer &timer) { + remove(timer.position); + insert(timer); } // Deletes all timers - void clear(){ - m_data.clear(); + void clear() { + m_timers.clear(); + m_iterators.clear(); + m_next_trigger_time = -1.; + } + + inline double getNextTriggerTime() { + return m_next_trigger_time; } - // A step in time. Returns map of elapsed timers. - std::map<v3s16, NodeTimer> step(float dtime); + // Move forward in time, returns elapsed timers + std::vector<NodeTimer> step(float dtime); private: - std::map<v3s16, NodeTimer> m_data; + std::multimap<double, NodeTimer> m_timers; + std::map<v3s16, std::multimap<double, NodeTimer>::iterator> m_iterators; + double m_next_trigger_time; + double m_time; }; #endif diff --git a/src/noise.cpp b/src/noise.cpp index 2ddc3926f..b918c9936 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -93,22 +93,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 threshold. - In our example, threshold == 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. + This is an optimization of the expression: + 0x100000000ull % bound + since 64-bit modulo operations typically much slower than 32. */ u32 threshold = -bound % bound; u32 r; + /* + 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 threshold. + + In our example, threshold == 4 % 3 == 1, so reject values < 1 + (that is, 0), thus making the range == 3 with no bias. + + This loop may look dangerous, but will always terminate due to the + RNG's property of uniformity. + */ while ((r = next()) < threshold) ; @@ -156,7 +165,7 @@ s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials) /////////////////////////////////////////////////////////////////////////////// -float noise2d(int x, int y, int seed) +float noise2d(int x, int y, s32 seed) { unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_SEED * seed) & 0x7fffffff; @@ -166,7 +175,7 @@ float noise2d(int x, int y, int seed) } -float noise3d(int x, int y, int z, int seed) +float noise3d(int x, int y, int z, s32 seed) { unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_Z * z + NOISE_MAGIC_SEED * seed) & 0x7fffffff; @@ -235,7 +244,7 @@ float triLinearInterpolationNoEase( return linearInterpolation(u, v, z); } -float noise2d_gradient(float x, float y, int seed, bool eased) +float noise2d_gradient(float x, float y, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); @@ -256,7 +265,7 @@ float noise2d_gradient(float x, float y, int seed, bool eased) } -float noise3d_gradient(float x, float y, float z, int seed, bool eased) +float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); @@ -290,7 +299,7 @@ float noise3d_gradient(float x, float y, float z, int seed, bool eased) } -float noise2d_perlin(float x, float y, int seed, +float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; @@ -306,7 +315,7 @@ float noise2d_perlin(float x, float y, int seed, } -float noise2d_perlin_abs(float x, float y, int seed, +float noise2d_perlin_abs(float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; @@ -321,7 +330,7 @@ float noise2d_perlin_abs(float x, float y, int seed, } -float noise3d_perlin(float x, float y, float z, int seed, +float noise3d_perlin(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased) { float a = 0; @@ -336,7 +345,7 @@ float noise3d_perlin(float x, float y, float z, int seed, } -float noise3d_perlin_abs(float x, float y, float z, int seed, +float noise3d_perlin_abs(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased) { float a = 0; @@ -363,7 +372,7 @@ float contour(float v) ///////////////////////// [ New noise ] //////////////////////////// -float NoisePerlin2D(NoiseParams *np, float x, float y, int seed) +float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed) { float a = 0; float f = 1.0; @@ -389,7 +398,7 @@ float NoisePerlin2D(NoiseParams *np, float x, float y, int seed) } -float NoisePerlin3D(NoiseParams *np, float x, float y, float z, int seed) +float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) { float a = 0; float f = 1.0; @@ -416,7 +425,7 @@ float NoisePerlin3D(NoiseParams *np, float x, float y, float z, int seed) } -Noise::Noise(NoiseParams *np_, int seed, u32 sx, u32 sy, u32 sz) +Noise::Noise(NoiseParams *np_, s32 seed, u32 sx, u32 sy, u32 sz) { memcpy(&np, np_, sizeof(np)); this->seed = seed; @@ -543,7 +552,7 @@ void Noise::resizeNoiseBuf(bool is3d) void Noise::gradientMap2D( float x, float y, float step_x, float step_y, - int seed) + s32 seed) { float v00, v01, v10, v11, u, v, orig_u; u32 index, i, j, noisex, noisey; @@ -607,7 +616,7 @@ void Noise::gradientMap2D( void Noise::gradientMap3D( float x, float y, float z, float step_x, float step_y, float step_z, - int seed) + s32 seed) { float v000, v010, v100, v110; float v001, v011, v101, v111; diff --git a/src/noise.h b/src/noise.h index 0e4252dd4..41b93ae01 100644 --- a/src/noise.h +++ b/src/noise.h @@ -148,7 +148,7 @@ struct NoiseParams { class Noise { public: NoiseParams np; - int seed; + s32 seed; u32 sx; u32 sy; u32 sz; @@ -157,7 +157,7 @@ public: float *persist_buf; float *result; - Noise(NoiseParams *np, int seed, u32 sx, u32 sy, u32 sz=1); + Noise(NoiseParams *np, s32 seed, u32 sx, u32 sy, u32 sz=1); ~Noise(); void setSize(u32 sx, u32 sy, u32 sz=1); @@ -167,11 +167,11 @@ public: void gradientMap2D( float x, float y, float step_x, float step_y, - int seed); + s32 seed); void gradientMap3D( float x, float y, float z, float step_x, float step_y, float step_z, - int seed); + s32 seed); float *perlinMap2D(float x, float y, float *persistence_map=NULL); float *perlinMap3D(float x, float y, float z, float *persistence_map=NULL); @@ -202,11 +202,11 @@ private: }; -float NoisePerlin2D(NoiseParams *np, float x, float y, int seed); -float NoisePerlin3D(NoiseParams *np, float x, float y, float z, int seed); +float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed); +float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed); inline float NoisePerlin2D_PO(NoiseParams *np, float x, float xoff, - float y, float yoff, int seed) + float y, float yoff, s32 seed) { return NoisePerlin2D(np, x + xoff * np->spread.X, @@ -215,7 +215,7 @@ inline float NoisePerlin2D_PO(NoiseParams *np, float x, float xoff, } inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, - float y, float yoff, float z, float zoff, int seed) + float y, float yoff, float z, float zoff, s32 seed) { return NoisePerlin3D(np, x + xoff * np->spread.X, @@ -225,22 +225,22 @@ inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, } // Return value: -1 ... 1 -float noise2d(int x, int y, int seed); -float noise3d(int x, int y, int z, int seed); +float noise2d(int x, int y, s32 seed); +float noise3d(int x, int y, int z, s32 seed); -float noise2d_gradient(float x, float y, int seed, bool eased=true); -float noise3d_gradient(float x, float y, float z, int seed, bool eased=false); +float noise2d_gradient(float x, float y, s32 seed, bool eased=true); +float noise3d_gradient(float x, float y, float z, s32 seed, bool eased=false); -float noise2d_perlin(float x, float y, int seed, +float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, bool eased=true); -float noise2d_perlin_abs(float x, float y, int seed, +float noise2d_perlin_abs(float x, float y, s32 seed, int octaves, float persistence, bool eased=true); -float noise3d_perlin(float x, float y, float z, int seed, +float noise3d_perlin(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased=false); -float noise3d_perlin_abs(float x, float y, float z, int seed, +float noise3d_perlin_abs(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased=false); inline float easeCurve(float t) diff --git a/src/particles.cpp b/src/particles.cpp index 525258a25..acf9cc815 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -54,6 +54,7 @@ Particle::Particle( float expirationtime, float size, bool collisiondetection, + bool collision_removal, bool vertical, video::ITexture *texture, v2f texpos, @@ -85,6 +86,7 @@ Particle::Particle( m_player = player; m_size = size; m_collisiondetection = collisiondetection; + m_collision_removal = collision_removal; m_vertical = vertical; // Irrlicht stuff @@ -126,20 +128,21 @@ void Particle::render() void Particle::step(float dtime) { m_time += dtime; - if (m_collisiondetection) - { + if (m_collisiondetection) { aabb3f box = m_collisionbox; - v3f p_pos = m_pos*BS; - v3f p_velocity = m_velocity*BS; - collisionMoveSimple(m_env, m_gamedef, - BS*0.5, box, - 0, dtime, - &p_pos, &p_velocity, m_acceleration * BS); - m_pos = p_pos/BS; - m_velocity = p_velocity/BS; - } - else - { + v3f p_pos = m_pos * BS; + v3f p_velocity = m_velocity * BS; + collisionMoveResult r = collisionMoveSimple(m_env, + m_gamedef, BS * 0.5, box, 0, dtime, &p_pos, + &p_velocity, m_acceleration * BS); + if (m_collision_removal && r.collides) { + // force expiration of the particle + m_expiration = -1.0; + } else { + m_pos = p_pos / BS; + m_velocity = p_velocity / BS; + } + } else { m_velocity += m_acceleration * dtime; m_pos += m_velocity * dtime; } @@ -210,8 +213,8 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, u16 amount, float time, v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, - bool collisiondetection, bool vertical, video::ITexture *texture, u32 id, - ParticleManager *p_manager) : + bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical, + video::ITexture *texture, u32 id, ParticleManager *p_manager) : m_particlemanager(p_manager) { m_gamedef = gamedef; @@ -230,6 +233,8 @@ ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, m_minsize = minsize; m_maxsize = maxsize; m_collisiondetection = collisiondetection; + m_collision_removal = collision_removal; + m_attached_id = attached_id; m_vertical = vertical; m_texture = texture; m_time = 0; @@ -247,6 +252,20 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) { m_time += dtime; + bool unloaded = false; + bool is_attached = false; + v3f attached_pos = v3f(0,0,0); + float attached_yaw = 0; + if (m_attached_id != 0) { + if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) { + attached_pos = attached->getPosition() / BS; + attached_yaw = attached->getYaw(); + is_attached = true; + } else { + unloaded = true; + } + } + if (m_spawntime != 0) // Spawner exists for a predefined timespan { for(std::vector<float>::iterator i = m_spawntimes.begin(); @@ -256,32 +275,47 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) { m_amount--; - v3f pos = random_v3f(m_minpos, m_maxpos); - v3f vel = random_v3f(m_minvel, m_maxvel); - v3f acc = random_v3f(m_minacc, m_maxacc); - float exptime = rand()/(float)RAND_MAX - *(m_maxexptime-m_minexptime) - +m_minexptime; - float size = rand()/(float)RAND_MAX - *(m_maxsize-m_minsize) - +m_minsize; - - Particle* toadd = new Particle( - m_gamedef, - m_smgr, - m_player, - env, - pos, - vel, - acc, - exptime, - size, - m_collisiondetection, - m_vertical, - m_texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0)); - m_particlemanager->addParticle(toadd); + // Pretend to, but don't actually spawn a + // particle if it is attached to an unloaded + // object. + if (!unloaded) { + v3f pos = random_v3f(m_minpos, m_maxpos); + v3f vel = random_v3f(m_minvel, m_maxvel); + v3f acc = random_v3f(m_minacc, m_maxacc); + + if (is_attached) { + // Apply attachment yaw and position + pos.rotateXZBy(attached_yaw); + pos += attached_pos; + vel.rotateXZBy(attached_yaw); + acc.rotateXZBy(attached_yaw); + } + + float exptime = rand()/(float)RAND_MAX + *(m_maxexptime-m_minexptime) + +m_minexptime; + float size = rand()/(float)RAND_MAX + *(m_maxsize-m_minsize) + +m_minsize; + + Particle* toadd = new Particle( + m_gamedef, + m_smgr, + m_player, + env, + pos, + vel, + acc, + exptime, + size, + m_collisiondetection, + m_collision_removal, + m_vertical, + m_texture, + v2f(0.0, 0.0), + v2f(1.0, 1.0)); + m_particlemanager->addParticle(toadd); + } i = m_spawntimes.erase(i); } else @@ -292,6 +326,9 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) } else // Spawner exists for an infinity timespan, spawn on a per-second base { + // Skip this step if attached to an unloaded object + if (unloaded) + return; for (int i = 0; i <= m_amount; i++) { if (rand()/(float)RAND_MAX < dtime) @@ -299,6 +336,15 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) v3f pos = random_v3f(m_minpos, m_maxpos); v3f vel = random_v3f(m_minvel, m_maxvel); v3f acc = random_v3f(m_minacc, m_maxacc); + + if (is_attached) { + // Apply attachment yaw and position + pos.rotateXZBy(attached_yaw); + pos += attached_pos; + vel.rotateXZBy(attached_yaw); + acc.rotateXZBy(attached_yaw); + } + float exptime = rand()/(float)RAND_MAX *(m_maxexptime-m_minexptime) +m_minexptime; @@ -317,6 +363,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) exptime, size, m_collisiondetection, + m_collision_removal, m_vertical, m_texture, v2f(0.0, 0.0), @@ -446,6 +493,8 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef, event->add_particlespawner.minsize, event->add_particlespawner.maxsize, event->add_particlespawner.collisiondetection, + event->add_particlespawner.collision_removal, + event->add_particlespawner.attached_id, event->add_particlespawner.vertical, texture, event->add_particlespawner.id, @@ -480,6 +529,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef, event->spawn_particle.expirationtime, event->spawn_particle.size, event->spawn_particle.collisiondetection, + event->spawn_particle.collision_removal, event->spawn_particle.vertical, texture, v2f(0.0, 0.0), @@ -555,6 +605,7 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* s visual_size, true, false, + false, texture, texpos, texsize); diff --git a/src/particles.h b/src/particles.h index dda84385c..eb8c6665d 100644 --- a/src/particles.h +++ b/src/particles.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., struct ClientEvent; class ParticleManager; +class ClientEnvironment; class Particle : public scene::ISceneNode { @@ -45,6 +46,7 @@ class Particle : public scene::ISceneNode float expirationtime, float size, bool collisiondetection, + bool collision_removal, bool vertical, video::ITexture *texture, v2f texpos, @@ -97,6 +99,7 @@ private: float m_size; u8 m_light; bool m_collisiondetection; + bool m_collision_removal; bool m_vertical; v3s16 m_camera_offset; }; @@ -115,6 +118,8 @@ class ParticleSpawner float minexptime, float maxexptime, float minsize, float maxsize, bool collisiondetection, + bool collision_removal, + u16 attached_id, bool vertical, video::ITexture *texture, u32 id, @@ -148,8 +153,9 @@ class ParticleSpawner video::ITexture *m_texture; std::vector<float> m_spawntimes; bool m_collisiondetection; + bool m_collision_removal; bool m_vertical; - + u16 m_attached_id; }; /** diff --git a/src/player.cpp b/src/player.cpp index 5949712a5..85bc639ec 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -19,49 +19,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" -#include <fstream> #include "threading/mutex_auto_lock.h" #include "util/numeric.h" #include "hud.h" #include "constants.h" #include "gamedef.h" #include "settings.h" -#include "content_sao.h" -#include "filesys.h" #include "log.h" #include "porting.h" // strlcpy -Player::Player(IGameDef *gamedef, const char *name): - got_teleported(false), - touching_ground(false), - in_liquid(false), - in_liquid_stable(false), - liquid_viscosity(0), - is_climbing(false), - swimming_vertical(false), - camera_barely_in_ceiling(false), - inventory(gamedef->idef()), - hp(PLAYER_MAX_HP), - hurt_tilt_timer(0), - hurt_tilt_strength(0), - protocol_version(0), +Player::Player(const char *name, IItemDefManager *idef): + inventory(idef), peer_id(PEER_ID_INEXISTENT), keyPressed(0), // protected - m_gamedef(gamedef), - m_breath(PLAYER_MAX_BREATH), - m_pitch(0), - m_yaw(0), - m_speed(0,0,0), - m_position(0,0,0), - m_collisionbox(-BS*0.30,0.0,-BS*0.30,BS*0.30,BS*1.75,BS*0.30), - m_dirty(false) + m_speed(0,0,0) { strlcpy(m_name, name, PLAYERNAME_SIZE); inventory.clear(); inventory.addList("main", PLAYER_INVENTORY_SIZE); + inventory.addList("hand", 1); InventoryList *craft = inventory.addList("craft", 9); craft->setWidth(3); inventory.addList("craftpreview", 1); @@ -92,13 +71,6 @@ Player::Player(IGameDef *gamedef, const char *name): movement_gravity = 9.81 * BS; local_animation_speed = 0.0; - // Movement overrides are multipliers and must be 1 by default - physics_override_speed = 1; - physics_override_jump = 1; - physics_override_gravity = 1; - 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 | @@ -112,75 +84,6 @@ Player::~Player() clearHud(); } -v3s16 Player::getLightPosition() const -{ - return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); -} - -void Player::serialize(std::ostream &os) -{ - // Utilize a Settings object for storing values - Settings args; - args.setS32("version", 1); - args.set("name", m_name); - //args.set("password", m_password); - args.setFloat("pitch", m_pitch); - args.setFloat("yaw", m_yaw); - args.setV3F("position", m_position); - args.setS32("hp", hp); - args.setS32("breath", m_breath); - - args.writeLines(os); - - os<<"PlayerArgsEnd\n"; - - inventory.serialize(os); -} - -void Player::deSerialize(std::istream &is, std::string playername) -{ - Settings args; - - if (!args.parseConfigLines(is, "PlayerArgsEnd")) { - throw SerializationError("PlayerArgsEnd of player " + - playername + " not found!"); - } - - m_dirty = true; - //args.getS32("version"); // Version field value not used - std::string name = args.get("name"); - strlcpy(m_name, name.c_str(), PLAYERNAME_SIZE); - setPitch(args.getFloat("pitch")); - setYaw(args.getFloat("yaw")); - setPosition(args.getV3F("position")); - try{ - hp = args.getS32("hp"); - }catch(SettingNotFoundException &e) { - hp = PLAYER_MAX_HP; - } - try{ - m_breath = args.getS32("breath"); - }catch(SettingNotFoundException &e) { - m_breath = PLAYER_MAX_BREATH; - } - - inventory.deSerialize(is); - - if(inventory.getList("craftpreview") == NULL) { - // Convert players without craftpreview - inventory.addList("craftpreview", 1); - - bool craftresult_is_preview = true; - if(args.exists("craftresult_is_preview")) - craftresult_is_preview = args.getBool("craftresult_is_preview"); - if(craftresult_is_preview) - { - // Clear craftresult - inventory.getList("craftresult")->changeItem(0, ItemStack()); - } - } -} - u32 Player::addHud(HudElement *toadd) { MutexAutoLock lock(m_mutex); @@ -226,81 +129,3 @@ void Player::clearHud() hud.pop_back(); } } - -RemotePlayer::RemotePlayer(IGameDef *gamedef, const char *name): - Player(gamedef, name), - m_sao(NULL) -{ - movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS; - movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS; - movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS; - movement_speed_walk = g_settings->getFloat("movement_speed_walk") * BS; - movement_speed_crouch = g_settings->getFloat("movement_speed_crouch") * BS; - movement_speed_fast = g_settings->getFloat("movement_speed_fast") * BS; - movement_speed_climb = g_settings->getFloat("movement_speed_climb") * BS; - movement_speed_jump = g_settings->getFloat("movement_speed_jump") * BS; - movement_liquid_fluidity = g_settings->getFloat("movement_liquid_fluidity") * BS; - movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; - movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; - movement_gravity = g_settings->getFloat("movement_gravity") * BS; -} - -void RemotePlayer::save(std::string savedir) -{ - /* - * We have to open all possible player files in the players directory - * and check their player names because some file systems are not - * case-sensitive and player names are case-sensitive. - */ - - // A player to deserialize files into to check their names - RemotePlayer testplayer(m_gamedef, ""); - - savedir += DIR_DELIM; - std::string path = savedir + m_name; - for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { - if (!fs::PathExists(path)) { - // Open file and serialize - std::ostringstream ss(std::ios_base::binary); - serialize(ss); - if (!fs::safeWriteToFile(path, ss.str())) { - infostream << "Failed to write " << path << std::endl; - } - setModified(false); - return; - } - // Open file and deserialize - std::ifstream is(path.c_str(), std::ios_base::binary); - if (!is.good()) { - infostream << "Failed to open " << path << std::endl; - return; - } - testplayer.deSerialize(is, path); - is.close(); - if (strcmp(testplayer.getName(), m_name) == 0) { - // Open file and serialize - std::ostringstream ss(std::ios_base::binary); - serialize(ss); - if (!fs::safeWriteToFile(path, ss.str())) { - infostream << "Failed to write " << path << std::endl; - } - setModified(false); - return; - } - path = savedir + m_name + itos(i); - } - - infostream << "Didn't find free file for player " << m_name << std::endl; - return; -} - -/* - RemotePlayer -*/ -void RemotePlayer::setPosition(const v3f &position) -{ - Player::setPosition(position); - if(m_sao) - m_sao->setBasePosition(position); -} - diff --git a/src/player.h b/src/player.h index b317cda4f..5f9bb7ec9 100644 --- a/src/player.h +++ b/src/player.h @@ -46,7 +46,10 @@ struct PlayerControl RMB = false; pitch = 0; yaw = 0; + sidew_move_joystick_axis = .0f; + forw_move_joystick_axis = .0f; } + PlayerControl( bool a_up, bool a_down, @@ -55,10 +58,13 @@ struct PlayerControl bool a_jump, bool a_aux1, bool a_sneak, + bool a_zoom, bool a_LMB, bool a_RMB, float a_pitch, - float a_yaw + float a_yaw, + float a_sidew_move_joystick_axis, + float a_forw_move_joystick_axis ) { up = a_up; @@ -68,10 +74,13 @@ struct PlayerControl jump = a_jump; aux1 = a_aux1; sneak = a_sneak; + zoom = a_zoom; LMB = a_LMB; RMB = a_RMB; pitch = a_pitch; yaw = a_yaw; + sidew_move_joystick_axis = a_sidew_move_joystick_axis; + forw_move_joystick_axis = a_forw_move_joystick_axis; } bool up; bool down; @@ -80,16 +89,17 @@ struct PlayerControl bool jump; bool aux1; bool sneak; + bool zoom; bool LMB; bool RMB; float pitch; float yaw; + float sidew_move_joystick_axis; + float forw_move_joystick_axis; }; class Map; -class IGameDef; struct CollisionInfo; -class PlayerSAO; struct HudElement; class Environment; @@ -100,7 +110,7 @@ class Player { public: - Player(IGameDef *gamedef, const char *name); + Player(const char *name, IItemDefManager *idef); virtual ~Player() = 0; virtual void move(f32 dtime, Environment *env, f32 pos_max_d) @@ -119,88 +129,10 @@ public: m_speed = speed; } - v3f getPosition() - { - return m_position; - } - - v3s16 getLightPosition() const; - - v3f getEyeOffset() - { - float eye_height = camera_barely_in_ceiling ? 1.5f : 1.625f; - return v3f(0, BS * eye_height, 0); - } - - v3f getEyePosition() - { - return m_position + getEyeOffset(); - } - - virtual void setPosition(const v3f &position) - { - if (position != m_position) - m_dirty = true; - m_position = position; - } - - void setPitch(f32 pitch) - { - if (pitch != m_pitch) - m_dirty = true; - m_pitch = pitch; - } - - virtual void setYaw(f32 yaw) - { - if (yaw != m_yaw) - m_dirty = true; - m_yaw = yaw; - } - - f32 getPitch() - { - return m_pitch; - } - - f32 getYaw() - { - return m_yaw; - } - - u16 getBreath() - { - return m_breath; - } - - virtual void setBreath(u16 breath) - { - if (breath != m_breath) - m_dirty = true; - m_breath = breath; - } - - f32 getRadPitch() - { - return -1.0 * m_pitch * core::DEGTORAD; - } - - f32 getRadYaw() - { - return (m_yaw + 90.) * core::DEGTORAD; - } + const char *getName() const { return m_name; } - const char *getName() const + u32 getFreeHudID() { - return m_name; - } - - aabb3f getCollisionbox() - { - return m_collisionbox; - } - - u32 getFreeHudID() { size_t size = hud.size(); for (size_t i = 0; i != size; i++) { if (!hud[i]) @@ -209,126 +141,6 @@ 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; - } - - virtual PlayerSAO *getPlayerSAO() - { - return NULL; - } - - virtual void setPlayerSAO(PlayerSAO *sao) - { - FATAL_ERROR("FIXME"); - } - - /* - serialize() writes a bunch of text that can contain - any characters except a '\0', and such an ending that - deSerialize stops reading exactly at the right point. - */ - void serialize(std::ostream &os); - void deSerialize(std::istream &is, std::string playername); - - bool checkModified() const - { - return m_dirty || inventory.checkModified(); - } - - void setModified(const bool x) - { - m_dirty = x; - if (x == false) - inventory.setModified(x); - } - - // Use a function, if isDead can be defined by other conditions - bool isDead() { return hp == 0; } - - bool got_teleported; - bool touching_ground; - // This oscillates so that the player jumps a bit above the surface - bool in_liquid; - // This is more stable and defines the maximum speed of the player - bool in_liquid_stable; - // Gets the viscosity of water to calculate friction - u8 liquid_viscosity; - bool is_climbing; - bool swimming_vertical; - bool camera_barely_in_ceiling; v3f eye_offset_first; v3f eye_offset_third; @@ -347,67 +159,30 @@ public: f32 movement_liquid_sink; f32 movement_gravity; - float physics_override_speed; - float physics_override_jump; - float physics_override_gravity; - bool physics_override_sneak; - bool physics_override_sneak_glitch; - v2s32 local_animations[4]; float local_animation_speed; - u16 hp; - - float hurt_tilt_timer; - float hurt_tilt_strength; - - u16 protocol_version; u16 peer_id; std::string inventory_formspec; PlayerControl control; - PlayerControl getPlayerControl() - { - return control; - } + const PlayerControl& getPlayerControl() { return control; } u32 keyPressed; - HudElement* getHud(u32 id); u32 addHud(HudElement* hud); HudElement* removeHud(u32 id); void clearHud(); - u32 maxHudId() { - return hud.size(); - } u32 hud_flags; s32 hud_hotbar_itemcount; - std::string hud_hotbar_image; - std::string hud_hotbar_selected_image; protected: - IGameDef *m_gamedef; - char m_name[PLAYERNAME_SIZE]; - u16 m_breath; - f32 m_pitch; - f32 m_yaw; v3f m_speed; - v3f m_position; - aabb3f m_collisionbox; - - 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 @@ -415,27 +190,5 @@ private: Mutex m_mutex; }; - -/* - Player on the server -*/ -class RemotePlayer : public Player -{ -public: - RemotePlayer(IGameDef *gamedef, const char *name); - virtual ~RemotePlayer() {} - - void save(std::string savedir); - - PlayerSAO *getPlayerSAO() - { return m_sao; } - void setPlayerSAO(PlayerSAO *sao) - { m_sao = sao; } - void setPosition(const v3f &position); - -private: - PlayerSAO *m_sao; -}; - #endif diff --git a/src/porting.cpp b/src/porting.cpp index 98b85b7d0..023f0cca7 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -75,11 +75,16 @@ bool * signal_handler_killstatus(void) #if !defined(_WIN32) // POSIX #include <signal.h> -void sigint_handler(int sig) +void signal_handler(int sig) { if (!g_killed) { - dstream << "INFO: sigint_handler(): " - << "Ctrl-C pressed, shutting down." << std::endl; + if (sig == SIGINT) { + dstream << "INFO: signal_handler(): " + << "Ctrl-C pressed, shutting down." << std::endl; + } else if (sig == SIGTERM) { + dstream << "INFO: signal_handler(): " + << "got SIGTERM, shutting down." << std::endl; + } // Comment out for less clutter when testing scripts /*dstream << "INFO: sigint_handler(): " @@ -88,13 +93,14 @@ void sigint_handler(int sig) g_killed = true; } else { - (void)signal(SIGINT, SIG_DFL); + (void)signal(sig, SIG_DFL); } } void signal_handler_init(void) { - (void)signal(SIGINT, sigint_handler); + (void)signal(SIGINT, signal_handler); + (void)signal(SIGTERM, signal_handler); } #else // _WIN32 @@ -252,7 +258,7 @@ bool getCurrentExecPath(char *buf, size_t len) //// Linux -#elif defined(linux) || defined(__linux) || defined(__linux__) +#elif defined(__linux__) bool getCurrentExecPath(char *buf, size_t len) { @@ -368,7 +374,7 @@ bool setSystemPaths() //// Linux -#elif defined(linux) || defined(__linux) +#elif defined(__linux__) bool setSystemPaths() { @@ -605,6 +611,115 @@ void setXorgClassHint(const video::SExposedVideoData &video_data, #endif } +bool setXorgWindowIcon(IrrlichtDevice *device) +{ +#ifdef XORG_USED +# if RUN_IN_PLACE + return setXorgWindowIconFromPath(device, + path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png"); +# else + // We have semi-support for reading in-place data if we are + // compiled with RUN_IN_PLACE. Don't break with this and + // also try the path_share location. + return + setXorgWindowIconFromPath(device, + ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") || + setXorgWindowIconFromPath(device, + path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png"); +# endif +#else + return false; +#endif +} + +bool setXorgWindowIconFromPath(IrrlichtDevice *device, + const std::string &icon_file) +{ +#ifdef XORG_USED + + video::IVideoDriver *v_driver = device->getVideoDriver(); + + video::IImageLoader *image_loader = NULL; + u32 cnt = v_driver->getImageLoaderCount(); + for (u32 i = 0; i < cnt; i++) { + if (v_driver->getImageLoader(i)->isALoadableFileExtension(icon_file.c_str())) { + image_loader = v_driver->getImageLoader(i); + break; + } + } + + if (!image_loader) { + warningstream << "Could not find image loader for file '" + << icon_file << "'" << std::endl; + return false; + } + + io::IReadFile *icon_f = device->getFileSystem()->createAndOpenFile(icon_file.c_str()); + + if (!icon_f) { + warningstream << "Could not load icon file '" + << icon_file << "'" << std::endl; + return false; + } + + video::IImage *img = image_loader->loadImage(icon_f); + + if (!img) { + warningstream << "Could not load icon file '" + << icon_file << "'" << std::endl; + icon_f->drop(); + return false; + } + + u32 height = img->getDimension().Height; + u32 width = img->getDimension().Width; + + size_t icon_buffer_len = 2 + height * width; + long *icon_buffer = new long[icon_buffer_len]; + + icon_buffer[0] = width; + icon_buffer[1] = height; + + for (u32 x = 0; x < width; x++) { + for (u32 y = 0; y < height; y++) { + video::SColor col = img->getPixel(x, y); + long pixel_val = 0; + pixel_val |= (u8)col.getAlpha() << 24; + pixel_val |= (u8)col.getRed() << 16; + pixel_val |= (u8)col.getGreen() << 8; + pixel_val |= (u8)col.getBlue(); + icon_buffer[2 + x + y * width] = pixel_val; + } + } + + img->drop(); + icon_f->drop(); + + const video::SExposedVideoData &video_data = v_driver->getExposedVideoData(); + + Display *x11_dpl = (Display *)video_data.OpenGLLinux.X11Display; + + if (x11_dpl == NULL) { + warningstream << "Could not find x11 display for setting its icon." + << std::endl; + delete [] icon_buffer; + return false; + } + + Window x11_win = (Window)video_data.OpenGLLinux.X11Window; + + Atom net_wm_icon = XInternAtom(x11_dpl, "_NET_WM_ICON", False); + Atom cardinal = XInternAtom(x11_dpl, "CARDINAL", False); + XChangeProperty(x11_dpl, x11_win, + net_wm_icon, cardinal, 32, + PropModeReplace, (const unsigned char *)icon_buffer, + icon_buffer_len); + + delete [] icon_buffer; + +#endif + return true; +} //// //// Video/Display Information (Client-only) diff --git a/src/porting.h b/src/porting.h index 4d51c5058..f5c7efcb2 100644 --- a/src/porting.h +++ b/src/porting.h @@ -60,7 +60,11 @@ 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) + // Use standard Posix macro for Linux + #if (defined(linux) || defined(__linux)) && !defined(__linux__) + #define __linux__ + #endif + #if (defined(__linux__) || defined(__GNU__)) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif @@ -321,7 +325,7 @@ inline const char *getPlatformName() return #if defined(ANDROID) "Android" -#elif defined(linux) || defined(__linux) || defined(__linux__) +#elif defined(__linux__) "Linux" #elif defined(_WIN32) || defined(_WIN64) "Windows" @@ -363,6 +367,11 @@ inline const char *getPlatformName() void setXorgClassHint(const video::SExposedVideoData &video_data, const std::string &name); +bool setXorgWindowIcon(IrrlichtDevice *device); + +bool setXorgWindowIconFromPath(IrrlichtDevice *device, + const std::string &icon_file); + // This only needs to be called at the start of execution, since all future // threads in the process inherit this exception handler void setWin32ExceptionHandler(); diff --git a/src/reflowscan.cpp b/src/reflowscan.cpp new file mode 100644 index 000000000..49b9406d7 --- /dev/null +++ b/src/reflowscan.cpp @@ -0,0 +1,206 @@ +/* +Minetest +Copyright (C) 2016 MillersMan <millersman@users.noreply.github.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 "reflowscan.h" +#include "map.h" +#include "mapblock.h" +#include "nodedef.h" +#include "util/timetaker.h" + + +ReflowScan::ReflowScan(Map *map, INodeDefManager *ndef) : + m_map(map), + m_ndef(ndef), + m_liquid_queue(NULL) +{ +} + +void ReflowScan::scan(MapBlock *block, UniqueQueue<v3s16> *liquid_queue) +{ + m_block_pos = block->getPos(); + m_rel_block_pos = block->getPosRelative(); + m_liquid_queue = liquid_queue; + + // Prepare the lookup which is a 3x3x3 array of the blocks surrounding the + // scanned block. Blocks are only added to the lookup if they are really + // needed. The lookup is indexed manually to use the same index in a + // bit-array (of uint32 type) which stores for unloaded blocks that they + // were already fetched from Map but were actually NULL. + memset(m_lookup, 0, sizeof(m_lookup)); + int block_idx = 1 + (1 * 9) + (1 * 3); + m_lookup[block_idx] = block; + m_lookup_state_bitset = 1 << block_idx; + + // Scan the columns in the block + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + scanColumn(x, z); + } + + // Scan neighbouring columns from the nearby blocks as they might contain + // liquid nodes that weren't allowed to flow to prevent gaps. + for (s16 i = 0; i < MAP_BLOCKSIZE; i++) { + scanColumn(i, -1); + scanColumn(i, MAP_BLOCKSIZE); + scanColumn(-1, i); + scanColumn(MAP_BLOCKSIZE, i); + } +} + +inline MapBlock *ReflowScan::lookupBlock(int x, int y, int z) +{ + // Gets the block that contains (x,y,z) relativ to the scanned block. + // This uses a lookup as there might be many lookups into the same + // neighbouring block which makes fetches from Map costly. + int bx = (MAP_BLOCKSIZE + x) / MAP_BLOCKSIZE; + int by = (MAP_BLOCKSIZE + y) / MAP_BLOCKSIZE; + int bz = (MAP_BLOCKSIZE + z) / MAP_BLOCKSIZE; + int idx = (bx + (by * 9) + (bz * 3)); + MapBlock *result = m_lookup[idx]; + if (!result && (m_lookup_state_bitset & (1 << idx)) == 0) { + // The block wasn't requested yet so fetch it from Map and store it + // in the lookup + v3s16 pos = m_block_pos + v3s16(bx - 1, by - 1, bz - 1); + m_lookup[idx] = result = m_map->getBlockNoCreateNoEx(pos); + m_lookup_state_bitset |= (1 << idx); + } + return result; +} + +inline bool ReflowScan::isLiquidFlowableTo(int x, int y, int z) +{ + // Tests whether (x,y,z) is a node to which liquid might flow. + bool valid_position; + MapBlock *block = lookupBlock(x, y, z); + if (block) { + int dx = (MAP_BLOCKSIZE + x) % MAP_BLOCKSIZE; + int dy = (MAP_BLOCKSIZE + y) % MAP_BLOCKSIZE; + int dz = (MAP_BLOCKSIZE + z) % MAP_BLOCKSIZE; + MapNode node = block->getNodeNoCheck(dx, dy, dz, &valid_position); + if (node.getContent() != CONTENT_IGNORE) { + const ContentFeatures &f = m_ndef->get(node); + // NOTE: No need to check for flowing nodes with lower liquid level + // as they should only occur on top of other columns where they + // will be added to the queue themselves. + return f.floodable; + } + } + return false; +} + +inline bool ReflowScan::isLiquidHorizontallyFlowable(int x, int y, int z) +{ + // Check if the (x,y,z) might spread to one of the horizontally + // neighbouring nodes + return isLiquidFlowableTo(x - 1, y, z) || + isLiquidFlowableTo(x + 1, y, z) || + isLiquidFlowableTo(x, y, z - 1) || + isLiquidFlowableTo(x, y, z + 1); +} + +void ReflowScan::scanColumn(int x, int z) +{ + bool valid_position; + + // Is the column inside a loaded block? + MapBlock *block = lookupBlock(x, 0, z); + if (!block) + return; + + MapBlock *above = lookupBlock(x, MAP_BLOCKSIZE, z); + int dx = (MAP_BLOCKSIZE + x) % MAP_BLOCKSIZE; + int dz = (MAP_BLOCKSIZE + z) % MAP_BLOCKSIZE; + + // Get the state from the node above the scanned block + bool was_ignore, was_liquid; + if (above) { + MapNode node = above->getNodeNoCheck(dx, 0, dz, &valid_position); + was_ignore = node.getContent() == CONTENT_IGNORE; + was_liquid = m_ndef->get(node).isLiquid(); + } else { + was_ignore = true; + was_liquid = false; + } + bool was_checked = false; + bool was_pushed = false; + + // Scan through the whole block + for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { + MapNode node = block->getNodeNoCheck(dx, y, dz, &valid_position); + const ContentFeatures &f = m_ndef->get(node); + bool is_ignore = node.getContent() == CONTENT_IGNORE; + bool is_liquid = f.isLiquid(); + + if (is_ignore || was_ignore || is_liquid == was_liquid) { + // Neither topmost node of liquid column nor topmost node below column + was_checked = false; + was_pushed = false; + } else if (is_liquid) { + // This is the topmost node in the column + bool is_pushed = false; + if (f.liquid_type == LIQUID_FLOWING || + isLiquidHorizontallyFlowable(x, y, z)) { + m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y, z)); + is_pushed = true; + } + // Remember waschecked and waspushed to avoid repeated + // checks/pushes in case the column consists of only this node + was_checked = true; + was_pushed = is_pushed; + } else { + // This is the topmost node below a liquid column + if (!was_pushed && (f.floodable || + (!was_checked && isLiquidHorizontallyFlowable(x, y + 1, z)))) { + // Activate the lowest node in the column which is one + // node above this one + m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y + 1, z)); + } + } + + was_liquid = is_liquid; + was_ignore = is_ignore; + } + + // Check the node below the current block + MapBlock *below = lookupBlock(x, -1, z); + if (below) { + MapNode node = below->getNodeNoCheck(dx, MAP_BLOCKSIZE - 1, dz, &valid_position); + const ContentFeatures &f = m_ndef->get(node); + bool is_ignore = node.getContent() == CONTENT_IGNORE; + bool is_liquid = f.isLiquid(); + + if (is_ignore || was_ignore || is_liquid == was_liquid) { + // Neither topmost node of liquid column nor topmost node below column + } else if (is_liquid) { + // This is the topmost node in the column and might want to flow away + if (f.liquid_type == LIQUID_FLOWING || + isLiquidHorizontallyFlowable(x, -1, z)) { + m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, -1, z)); + } + } else { + // This is the topmost node below a liquid column + if (!was_pushed && (f.floodable || + (!was_checked && isLiquidHorizontallyFlowable(x, 0, z)))) { + // Activate the lowest node in the column which is one + // node above this one + m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, 0, z)); + } + } + } +} diff --git a/src/reflowscan.h b/src/reflowscan.h new file mode 100644 index 000000000..a6d289ad5 --- /dev/null +++ b/src/reflowscan.h @@ -0,0 +1,50 @@ +/* +Minetest +Copyright (C) 2016 MillersMan <millersman@users.noreply.github.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 REFLOWSCAN_H +#define REFLOWSCAN_H + +#include "util/container.h" +#include "irrlichttypes_bloated.h" + +class INodeDefManager; +class Map; +class MapBlock; + +class ReflowScan { +public: + ReflowScan(Map *map, INodeDefManager *ndef); + void scan(MapBlock *block, UniqueQueue<v3s16> *liquid_queue); + +private: + MapBlock *lookupBlock(int x, int y, int z); + bool isLiquidFlowableTo(int x, int y, int z); + bool isLiquidHorizontallyFlowable(int x, int y, int z); + void scanColumn(int x, int z); + +private: + Map *m_map; + INodeDefManager *m_ndef; + v3s16 m_block_pos, m_rel_block_pos; + UniqueQueue<v3s16> *m_liquid_queue; + MapBlock *m_lookup[3 * 3 * 3]; + u32 m_lookup_state_bitset; +}; + +#endif // REFLOWSCAN_H diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp new file mode 100644 index 000000000..67ab89113 --- /dev/null +++ b/src/remoteplayer.cpp @@ -0,0 +1,232 @@ +/* +Minetest +Copyright (C) 2010-2016 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2014-2016 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 "remoteplayer.h" +#include "content_sao.h" +#include "filesys.h" +#include "gamedef.h" +#include "porting.h" // strlcpy +#include "settings.h" + + +/* + RemotePlayer +*/ +// static config cache for remoteplayer +bool RemotePlayer::m_setting_cache_loaded = false; +float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f; +u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0; + +RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): + Player(name, idef), + protocol_version(0), + m_sao(NULL), + m_dirty(false), + m_last_chat_message_sent(time(NULL)), + m_chat_message_allowance(5.0f), + m_message_rate_overhead(0), + hud_hotbar_image(""), + hud_hotbar_selected_image("") +{ + if (!RemotePlayer::m_setting_cache_loaded) { + RemotePlayer::m_setting_chat_message_limit_per_10sec = + g_settings->getFloat("chat_message_limit_per_10sec"); + RemotePlayer::m_setting_chat_message_limit_trigger_kick = + g_settings->getU16("chat_message_limit_trigger_kick"); + RemotePlayer::m_setting_cache_loaded = true; + } + movement_acceleration_default = g_settings->getFloat("movement_acceleration_default") * BS; + movement_acceleration_air = g_settings->getFloat("movement_acceleration_air") * BS; + movement_acceleration_fast = g_settings->getFloat("movement_acceleration_fast") * BS; + movement_speed_walk = g_settings->getFloat("movement_speed_walk") * BS; + movement_speed_crouch = g_settings->getFloat("movement_speed_crouch") * BS; + movement_speed_fast = g_settings->getFloat("movement_speed_fast") * BS; + movement_speed_climb = g_settings->getFloat("movement_speed_climb") * BS; + movement_speed_jump = g_settings->getFloat("movement_speed_jump") * BS; + movement_liquid_fluidity = g_settings->getFloat("movement_liquid_fluidity") * BS; + movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS; + movement_liquid_sink = g_settings->getFloat("movement_liquid_sink") * BS; + movement_gravity = g_settings->getFloat("movement_gravity") * BS; +} + +void RemotePlayer::save(std::string savedir, IGameDef *gamedef) +{ + /* + * We have to open all possible player files in the players directory + * and check their player names because some file systems are not + * case-sensitive and player names are case-sensitive. + */ + + // A player to deserialize files into to check their names + RemotePlayer testplayer("", gamedef->idef()); + + savedir += DIR_DELIM; + std::string path = savedir + m_name; + for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { + if (!fs::PathExists(path)) { + // Open file and serialize + std::ostringstream ss(std::ios_base::binary); + serialize(ss); + if (!fs::safeWriteToFile(path, ss.str())) { + infostream << "Failed to write " << path << std::endl; + } + setModified(false); + return; + } + // Open file and deserialize + std::ifstream is(path.c_str(), std::ios_base::binary); + if (!is.good()) { + infostream << "Failed to open " << path << std::endl; + return; + } + testplayer.deSerialize(is, path, NULL); + is.close(); + if (strcmp(testplayer.getName(), m_name) == 0) { + // Open file and serialize + std::ostringstream ss(std::ios_base::binary); + serialize(ss); + if (!fs::safeWriteToFile(path, ss.str())) { + infostream << "Failed to write " << path << std::endl; + } + setModified(false); + return; + } + path = savedir + m_name + itos(i); + } + + infostream << "Didn't find free file for player " << m_name << std::endl; + return; +} + +void RemotePlayer::deSerialize(std::istream &is, const std::string &playername, + PlayerSAO *sao) +{ + Settings args; + + if (!args.parseConfigLines(is, "PlayerArgsEnd")) { + throw SerializationError("PlayerArgsEnd of player " + playername + " not found!"); + } + + m_dirty = true; + //args.getS32("version"); // Version field value not used + std::string name = args.get("name"); + strlcpy(m_name, name.c_str(), PLAYERNAME_SIZE); + + if (sao) { + try { + sao->setHPRaw(args.getS32("hp")); + } catch(SettingNotFoundException &e) { + sao->setHPRaw(PLAYER_MAX_HP); + } + + try { + sao->setBasePosition(args.getV3F("position")); + } catch (SettingNotFoundException &e) {} + + try { + sao->setPitch(args.getFloat("pitch")); + } catch (SettingNotFoundException &e) {} + try { + sao->setYaw(args.getFloat("yaw")); + } catch (SettingNotFoundException &e) {} + + try { + sao->setBreath(args.getS32("breath")); + } catch (SettingNotFoundException &e) {} + } + + inventory.deSerialize(is); + + if (inventory.getList("craftpreview") == NULL) { + // Convert players without craftpreview + inventory.addList("craftpreview", 1); + + bool craftresult_is_preview = true; + if(args.exists("craftresult_is_preview")) + craftresult_is_preview = args.getBool("craftresult_is_preview"); + if(craftresult_is_preview) + { + // Clear craftresult + inventory.getList("craftresult")->changeItem(0, ItemStack()); + } + } +} + +void RemotePlayer::serialize(std::ostream &os) +{ + // Utilize a Settings object for storing values + Settings args; + args.setS32("version", 1); + args.set("name", m_name); + //args.set("password", m_password); + + // This should not happen + assert(m_sao); + args.setS32("hp", m_sao->getHP()); + args.setV3F("position", m_sao->getBasePosition()); + args.setFloat("pitch", m_sao->getPitch()); + args.setFloat("yaw", m_sao->getYaw()); + args.setS32("breath", m_sao->getBreath()); + + args.writeLines(os); + + os<<"PlayerArgsEnd\n"; + + inventory.serialize(os); +} + +const RemotePlayerChatResult RemotePlayer::canSendChatMessage() +{ + // Rate limit messages + u32 now = time(NULL); + float time_passed = now - m_last_chat_message_sent; + m_last_chat_message_sent = now; + + // If this feature is disabled + if (m_setting_chat_message_limit_per_10sec <= 0.0) { + return RPLAYER_CHATRESULT_OK; + } + + m_chat_message_allowance += time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f); + if (m_chat_message_allowance > m_setting_chat_message_limit_per_10sec) { + m_chat_message_allowance = m_setting_chat_message_limit_per_10sec; + } + + if (m_chat_message_allowance < 1.0f) { + infostream << "Player " << m_name + << " chat limited due to excessive message amount." << std::endl; + + // Kick player if flooding is too intensive + m_message_rate_overhead++; + if (m_message_rate_overhead > RemotePlayer::m_setting_chat_message_limit_trigger_kick) { + return RPLAYER_CHATRESULT_KICK; + } + + return RPLAYER_CHATRESULT_FLOODING; + } + + // Reinit message overhead + if (m_message_rate_overhead > 0) { + m_message_rate_overhead = 0; + } + + m_chat_message_allowance -= 1.0f; + return RPLAYER_CHATRESULT_OK; +} diff --git a/src/remoteplayer.h b/src/remoteplayer.h new file mode 100644 index 000000000..61b5a23de --- /dev/null +++ b/src/remoteplayer.h @@ -0,0 +1,160 @@ +/* +Minetest +Copyright (C) 2010-2016 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2014-2016 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 REMOTEPLAYER_HEADER +#define REMOTEPLAYER_HEADER + +#include "player.h" + +class PlayerSAO; + +enum RemotePlayerChatResult { + RPLAYER_CHATRESULT_OK, + RPLAYER_CHATRESULT_FLOODING, + RPLAYER_CHATRESULT_KICK, +}; +/* + Player on the server +*/ +class RemotePlayer : public Player +{ +public: + RemotePlayer(const char *name, IItemDefManager *idef); + virtual ~RemotePlayer() {} + + void save(std::string savedir, IGameDef *gamedef); + void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao); + + PlayerSAO *getPlayerSAO() { return m_sao; } + void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } + + const RemotePlayerChatResult canSendChatMessage(); + + void setHotbarItemcount(s32 hotbar_itemcount) + { + hud_hotbar_itemcount = hotbar_itemcount; + } + + s32 getHotbarItemcount() const { return hud_hotbar_itemcount; } + + 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 setHotbarImage(const std::string &name) + { + hud_hotbar_image = name; + } + + std::string getHotbarImage() const + { + return hud_hotbar_image; + } + + void setHotbarSelectedImage(const std::string &name) + { + hud_hotbar_selected_image = name; + } + + const std::string &getHotbarSelectedImage() const + { + 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; + } + + bool checkModified() const { return m_dirty || inventory.checkModified(); } + + void setModified(const bool x) + { + m_dirty = x; + if (!x) + inventory.setModified(x); + } + + 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; + } + + void setDirty(bool dirty) { m_dirty = true; } + + u16 protocol_version; +private: + /* + serialize() writes a bunch of text that can contain + any characters except a '\0', and such an ending that + deSerialize stops reading exactly at the right point. + */ + void serialize(std::ostream &os); + + PlayerSAO *m_sao; + bool m_dirty; + + static bool m_setting_cache_loaded; + static float m_setting_chat_message_limit_per_10sec; + static u16 m_setting_chat_message_limit_trigger_kick; + + u32 m_last_chat_message_sent; + float m_chat_message_allowance; + u16 m_message_rate_overhead; + + bool m_day_night_ratio_do_override; + float m_day_night_ratio; + std::string hud_hotbar_image; + std::string hud_hotbar_selected_image; + + std::string m_sky_type; + video::SColor m_sky_bgcolor; + std::vector<std::string> m_sky_params; +}; + +#endif diff --git a/src/rollback.cpp b/src/rollback.cpp index 2367c3a21..4d34decf3 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -42,6 +42,14 @@ with this program; if not, write to the Free Software Foundation, Inc., } #define SQLOK(f) SQLRES(f, SQLITE_OK) +#define SQLOK_ERRSTREAM(s, m) \ + if ((s) != SQLITE_OK) { \ + errorstream << "RollbackManager: " << (m) << ": " \ + << sqlite3_errmsg(db) << std::endl; \ + } + +#define FINALIZE_STATEMENT(statement) \ + SQLOK_ERRSTREAM(sqlite3_finalize(statement), "Failed to finalize " #statement) class ItemStackRow : public ItemStack { public: @@ -93,10 +101,10 @@ RollbackManager::RollbackManager(const std::string & world_path, std::string migrating_flag = txt_filename + ".migrating"; database_path = world_path + DIR_DELIM "rollback.sqlite"; - initDatabase(); + bool created = initDatabase(); - if (fs::PathExists(txt_filename) && (fs::PathExists(migrating_flag) || - !fs::PathExists(database_path))) { + if (fs::PathExists(txt_filename) && (created || + fs::PathExists(migrating_flag))) { std::ofstream of(migrating_flag.c_str()); of.close(); migrate(txt_filename); @@ -109,17 +117,17 @@ RollbackManager::~RollbackManager() { flush(); - SQLOK(sqlite3_finalize(stmt_insert)); - SQLOK(sqlite3_finalize(stmt_replace)); - SQLOK(sqlite3_finalize(stmt_select)); - SQLOK(sqlite3_finalize(stmt_select_range)); - SQLOK(sqlite3_finalize(stmt_select_withActor)); - SQLOK(sqlite3_finalize(stmt_knownActor_select)); - SQLOK(sqlite3_finalize(stmt_knownActor_insert)); - SQLOK(sqlite3_finalize(stmt_knownNode_select)); - SQLOK(sqlite3_finalize(stmt_knownNode_insert)); - - SQLOK(sqlite3_close(db)); + FINALIZE_STATEMENT(stmt_insert); + FINALIZE_STATEMENT(stmt_replace); + FINALIZE_STATEMENT(stmt_select); + FINALIZE_STATEMENT(stmt_select_range); + FINALIZE_STATEMENT(stmt_select_withActor); + FINALIZE_STATEMENT(stmt_knownActor_select); + FINALIZE_STATEMENT(stmt_knownActor_insert); + FINALIZE_STATEMENT(stmt_knownNode_select); + FINALIZE_STATEMENT(stmt_knownNode_insert); + + SQLOK_ERRSTREAM(sqlite3_close(db), "Could not close db"); } @@ -250,15 +258,15 @@ bool RollbackManager::createTables() } -void RollbackManager::initDatabase() +bool RollbackManager::initDatabase() { verbosestream << "RollbackManager: Database connection setup" << std::endl; - bool needsCreate = !fs::PathExists(database_path); + bool needs_create = !fs::PathExists(database_path); SQLOK(sqlite3_open_v2(database_path.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)); - if (needsCreate) { + if (needs_create) { createTables(); } @@ -374,6 +382,8 @@ void RollbackManager::initDatabase() ); } SQLOK(sqlite3_reset(stmt_knownNode_select)); + + return needs_create; } @@ -672,23 +682,27 @@ void RollbackManager::migrate(const std::string & file_path) std::streampos file_size = fh.tellg(); - if (file_size > 10) { + if (file_size < 10) { errorstream << "Empty rollback log." << std::endl; return; } fh.seekg(0); + sqlite3_stmt *stmt_begin; + sqlite3_stmt *stmt_commit; + SQLOK(sqlite3_prepare_v2(db, "BEGIN", -1, &stmt_begin, NULL)); + SQLOK(sqlite3_prepare_v2(db, "COMMIT", -1, &stmt_commit, NULL)); + std::string bit; int i = 0; - int id = 1; time_t start = time(0); time_t t = start; - sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); + SQLRES(sqlite3_step(stmt_begin), SQLITE_DONE); + sqlite3_reset(stmt_begin); do { ActionRow row; - - row.id = id; + row.id = 0; // Get the timestamp std::getline(fh, bit, ' '); @@ -758,17 +772,24 @@ void RollbackManager::migrate(const std::string & file_path) ++i; if (time(0) - t >= 1) { - sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); + SQLRES(sqlite3_step(stmt_commit), SQLITE_DONE); + sqlite3_reset(stmt_commit); t = time(0); std::cout << " Done: " << static_cast<int>((static_cast<float>(fh.tellg()) / static_cast<float>(file_size)) * 100) << "%" << " Speed: " << i / (t - start) << "/second \r" << std::flush; - sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); + SQLRES(sqlite3_step(stmt_begin), SQLITE_DONE); + sqlite3_reset(stmt_begin); } } while (fh.good()); + SQLRES(sqlite3_step(stmt_commit), SQLITE_DONE); + sqlite3_reset(stmt_commit); + + SQLOK(sqlite3_finalize(stmt_begin)); + SQLOK(sqlite3_finalize(stmt_commit)); std::cout - << " Done: 100% " << std::endl + << " Done: 100% " << std::endl << "Now you can delete the old rollback.txt file." << std::endl; } diff --git a/src/rollback.h b/src/rollback.h index c57e38ab0..a05ef8b78 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -61,7 +61,7 @@ private: const char * getActorName(const int id); const char * getNodeName(const int id); bool createTables(); - void initDatabase(); + bool initDatabase(); bool registerRow(const ActionRow & row); const std::list<ActionRow> actionRowsFromSelect(sqlite3_stmt * stmt); ActionRow actionRowFromRollbackAction(const RollbackAction & action); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 06e20c2a0..541744895 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "mg_schematic.h" #include "noise.h" -#include "json/json.h" +#include <json/json.h> struct EnumString es_TileAnimationType[] = { @@ -65,9 +65,8 @@ ItemDefinition read_item_definition(lua_State* L,int index, } lua_pop(L, 1); - def.stack_max = getintfield_default(L, index, "stack_max", def.stack_max); - if(def.stack_max == 0) - def.stack_max = 1; + int stack_max = getintfield_default(L, index, "stack_max", def.stack_max); + def.stack_max = rangelim(stack_max, 1, U16_MAX); lua_getfield(L, index, "on_use"); def.usable = lua_isfunction(L, -1); @@ -527,6 +526,12 @@ ContentFeatures read_content_features(lua_State *L, int index) // Amount of light the node emits f.light_source = getintfield_default(L, index, "light_source", f.light_source); + if (f.light_source > LIGHT_MAX) { + warningstream << "Node " << f.name.c_str() + << " had greater light_source than " << LIGHT_MAX + << ", it was reduced." << std::endl; + f.light_source = LIGHT_MAX; + } f.damage_per_second = getintfield_default(L, index, "damage_per_second", f.damage_per_second); @@ -830,20 +835,18 @@ void push_tool_capabilities(lua_State *L, // Create groupcaps table lua_newtable(L); // For each groupcap - for(std::map<std::string, ToolGroupCap>::const_iterator - i = toolcap.groupcaps.begin(); i != toolcap.groupcaps.end(); i++){ + for (ToolGCMap::const_iterator i = toolcap.groupcaps.begin(); + i != toolcap.groupcaps.end(); i++) { // Create groupcap table lua_newtable(L); const std::string &name = i->first; const ToolGroupCap &groupcap = i->second; // Create subtable "times" lua_newtable(L); - for(std::map<int, float>::const_iterator - i = groupcap.times.begin(); i != groupcap.times.end(); i++){ - int rating = i->first; - float time = i->second; - lua_pushinteger(L, rating); - lua_pushnumber(L, time); + for (UNORDERED_MAP<int, float>::const_iterator + i = groupcap.times.begin(); i != groupcap.times.end(); i++) { + lua_pushinteger(L, i->first); + lua_pushnumber(L, i->second); lua_settable(L, -3); } // Set subtable "times" @@ -859,8 +862,8 @@ void push_tool_capabilities(lua_State *L, //Create damage_groups table lua_newtable(L); // For each damage group - for(std::map<std::string, s16>::const_iterator - i = toolcap.damageGroups.begin(); i != toolcap.damageGroups.end(); i++){ + for (DamageGroup::const_iterator i = toolcap.damageGroups.begin(); + i != toolcap.damageGroups.end(); i++) { // Create damage group table lua_pushinteger(L, i->second); lua_setfield(L, -2, i->first.c_str()); @@ -1066,8 +1069,7 @@ void push_flags_string(lua_State *L, FlagDesc *flagdesc, u32 flags, u32 flagmask /******************************************************************************/ /******************************************************************************/ -void read_groups(lua_State *L, int index, - std::map<std::string, int> &result) +void read_groups(lua_State *L, int index, ItemGroupList &result) { if (!lua_istable(L,index)) return; @@ -1086,11 +1088,10 @@ void read_groups(lua_State *L, int index, } /******************************************************************************/ -void push_groups(lua_State *L, const std::map<std::string, int> &groups) +void push_groups(lua_State *L, const ItemGroupList &groups) { lua_newtable(L); - std::map<std::string, int>::const_iterator it; - for (it = groups.begin(); it != groups.end(); ++it) { + for (ItemGroupList::const_iterator it = groups.begin(); it != groups.end(); ++it) { lua_pushnumber(L, it->second); lua_setfield(L, -2, it->first.c_str()); } @@ -1250,8 +1251,13 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value, lua_newtable(L); for (Json::Value::const_iterator it = value.begin(); it != value.end(); ++it) { +#ifndef JSONCPP_STRING const char *str = it.memberName(); lua_pushstring(L, str ? str : ""); +#else + std::string str = it.name(); + lua_pushstring(L, str.c_str()); +#endif push_json_value_helper(L, *it, nullindex); lua_rawset(L, -3); } diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 46416ad8e..2a2228b6d 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -33,11 +33,11 @@ extern "C" { } #include <iostream> -#include <map> #include <vector> #include "irrlichttypes_bloated.h" #include "util/string.h" +#include "itemgroup.h" namespace Json { class Value; } @@ -106,10 +106,10 @@ void pushnode (lua_State *L, const MapNode &n, NodeBox read_nodebox (lua_State *L, int index); void read_groups (lua_State *L, int index, - std::map<std::string, int> &result); + ItemGroupList &result); void push_groups (lua_State *L, - const std::map<std::string, int> &groups); + const ItemGroupList &groups); //TODO rename to "read_enum_field" int getenumfield (lua_State *L, int table, diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 55c4a5f5a..f36298915 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -23,6 +23,7 @@ extern "C" { } #include "util/numeric.h" +#include "util/serialize.h" #include "util/string.h" #include "common/c_converter.h" #include "constants.h" @@ -37,6 +38,14 @@ extern "C" { } \ } while(0) #define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) +#define CHECK_FLOAT_RANGE(value, name) \ +if (value < F1000_MIN || value > F1000_MAX) { \ + std::ostringstream error_text; \ + error_text << "Invalid float vector dimension range '" name "' " << \ + "(expected " << F1000_MIN << " < " name " < " << F1000_MAX << \ + " got " << value << ")." << std::endl; \ + throw LuaError(error_text.str()); \ +} #define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) @@ -170,14 +179,17 @@ v3f check_v3f(lua_State *L, int index) lua_getfield(L, index, "x"); CHECK_POS_COORD("x"); pos.X = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.X, "x") lua_pop(L, 1); lua_getfield(L, index, "y"); CHECK_POS_COORD("y"); pos.Y = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.Y, "y") lua_pop(L, 1); lua_getfield(L, index, "z"); CHECK_POS_COORD("z"); pos.Z = lua_tonumber(L, -1); + CHECK_FLOAT_RANGE(pos.Z, "z") lua_pop(L, 1); return pos; } @@ -391,7 +403,7 @@ bool getintfield(lua_State *L, int table, lua_getfield(L, table, fieldname); bool got = false; if(lua_isnumber(L, -1)){ - result = lua_tonumber(L, -1); + result = lua_tointeger(L, -1); got = true; } lua_pop(L, 1); @@ -404,7 +416,7 @@ bool getintfield(lua_State *L, int table, lua_getfield(L, table, fieldname); bool got = false; if(lua_isnumber(L, -1)){ - result = lua_tonumber(L, -1); + result = lua_tointeger(L, -1); got = true; } lua_pop(L, 1); @@ -417,7 +429,7 @@ bool getintfield(lua_State *L, int table, lua_getfield(L, table, fieldname); bool got = false; if(lua_isnumber(L, -1)){ - result = lua_tonumber(L, -1); + result = lua_tointeger(L, -1); got = true; } lua_pop(L, 1); @@ -430,7 +442,7 @@ bool getintfield(lua_State *L, int table, lua_getfield(L, table, fieldname); bool got = false; if(lua_isnumber(L, -1)){ - result = lua_tonumber(L, -1); + result = lua_tointeger(L, -1); got = true; } lua_pop(L, 1); diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index eefac0ed7..a5fbee765 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define C_CONVERTER_H_ #include <vector> -#include <map> +#include "util/cpp11_container.h" #include "irrlichttypes_bloated.h" #include "common/c_types.h" @@ -60,7 +60,7 @@ bool getintfield(lua_State *L, int table, 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); + UNORDERED_MAP<std::string, int> &result); bool getboolfield(lua_State *L, int table, const char *fieldname, bool &result); bool getfloatfield(lua_State *L, int table, diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 9bf3fcf49..1fb84fab6 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -81,6 +81,7 @@ bool AsyncEngine::registerFunction(const char* name, lua_CFunction func) if (initDone) { return false; } + functionList[name] = func; return true; } @@ -203,7 +204,7 @@ void AsyncEngine::pushFinishedJobs(lua_State* L) { /******************************************************************************/ void AsyncEngine::prepareEnvironment(lua_State* L, int top) { - for (std::map<std::string, lua_CFunction>::iterator it = functionList.begin(); + for (UNORDERED_MAP<std::string, lua_CFunction>::iterator it = functionList.begin(); it != functionList.end(); it++) { lua_pushstring(L, it->first.c_str()); lua_pushcfunction(L, it->second); diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 8d612d58c..016381e5f 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -132,7 +132,7 @@ private: bool initDone; // Internal store for registred functions - std::map<std::string, lua_CFunction> functionList; + UNORDERED_MAP<std::string, lua_CFunction> functionList; // Internal counter to create job IDs unsigned int jobIdCounter; diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 82d0d4f0e..913d8539d 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -212,11 +212,13 @@ void ScriptApiEnv::on_emerge_area_completion( { Server *server = getServer(); + // This function should be executed with envlock held. + // The caller (LuaEmergeAreaCallback in src/script/lua_api/l_env.cpp) + // should have obtained the lock. // Note that the order of these locks is important! Envlock must *ALWAYS* // be acquired before attempting to acquire scriptlock, or else ServerThread // will try to acquire scriptlock after it already owns envlock, thus // deadlocking EmergeThread and ServerThread - MutexAutoLock envlock(server->m_env_mutex); SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index 17f0f0dac..379ed773f 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -58,6 +58,7 @@ struct EnumString ScriptApiNode::es_ContentParamType2[] = {CPT2_WALLMOUNTED, "wallmounted"}, {CPT2_LEVELED, "leveled"}, {CPT2_DEGROTATE, "degrotate"}, + {CPT2_MESHOPTIONS, "meshoptions"}, {0, NULL}, }; diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index 807430678..a8c07476c 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -135,7 +135,8 @@ void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player) runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } -void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player) +void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player, + bool timeout) { SCRIPTAPI_PRECHECKHEADER @@ -144,7 +145,8 @@ void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_leaveplayers"); // Call callbacks objectrefGetOrCreate(L, player); - runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); + lua_pushboolean(L, timeout); + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_cheat(ServerActiveObject *player, diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index 2e4dc2222..86ee1b024 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -38,7 +38,7 @@ public: 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_leaveplayer(ServerActiveObject *player, bool timeout); void on_cheat(ServerActiveObject *player, const std::string &cheat_type); bool on_punchplayer(ServerActiveObject *player, ServerActiveObject *hitter, float time_from_last_punch, diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 730235c7b..1b1f148cd 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -249,8 +249,8 @@ bool ScriptApiSecurity::isSecure(lua_State *L) #define CHECK_FILE_ERR(ret, fp) \ if (ret) { \ - if (fp) std::fclose(fp); \ lua_pushfstring(L, "%s: %s", path, strerror(errno)); \ + if (fp) std::fclose(fp); \ return false; \ } @@ -285,39 +285,50 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path) if (c == LUA_SIGNATURE[0]) { lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + std::fclose(fp); + if (path) { + delete [] chunk_name; + } 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)); + std::fclose(fp); + delete [] code; + if (path) { + delete [] chunk_name; + } 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."); + delete [] code; + if (path) { + delete [] chunk_name; + } return false; } if (luaL_loadbuffer(L, code, size, chunk_name)) { + delete [] code; return false; } + delete [] code; + if (path) { delete [] chunk_name; } @@ -325,12 +336,15 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path) } -bool ScriptApiSecurity::checkPath(lua_State *L, const char *path) +bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, + bool write_required, bool *write_allowed) { + if (write_allowed) + *write_allowed = false; + std::string str; // Transient - std::string norel_path = fs::RemoveRelativePathComponents(path); - std::string abs_path = fs::AbsolutePath(norel_path); + std::string abs_path = fs::AbsolutePath(path); if (!abs_path.empty()) { // Don't allow accessing the settings file @@ -341,18 +355,29 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path) // 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 cur_path = 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); + std::string component; + cur_path = fs::RemoveLastPathComponent(cur_path, &component); + if (component == "..") { + // Parent components can't be allowed or we could allow something like + // /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd. + // If we have previous non-relative elements in the path we might be + // able to remove them so that things like worlds/foo/noexist/../auth.txt + // could be allowed, but those paths will be interpreted as nonexistent + // by the operating system anyways. + return false; + } + removed = component + (removed.empty() ? "" : DIR_DELIM + removed); abs_path = fs::AbsolutePath(cur_path); } - if (abs_path.empty()) return false; + 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; + if (!removed.empty()) + abs_path += DIR_DELIM + removed; // Get server from registry lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); @@ -369,32 +394,53 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path) // Builtin can access anything if (mod_name == BUILTIN_MOD_NAME) { + if (write_allowed) *write_allowed = true; return true; } // Allow paths in mod path - const ModSpec *mod = server->getModSpec(mod_name); - if (mod) { - str = fs::AbsolutePath(mod->path); + // Don't bother if write access isn't important, since it will be handled later + if (write_required || write_allowed != NULL) { + const ModSpec *mod = server->getModSpec(mod_name); + if (mod) { + str = fs::AbsolutePath(mod->path); + if (!str.empty() && fs::PathStartsWith(abs_path, str)) { + if (write_allowed) *write_allowed = true; + return true; + } + } + } + } + lua_pop(L, 1); // Pop mod name + + // Allow read-only access to all mod directories + if (!write_required) { + const std::vector<ModSpec> mods = server->getMods(); + for (size_t i = 0; i < mods.size(); ++i) { + str = fs::AbsolutePath(mods[i].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; + if (!str.empty()) { + // Don't allow access to other paths in the world mod/game path. + // These have to be blocked so you can't override a trusted mod + // by creating a mod with the same name in a world mod directory. + // 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)) { + if (write_allowed) *write_allowed = true; + return true; + } } // Default to disallowing @@ -465,7 +511,7 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) if (lua_isstring(L, 1)) { path = lua_tostring(L, 1); - CHECK_SECURE_PATH(L, path); + CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL); } if (!safeLoadFile(L, path)) { @@ -514,14 +560,28 @@ int ScriptApiSecurity::sl_g_require(lua_State *L) int ScriptApiSecurity::sl_io_open(lua_State *L) { + bool with_mode = lua_gettop(L) > 1; + luaL_checktype(L, 1, LUA_TSTRING); const char *path = lua_tostring(L, 1); - CHECK_SECURE_PATH(L, path); + + bool write_requested = false; + if (with_mode) { + luaL_checktype(L, 2, LUA_TSTRING); + const char *mode = lua_tostring(L, 2); + write_requested = strchr(mode, 'w') != NULL || + strchr(mode, '+') != NULL || + strchr(mode, 'a') != NULL; + } + CHECK_SECURE_PATH_INTERNAL(L, path, write_requested, NULL); push_original(L, "io", "open"); lua_pushvalue(L, 1); - lua_pushvalue(L, 2); - lua_call(L, 2, 2); + if (with_mode) { + lua_pushvalue(L, 2); + } + + lua_call(L, with_mode ? 2 : 1, 2); return 2; } @@ -530,7 +590,7 @@ 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); + CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL); } push_original(L, "io", "input"); @@ -544,7 +604,7 @@ 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); + CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL); } push_original(L, "io", "output"); @@ -558,16 +618,16 @@ 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); + CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL); } + int top_precall = lua_gettop(L); 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); + return lua_gettop(L) - top_precall; } @@ -575,11 +635,11 @@ 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); + CHECK_SECURE_PATH_INTERNAL(L, path1, true, NULL); luaL_checktype(L, 2, LUA_TSTRING); const char *path2 = lua_tostring(L, 2); - CHECK_SECURE_PATH(L, path2); + CHECK_SECURE_PATH_INTERNAL(L, path2, true, NULL); push_original(L, "os", "rename"); lua_pushvalue(L, 1); @@ -593,7 +653,7 @@ 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); + CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL); push_original(L, "os", "remove"); lua_pushvalue(L, 1); diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index 97bc5c067..6876108e8 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -23,14 +23,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" -#define CHECK_SECURE_PATH(L, path) \ - if (!ScriptApiSecurity::checkPath(L, path)) { \ - throw LuaError(std::string("Attempt to access external file ") + \ - path + " with mod security on."); \ +#define CHECK_SECURE_PATH_INTERNAL(L, path, write_required, ptr) \ + if (!ScriptApiSecurity::checkPath(L, path, write_required, ptr)) { \ + throw LuaError(std::string("Mod security: Blocked attempted ") + \ + (write_required ? "write to " : "read from ") + path); \ } -#define CHECK_SECURE_PATH_OPTIONAL(L, path) \ +#define CHECK_SECURE_PATH(L, path, write_required) \ if (ScriptApiSecurity::isSecure(L)) { \ - CHECK_SECURE_PATH(L, path); \ + CHECK_SECURE_PATH_INTERNAL(L, path, write_required, NULL); \ + } +#define CHECK_SECURE_PATH_POSSIBLE_WRITE(L, path, ptr) \ + if (ScriptApiSecurity::isSecure(L)) { \ + CHECK_SECURE_PATH_INTERNAL(L, path, false, ptr); \ } @@ -43,8 +47,9 @@ public: 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); + // Checks if mods are allowed to read (and optionally write) to the path + static bool checkPath(lua_State *L, const char *path, bool write_required, + bool *write_allowed=NULL); private: // Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name> diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 20e7875c7..09a5c78f9 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -111,6 +111,9 @@ int LuaAreaStore::l_get_area(lua_State *L) const Area *res; res = ast->getArea(id); + if (!res) + return 0; + push_area(L, res, include_borders, include_data); return 1; @@ -260,7 +263,7 @@ int LuaAreaStore::l_to_file(lua_State *L) AreaStore *ast = o->as; const char *filename = luaL_checkstring(L, 2); - CHECK_SECURE_PATH_OPTIONAL(L, filename); + CHECK_SECURE_PATH(L, filename, true); std::ostringstream os(std::ios_base::binary); ast->serialize(os); @@ -291,7 +294,7 @@ int LuaAreaStore::l_from_file(lua_State *L) LuaAreaStore *o = checkobject(L, 1); const char *filename = luaL_checkstring(L, 2); - CHECK_SECURE_PATH_OPTIONAL(L, filename); + CHECK_SECURE_PATH(L, filename, false); std::ifstream is(filename, std::ios::binary); return deserialization_helper(L, o->as, is); diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 391a0133d..2236566de 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -34,7 +34,6 @@ struct EnumString ModApiCraft::es_CraftMethod[] = {0, NULL}, }; - // helper for register_craft bool ModApiCraft::readCraftRecipeShaped(lua_State *L, int index, int &width, std::vector<std::string> &recipe) @@ -281,6 +280,80 @@ int ModApiCraft::l_register_craft(lua_State *L) return 0; /* number of results */ } +// clear_craft({[output=item], [recipe={{item00,item10},{item01,item11}}]) +int ModApiCraft::l_clear_craft(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + luaL_checktype(L, 1, LUA_TTABLE); + int table = 1; + + // Get the writable craft definition manager from the server + IWritableCraftDefManager *craftdef = + getServer(L)->getWritableCraftDefManager(); + + std::string output = getstringfield_default(L, table, "output", ""); + std::string type = getstringfield_default(L, table, "type", "shaped"); + CraftOutput c_output(output, 0); + if (output != "") { + if (craftdef->clearCraftRecipesByOutput(c_output, getServer(L))) + return 0; + else + throw LuaError("No craft recipe known for output" + " (output=\"" + output + "\")"); + } + std::vector<std::string> recipe; + int width = 0; + CraftMethod method = CRAFT_METHOD_NORMAL; + /* + CraftDefinitionShaped + */ + if (type == "shaped") { + lua_getfield(L, table, "recipe"); + if (lua_isnil(L, -1)) + throw LuaError("Either output or recipe has to be defined"); + if (!readCraftRecipeShaped(L, -1, width, recipe)) + throw LuaError("Invalid crafting recipe"); + } + /* + CraftDefinitionShapeless + */ + else if (type == "shapeless") { + lua_getfield(L, table, "recipe"); + if (lua_isnil(L, -1)) + throw LuaError("Either output or recipe has to be defined"); + if (!readCraftRecipeShapeless(L, -1, recipe)) + throw LuaError("Invalid crafting recipe"); + } + /* + CraftDefinitionCooking + */ + else if (type == "cooking") { + method = CRAFT_METHOD_COOKING; + std::string rec = getstringfield_default(L, table, "recipe", ""); + if (rec == "") + throw LuaError("Crafting definition (cooking)" + " is missing a recipe"); + recipe.push_back(rec); + } + /* + CraftDefinitionFuel + */ + else if (type == "fuel") { + method = CRAFT_METHOD_FUEL; + std::string rec = getstringfield_default(L, table, "recipe", ""); + if (rec == "") + throw LuaError("Crafting definition (fuel)" + " is missing a recipe"); + recipe.push_back(rec); + } else { + throw LuaError("Unknown crafting definition type: \"" + type + "\""); + } + if (!craftdef->clearCraftRecipesByInput(method, width, recipe, getServer(L))) + throw LuaError("No crafting specified for input"); + lua_pop(L, 1); + return 0; +} + // get_craft_result(input) int ModApiCraft::l_get_craft_result(lua_State *L) { @@ -349,20 +422,28 @@ static void push_craft_recipe(lua_State *L, IGameDef *gdef, } lua_setfield(L, -2, "items"); setintfield(L, -1, "width", input.width); + + std::string method_s; switch (input.method) { case CRAFT_METHOD_NORMAL: - lua_pushstring(L, "normal"); + method_s = "normal"; break; case CRAFT_METHOD_COOKING: - lua_pushstring(L, "cooking"); + method_s = "cooking"; break; case CRAFT_METHOD_FUEL: - lua_pushstring(L, "fuel"); + method_s = "fuel"; break; default: - lua_pushstring(L, "unknown"); + method_s = "unknown"; } + lua_pushstring(L, method_s.c_str()); + lua_setfield(L, -2, "method"); + + // Deprecated, only for compatibility's sake + lua_pushstring(L, method_s.c_str()); lua_setfield(L, -2, "type"); + lua_pushstring(L, output.item.c_str()); lua_setfield(L, -2, "output"); } @@ -431,4 +512,5 @@ void ModApiCraft::Initialize(lua_State *L, int top) API_FCT(get_craft_recipe); API_FCT(get_craft_result); API_FCT(register_craft); + API_FCT(clear_craft); } diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 548608776..eb2bce706 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -33,6 +33,7 @@ private: static int l_get_craft_recipe(lua_State *L); static int l_get_all_craft_recipes(lua_State *L); static int l_get_craft_result(lua_State *L); + static int l_clear_craft(lua_State *L); static bool readCraftReplacements(lua_State *L, int index, CraftReplacements &replacements); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 8284c3fcb..68d10308c 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -137,6 +137,10 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) assert(state->script != NULL); assert(state->refcount > 0); + // state must be protected by envlock + Server *server = state->script->getServer(); + MutexAutoLock envlock(server->m_env_mutex); + state->refcount--; state->script->on_emerge_area_completion(blockpos, action, state); @@ -494,8 +498,8 @@ int ModApiEnvMod::l_get_player_by_name(lua_State *L) // Do it const char *name = luaL_checkstring(L, 1); - Player *player = env->getPlayer(name); - if(player == NULL){ + RemotePlayer *player = dynamic_cast<RemotePlayer *>(env->getPlayer(name)); + if (player == NULL){ lua_pushnil(L); return 1; } @@ -758,7 +762,7 @@ int ModApiEnvMod::l_get_perlin_map(lua_State *L) return 0; v3s16 size = read_v3s16(L, 2); - int seed = (int)(env->getServerMap().getSeed()); + s32 seed = (s32)(env->getServerMap().getSeed()); LuaPerlinNoiseMap *n = new LuaPerlinNoiseMap(&np, seed, size); *(void **)(lua_newuserdata(L, sizeof(void *))) = n; luaL_getmetatable(L, "PerlinNoiseMap"); diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index de9f9374a..38eade609 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -420,7 +420,7 @@ void InvRef::create(lua_State *L, const InventoryLocation &loc) luaL_getmetatable(L, className); lua_setmetatable(L, -2); } -void InvRef::createPlayer(lua_State *L, Player *player) +void InvRef::createPlayer(lua_State *L, RemotePlayer *player) { NO_MAP_LOCK_REQUIRED; InventoryLocation loc; @@ -520,16 +520,17 @@ int ModApiInventory::l_get_inventory(lua_State *L) } } -// create_detached_inventory_raw(name) +// create_detached_inventory_raw(name, [player_name]) int ModApiInventory::l_create_detached_inventory_raw(lua_State *L) { NO_MAP_LOCK_REQUIRED; const char *name = luaL_checkstring(L, 1); - if(getServer(L)->createDetachedInventory(name) != NULL){ + const char *player = lua_isstring(L, 2) ? lua_tostring(L, 2) : ""; + if (getServer(L)->createDetachedInventory(name, player) != NULL) { InventoryLocation loc; loc.setDetached(name); InvRef::create(L, loc); - }else{ + } else { lua_pushnil(L); } return 1; diff --git a/src/script/lua_api/l_inventory.h b/src/script/lua_api/l_inventory.h index 2d4b29d0c..cc5333965 100644 --- a/src/script/lua_api/l_inventory.h +++ b/src/script/lua_api/l_inventory.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventory.h" #include "inventorymanager.h" -class Player; +class RemotePlayer; /* InvRef @@ -112,7 +112,7 @@ public: // Creates an InvRef and leaves it on top of stack // Not callable from Lua; all references are created on the C side. static void create(lua_State *L, const InventoryLocation &loc); - static void createPlayer(lua_State *L, Player *player); + static void createPlayer(lua_State *L, RemotePlayer *player); static void createNodeMeta(lua_State *L, v3s16 p); static void Register(lua_State *L); }; @@ -123,11 +123,6 @@ private: static int l_get_inventory(lua_State *L); - static void inventory_set_list_from_lua(Inventory *inv, const char *name, - lua_State *L, int tableindex, int forcesize); - static void inventory_get_list_to_lua(Inventory *inv, const char *name, - lua_State *L); - public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 5381cba76..ff0baea14 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -525,6 +525,27 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) return 0; /* number of results */ } +// unregister_item(name) +int ModApiItemMod::l_unregister_item_raw(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + std::string name = luaL_checkstring(L, 1); + + IWritableItemDefManager *idef = + getServer(L)->getWritableItemDefManager(); + + // Unregister the node + if (idef->get(name).type == ITEM_NODE) { + IWritableNodeDefManager *ndef = + getServer(L)->getWritableNodeDefManager(); + ndef->removeNode(name); + } + + idef->unregisterItem(name); + + return 0; /* number of results */ +} + // register_alias_raw(name, convert_to_name) int ModApiItemMod::l_register_alias_raw(lua_State *L) { @@ -570,6 +591,7 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L) void ModApiItemMod::Initialize(lua_State *L, int top) { API_FCT(register_item_raw); + API_FCT(unregister_item_raw); API_FCT(register_alias_raw); API_FCT(get_content_id); API_FCT(get_name_from_content_id); diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 0f9e4ba9b..be919b701 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -135,6 +135,7 @@ public: class ModApiItemMod : public ModApiBase { private: static int l_register_item_raw(lua_State *L); + static int l_unregister_item_raw(lua_State *L); static int l_register_alias_raw(lua_State *L); static int l_get_content_id(lua_State *L); static int l_get_name_from_content_id(lua_State *L); diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 7b29db159..4a2484613 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "convert_json.h" #include "serverlist.h" -#include "emerge.h" +#include "mapgen.h" #include "sound.h" #include "settings.h" #include "log.h" @@ -707,7 +707,7 @@ int ModApiMainMenu::l_set_topleft_text(lua_State *L) int ModApiMainMenu::l_get_mapgen_names(lua_State *L) { std::vector<const char *> names; - EmergeManager::getMapgenNames(&names, lua_toboolean(L, 1)); + Mapgen::getMapgenNames(&names, lua_toboolean(L, 1)); lua_newtable(L); for (size_t i = 0; i != names.size(); i++) { @@ -956,13 +956,6 @@ int ModApiMainMenu::l_show_file_open_dialog(lua_State *L) } /******************************************************************************/ -int ModApiMainMenu::l_get_version(lua_State *L) -{ - lua_pushstring(L, g_version_string); - return 1; -} - -/******************************************************************************/ int ModApiMainMenu::l_sound_play(lua_State *L) { GUIEngine* engine = getGuiEngine(L); @@ -1157,7 +1150,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(extract_zip); API_FCT(get_mainmenu_path); API_FCT(show_file_open_dialog); - API_FCT(get_version); API_FCT(download_file); API_FCT(get_modstore_details); API_FCT(get_modstore_list); @@ -1188,7 +1180,6 @@ void ModApiMainMenu::InitializeAsync(AsyncEngine& engine) ASYNC_API_FCT(delete_dir); ASYNC_API_FCT(copy_dir); //ASYNC_API_FCT(extract_zip); //TODO remove dependency to GuiEngine - ASYNC_API_FCT(get_version); ASYNC_API_FCT(download_file); ASYNC_API_FCT(get_modstore_details); ASYNC_API_FCT(get_modstore_list); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 405af25e8..ad5155ac6 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -79,8 +79,6 @@ private: static int l_delete_favorite(lua_State *L); - static int l_get_version(lua_State *L); - static int l_sound_play(lua_State *L); static int l_sound_stop(lua_State *L); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index fb839176b..bc1c32f03 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -39,11 +39,11 @@ with this program; if not, write to the Free Software Foundation, Inc., struct EnumString ModApiMapgen::es_BiomeTerrainType[] = { - {BIOME_NORMAL, "normal"}, - {BIOME_LIQUID, "liquid"}, - {BIOME_NETHER, "nether"}, - {BIOME_AETHER, "aether"}, - {BIOME_FLAT, "flat"}, + {BIOMETYPE_NORMAL, "normal"}, + {BIOMETYPE_LIQUID, "liquid"}, + {BIOMETYPE_NETHER, "nether"}, + {BIOMETYPE_AETHER, "aether"}, + {BIOMETYPE_FLAT, "flat"}, {0, NULL}, }; @@ -100,7 +100,7 @@ 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); + BiomeManager *biomemgr, UNORDERED_SET<u8> *biome_id_list); Schematic *get_or_load_schematic(lua_State *L, int index, SchematicManager *schemmgr, StringMap *replace_names); @@ -244,7 +244,7 @@ bool read_schematic_def(lua_State *L, int index, schem->schemdata = new MapNode[numnodes]; size_t names_base = names->size(); - std::map<std::string, content_t> name_id_map; + UNORDERED_MAP<std::string, content_t> name_id_map; u32 i = 0; for (lua_pushnil(L); lua_next(L, -2); i++, lua_pop(L, 1)) { @@ -266,7 +266,7 @@ bool read_schematic_def(lua_State *L, int index, 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); + UNORDERED_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; @@ -371,13 +371,14 @@ Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef) return NULL; BiomeType biometype = (BiomeType)getenumfield(L, index, "type", - ModApiMapgen::es_BiomeTerrainType, BIOME_NORMAL); + ModApiMapgen::es_BiomeTerrainType, BIOMETYPE_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->depth_riverbed = getintfield_default(L, index, "depth_riverbed", 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); @@ -391,6 +392,7 @@ Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef) 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_riverbed", "")); nn.push_back(getstringfield_default(L, index, "node_dust", "")); ndef->pendNodeResolve(b); @@ -399,7 +401,7 @@ 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) + BiomeManager *biomemgr, UNORDERED_SET<u8> *biome_id_list) { if (index < 0) index = lua_gettop(L) + 1 + index; @@ -528,24 +530,26 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) return 1; } case MGOBJ_BIOMEMAP: { - if (!mg->biomemap) + if (!mg->biomegen) return 0; lua_newtable(L); for (size_t i = 0; i != maplen; i++) { - lua_pushinteger(L, mg->biomemap[i]); + lua_pushinteger(L, mg->biomegen->biomemap[i]); lua_rawseti(L, -2, i + 1); } return 1; } case MGOBJ_HEATMAP: { - if (!mg->heatmap) + if (!mg->biomegen || mg->biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; + BiomeGenOriginal *bg = (BiomeGenOriginal *)mg->biomegen; + lua_newtable(L); for (size_t i = 0; i != maplen; i++) { - lua_pushnumber(L, mg->heatmap[i]); + lua_pushnumber(L, bg->heatmap[i]); lua_rawseti(L, -2, i + 1); } @@ -553,12 +557,14 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) } case MGOBJ_HUMIDMAP: { - if (!mg->humidmap) + if (!mg->biomegen || mg->biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; + BiomeGenOriginal *bg = (BiomeGenOriginal *)mg->biomegen; + lua_newtable(L); for (size_t i = 0; i != maplen; i++) { - lua_pushnumber(L, mg->humidmap[i]); + lua_pushnumber(L, bg->humidmap[i]); lua_rawseti(L, -2, i + 1); } @@ -594,24 +600,37 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; - MapgenParams *params = &getServer(L)->getEmergeManager()->params; + log_deprecated(L, "get_mapgen_params is deprecated; " + "use get_mapgen_setting instead"); + + std::string value; + + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; lua_newtable(L); - lua_pushstring(L, params->mg_name.c_str()); + settingsmgr->getMapSetting("mg_name", &value); + lua_pushstring(L, value.c_str()); lua_setfield(L, -2, "mgname"); - lua_pushinteger(L, params->seed); + settingsmgr->getMapSetting("seed", &value); + std::istringstream ss(value); + u64 seed; + ss >> seed; + lua_pushinteger(L, seed); lua_setfield(L, -2, "seed"); - lua_pushinteger(L, params->water_level); + settingsmgr->getMapSetting("water_level", &value); + lua_pushinteger(L, stoi(value, -32768, 32767)); lua_setfield(L, -2, "water_level"); - lua_pushinteger(L, params->chunksize); + settingsmgr->getMapSetting("chunksize", &value); + lua_pushinteger(L, stoi(value, -32768, 32767)); lua_setfield(L, -2, "chunksize"); - std::string flagstr = writeFlagString(params->flags, flagdesc_mapgen, U32_MAX); - lua_pushstring(L, flagstr.c_str()); + settingsmgr->getMapSetting("mg_flags", &value); + lua_pushstring(L, value.c_str()); lua_setfield(L, -2, "flags"); return 1; @@ -624,44 +643,120 @@ int ModApiMapgen::l_set_mapgen_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; + log_deprecated(L, "set_mapgen_params is deprecated; " + "use set_mapgen_setting instead"); + if (!lua_istable(L, 1)) return 0; - EmergeManager *emerge = getServer(L)->getEmergeManager(); - if (emerge->isRunning()) - throw LuaError("Cannot set parameters while mapgen is running"); - - MapgenParams *params = &emerge->params; - u32 flags = 0, flagmask = 0; + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; lua_getfield(L, 1, "mgname"); - if (lua_isstring(L, -1)) { - params->mg_name = lua_tostring(L, -1); - delete params->sparams; - params->sparams = NULL; - } + if (lua_isstring(L, -1)) + settingsmgr->setMapSetting("mg_name", lua_tostring(L, -1), true); lua_getfield(L, 1, "seed"); if (lua_isnumber(L, -1)) - params->seed = lua_tointeger(L, -1); + settingsmgr->setMapSetting("seed", lua_tostring(L, -1), true); lua_getfield(L, 1, "water_level"); if (lua_isnumber(L, -1)) - params->water_level = lua_tointeger(L, -1); + settingsmgr->setMapSetting("water_level", lua_tostring(L, -1), true); lua_getfield(L, 1, "chunksize"); if (lua_isnumber(L, -1)) - params->chunksize = lua_tointeger(L, -1); + settingsmgr->setMapSetting("chunksize", lua_tostring(L, -1), true); warn_if_field_exists(L, 1, "flagmask", "Deprecated: flags field now includes unset flags."); - lua_getfield(L, 1, "flagmask"); + + lua_getfield(L, 1, "flags"); if (lua_isstring(L, -1)) - params->flags &= ~readFlagString(lua_tostring(L, -1), flagdesc_mapgen, NULL); + settingsmgr->setMapSetting("mg_flags", lua_tostring(L, -1), true); + + return 0; +} + +// get_mapgen_setting(name) +int ModApiMapgen::l_get_mapgen_setting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string value; + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; - if (getflagsfield(L, 1, "flags", flagdesc_mapgen, &flags, &flagmask)) { - params->flags &= ~flagmask; - params->flags |= flags; + const char *name = luaL_checkstring(L, 1); + if (!settingsmgr->getMapSetting(name, &value)) + return 0; + + lua_pushstring(L, value.c_str()); + return 1; +} + +// get_mapgen_setting_noiseparams(name) +int ModApiMapgen::l_get_mapgen_setting_noiseparams(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + NoiseParams np; + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; + + const char *name = luaL_checkstring(L, 1); + if (!settingsmgr->getMapSettingNoiseParams(name, &np)) + return 0; + + push_noiseparams(L, &np); + return 1; +} + +// set_mapgen_setting(name, value, override_meta) +// set mapgen config values +int ModApiMapgen::l_set_mapgen_setting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; + + const char *name = luaL_checkstring(L, 1); + const char *value = luaL_checkstring(L, 2); + bool override_meta = lua_isboolean(L, 3) ? lua_toboolean(L, 3) : false; + + if (!settingsmgr->setMapSetting(name, value, override_meta)) { + errorstream << "set_mapgen_setting: cannot set '" + << name << "' after initialization" << std::endl; + } + + return 0; +} + + +// set_mapgen_setting_noiseparams(name, noiseparams, set_default) +// set mapgen config values for noise parameters +int ModApiMapgen::l_set_mapgen_setting_noiseparams(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + MapSettingsManager *settingsmgr = + getServer(L)->getEmergeManager()->map_settings_mgr; + + const char *name = luaL_checkstring(L, 1); + + NoiseParams np; + if (!read_noiseparams(L, 2, &np)) { + errorstream << "set_mapgen_setting_noiseparams: cannot set '" << name + << "'; invalid noiseparams table" << std::endl; + return 0; + } + + bool override_meta = lua_isboolean(L, 3) ? lua_toboolean(L, 3) : false; + + if (!settingsmgr->setMapSettingNoiseParams(name, &np, override_meta)) { + errorstream << "set_mapgen_setting_noiseparams: cannot set '" + << name << "' after initialization" << std::endl; } return 0; @@ -677,8 +772,11 @@ int ModApiMapgen::l_set_noiseparams(lua_State *L) const char *name = luaL_checkstring(L, 1); NoiseParams np; - if (!read_noiseparams(L, 2, &np)) + if (!read_noiseparams(L, 2, &np)) { + errorstream << "set_noiseparams: cannot set '" << name + << "'; invalid noiseparams table" << std::endl; return 0; + } bool set_default = lua_isboolean(L, 3) ? lua_toboolean(L, 3) : true; @@ -804,6 +902,7 @@ int ModApiMapgen::l_register_decoration(lua_State *L) deco->fill_ratio = getfloatfield_default(L, index, "fill_ratio", 0.02); deco->y_min = getintfield_default(L, index, "y_min", -31000); deco->y_max = getintfield_default(L, index, "y_max", 31000); + deco->nspawnby = getintfield_default(L, index, "num_spawn_by", -1); deco->sidelen = getintfield_default(L, index, "sidelen", 8); if (deco->sidelen <= 0) { errorstream << "register_decoration: sidelen must be " @@ -831,6 +930,14 @@ int ModApiMapgen::l_register_decoration(lua_State *L) errorstream << "register_decoration: couldn't get all biomes " << std::endl; lua_pop(L, 1); + //// Get node name(s) to 'spawn by' + size_t 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; + } + //// Handle decoration type-specific parameters bool success = false; switch (decotype) { @@ -864,12 +971,11 @@ int ModApiMapgen::l_register_decoration(lua_State *L) bool read_deco_simple(lua_State *L, DecoSimple *deco) { - size_t nnames; int index = 1; + int param2; deco->deco_height = getintfield_default(L, index, "height", 1); deco->deco_height_max = getintfield_default(L, index, "height_max", 0); - deco->nspawnby = getintfield_default(L, index, "num_spawn_by", -1); if (deco->deco_height <= 0) { errorstream << "register_decoration: simple decoration height" @@ -877,7 +983,7 @@ bool read_deco_simple(lua_State *L, DecoSimple *deco) return false; } - nnames = getstringlistfield(L, index, "decoration", &deco->m_nodenames); + size_t nnames = getstringlistfield(L, index, "decoration", &deco->m_nodenames); deco->m_nnlistsizes.push_back(nnames); if (nnames == 0) { errorstream << "register_decoration: no decoration nodes " @@ -885,13 +991,13 @@ bool read_deco_simple(lua_State *L, DecoSimple *deco) return false; } - 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; + param2 = getintfield_default(L, index, "param2", 0); + if ((param2 < 0) || (param2 > 255)) { + errorstream << "register_decoration: param2 out of bounds (0-255)" + << std::endl; return false; } + deco->deco_param2 = (u8)param2; return true; } @@ -1137,7 +1243,7 @@ int ModApiMapgen::l_generate_ores(lua_State *L) EmergeManager *emerge = getServer(L)->getEmergeManager(); Mapgen mg; - mg.seed = emerge->params.seed; + mg.seed = emerge->mgparams->seed; mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; mg.ndef = getServer(L)->getNodeDefManager(); @@ -1163,7 +1269,7 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) EmergeManager *emerge = getServer(L)->getEmergeManager(); Mapgen mg; - mg.seed = emerge->params.seed; + mg.seed = emerge->mgparams->seed; mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; mg.ndef = getServer(L)->getNodeDefManager(); @@ -1189,7 +1295,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L) INodeDefManager *ndef = getServer(L)->getNodeDefManager(); const char *filename = luaL_checkstring(L, 4); - CHECK_SECURE_PATH_OPTIONAL(L, filename); + CHECK_SECURE_PATH(L, filename, true); Map *map = &(getEnv(L)->getMap()); Schematic schem; @@ -1387,6 +1493,10 @@ void ModApiMapgen::Initialize(lua_State *L, int top) API_FCT(get_mapgen_params); API_FCT(set_mapgen_params); + API_FCT(get_mapgen_setting); + API_FCT(set_mapgen_setting); + API_FCT(get_mapgen_setting_noiseparams); + API_FCT(set_mapgen_setting_noiseparams); API_FCT(set_noiseparams); API_FCT(get_noiseparams); API_FCT(set_gen_notify); diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index 9751c0db6..bb94575c7 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -40,6 +40,18 @@ private: // set mapgen parameters static int l_set_mapgen_params(lua_State *L); + // get_mapgen_setting(name) + static int l_get_mapgen_setting(lua_State *L); + + // set_mapgen_setting(name, value, override_meta) + static int l_set_mapgen_setting(lua_State *L); + + // get_mapgen_setting_noiseparams(name) + static int l_get_mapgen_setting_noiseparams(lua_State *L); + + // set_mapgen_setting_noiseparams(name, value, override_meta) + static int l_set_mapgen_setting_noiseparams(lua_State *L); + // set_noiseparam_defaults(name, noiseparams, set_default) static int l_set_noiseparams(lua_State *L); diff --git a/src/script/lua_api/l_nodetimer.cpp b/src/script/lua_api/l_nodetimer.cpp index 601113516..3242d6ea5 100644 --- a/src/script/lua_api/l_nodetimer.cpp +++ b/src/script/lua_api/l_nodetimer.cpp @@ -45,7 +45,7 @@ int NodeTimerRef::l_set(lua_State *L) if(env == NULL) return 0; f32 t = luaL_checknumber(L,2); f32 e = luaL_checknumber(L,3); - env->getMap().setNodeTimer(o->m_p,NodeTimer(t,e)); + env->getMap().setNodeTimer(NodeTimer(t, e, o->m_p)); return 0; } @@ -56,7 +56,7 @@ int NodeTimerRef::l_start(lua_State *L) ServerEnvironment *env = o->m_env; if(env == NULL) return 0; f32 t = luaL_checknumber(L,2); - env->getMap().setNodeTimer(o->m_p,NodeTimer(t,0)); + env->getMap().setNodeTimer(NodeTimer(t, 0, o->m_p)); return 0; } diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 04dc6048f..e0039371f 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -146,7 +146,7 @@ const luaL_reg LuaPerlinNoise::methods[] = { LuaPerlinNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, int seed, v3s16 size) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) { m_is3d = size.Z > 1; np = *params; diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 492eb7550..40bfd1315 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -79,7 +79,7 @@ class LuaPerlinNoiseMap : public ModApiBase { static int l_getMapSlice(lua_State *L); public: - LuaPerlinNoiseMap(NoiseParams *np, int seed, v3s16 size); + LuaPerlinNoiseMap(NoiseParams *np, s32 seed, v3s16 size); ~LuaPerlinNoiseMap(); @@ -111,7 +111,7 @@ private: static int l_next(lua_State *L); public: - LuaPseudoRandom(int seed) : + LuaPseudoRandom(s32 seed) : m_pseudo(seed) {} // LuaPseudoRandom(seed) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 6d6614e7d..2a8b8a64e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -107,7 +107,7 @@ PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) return (PlayerSAO*)obj; } -Player* ObjectRef::getplayer(ObjectRef *ref) +RemotePlayer *ObjectRef::getplayer(ObjectRef *ref) { PlayerSAO *playersao = getplayersao(ref); if (playersao == NULL) @@ -137,11 +137,12 @@ int ObjectRef::l_remove(lua_State *L) if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) return 0; - std::set<int> child_ids = co->getAttachmentChildIds(); - std::set<int>::iterator it; + UNORDERED_SET<int> child_ids = co->getAttachmentChildIds(); + UNORDERED_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)); + // Child can be NULL if it was deleted earlier + if (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; @@ -508,7 +509,7 @@ int ObjectRef::l_set_local_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; // Do it @@ -533,7 +534,7 @@ int ObjectRef::l_get_local_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -554,7 +555,7 @@ int ObjectRef::l_set_eye_offset(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; // Do it @@ -584,7 +585,7 @@ int ObjectRef::l_get_eye_offset(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; // Do it @@ -606,10 +607,10 @@ int ObjectRef::l_set_bone_position(lua_State *L) bone = lua_tostring(L, 2); v3f position = v3f(0, 0, 0); if (!lua_isnil(L, 3)) - position = read_v3f(L, 3); + position = check_v3f(L, 3); v3f rotation = v3f(0, 0, 0); if (!lua_isnil(L, 4)) - rotation = read_v3f(L, 4); + rotation = check_v3f(L, 4); co->setBonePosition(bone, position, rotation); return 0; } @@ -762,7 +763,7 @@ int ObjectRef::l_is_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); lua_pushboolean(L, (player != NULL)); return 1; } @@ -973,7 +974,7 @@ int ObjectRef::l_is_player_connected(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); lua_pushboolean(L, (player != NULL && player->peer_id != 0)); return 1; } @@ -983,7 +984,7 @@ int ObjectRef::l_get_player_name(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) { lua_pushlstring(L, "", 0); return 1; @@ -998,7 +999,7 @@ int ObjectRef::l_get_player_velocity(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) { lua_pushnil(L); return 1; @@ -1013,63 +1014,133 @@ 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; + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; // Do it - float pitch = player->getRadPitch(); - float yaw = player->getRadYaw(); + float pitch = co->getRadPitchDep(); + float yaw = co->getRadYawDep(); v3f v(cos(pitch)*cos(yaw), sin(pitch), cos(pitch)*sin(yaw)); push_v3f(L, v); return 1; } +// DEPRECATED // get_look_pitch(self) int ObjectRef::l_get_look_pitch(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, + "Deprecated call to get_look_pitch, use get_look_vertical instead"); + ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); - if (player == NULL) return 0; + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; // Do it - lua_pushnumber(L, player->getRadPitch()); + lua_pushnumber(L, co->getRadPitchDep()); return 1; } +// DEPRECATED // get_look_yaw(self) int ObjectRef::l_get_look_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, + "Deprecated call to get_look_yaw, use get_look_horizontal instead"); + ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); - if (player == NULL) return 0; + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; + // Do it + lua_pushnumber(L, co->getRadYawDep()); + return 1; +} + +// get_look_pitch2(self) +int ObjectRef::l_get_look_vertical(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; + // Do it + lua_pushnumber(L, co->getRadPitch()); + return 1; +} + +// get_look_yaw2(self) +int ObjectRef::l_get_look_horizontal(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; + // Do it + lua_pushnumber(L, co->getRadYaw()); + return 1; +} + +// set_look_vertical(self, radians) +int ObjectRef::l_set_look_vertical(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; + float pitch = luaL_checknumber(L, 2) * core::RADTODEG; // Do it - lua_pushnumber(L, player->getRadYaw()); + co->setPitchAndSend(pitch); return 1; } +// set_look_horizontal(self, radians) +int ObjectRef::l_set_look_horizontal(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO* co = getplayersao(ref); + if (co == NULL) return 0; + float yaw = luaL_checknumber(L, 2) * core::RADTODEG; + // Do it + co->setYawAndSend(yaw); + return 1; +} + +// DEPRECATED // set_look_pitch(self, radians) int ObjectRef::l_set_look_pitch(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, + "Deprecated call to set_look_pitch, use set_look_vertical instead."); + ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); if (co == NULL) return 0; float pitch = luaL_checknumber(L, 2) * core::RADTODEG; // Do it - co->setPitch(pitch); + co->setPitchAndSend(pitch); return 1; } +// DEPRECATED // set_look_yaw(self, radians) int ObjectRef::l_set_look_yaw(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, + "Deprecated call to set_look_yaw, use set_look_horizontal instead."); + ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); if (co == NULL) return 0; float yaw = luaL_checknumber(L, 2) * core::RADTODEG; // Do it - co->setYaw(yaw); + co->setYawAndSend(yaw); return 1; } @@ -1109,7 +1180,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; std::string formspec = luaL_checkstring(L, 2); @@ -1124,7 +1195,7 @@ int ObjectRef::l_get_inventory_formspec(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; std::string formspec = player->inventory_formspec; @@ -1137,13 +1208,13 @@ int ObjectRef::l_get_player_control(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) { lua_pushlstring(L, "", 0); return 1; } - // Do it - PlayerControl control = player->getPlayerControl(); + + const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); lua_pushboolean(L, control.up); lua_setfield(L, -2, "up"); @@ -1171,7 +1242,7 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) { lua_pushlstring(L, "", 0); return 1; @@ -1186,7 +1257,7 @@ int ObjectRef::l_hud_add(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1249,7 +1320,7 @@ int ObjectRef::l_hud_remove(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1269,7 +1340,7 @@ int ObjectRef::l_hud_change(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1346,7 +1417,7 @@ int ObjectRef::l_hud_get(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1397,7 +1468,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1423,7 +1494,7 @@ int ObjectRef::l_hud_get_flags(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1449,7 +1520,7 @@ int ObjectRef::l_hud_set_hotbar_itemcount(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1467,7 +1538,7 @@ int ObjectRef::l_hud_get_hotbar_itemcount(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1482,7 +1553,7 @@ int ObjectRef::l_hud_set_hotbar_image(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1497,7 +1568,7 @@ int ObjectRef::l_hud_get_hotbar_image(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1511,7 +1582,7 @@ int ObjectRef::l_hud_set_hotbar_selected_image(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1526,11 +1597,11 @@ int ObjectRef::l_hud_get_hotbar_selected_image(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; - std::string name = getServer(L)->hudGetHotbarSelectedImage(player); + const std::string &name = getServer(L)->hudGetHotbarSelectedImage(player); lua_pushlstring(L, name.c_str(), name.size()); return 1; } @@ -1540,7 +1611,7 @@ int ObjectRef::l_set_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1579,7 +1650,7 @@ int ObjectRef::l_get_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; video::SColor bgcolor(255, 255, 255, 255); @@ -1607,7 +1678,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1630,7 +1701,7 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); + RemotePlayer *player = getplayer(ref); if (player == NULL) return 0; @@ -1754,6 +1825,10 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, get_look_dir), luamethod(ObjectRef, get_look_pitch), luamethod(ObjectRef, get_look_yaw), + luamethod(ObjectRef, get_look_vertical), + luamethod(ObjectRef, get_look_horizontal), + luamethod(ObjectRef, set_look_horizontal), + luamethod(ObjectRef, set_look_vertical), luamethod(ObjectRef, set_look_yaw), luamethod(ObjectRef, set_look_pitch), luamethod(ObjectRef, get_breath), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index a4457cc05..09f10e417 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class ServerActiveObject; class LuaEntitySAO; class PlayerSAO; -class Player; +class RemotePlayer; /* ObjectRef @@ -47,7 +47,7 @@ private: static PlayerSAO* getplayersao(ObjectRef *ref); - static Player* getplayer(ObjectRef *ref); + static RemotePlayer *getplayer(ObjectRef *ref); // Exported functions @@ -189,15 +189,31 @@ private: // get_look_dir(self) static int l_get_look_dir(lua_State *L); + // DEPRECATED // get_look_pitch(self) static int l_get_look_pitch(lua_State *L); + // DEPRECATED // get_look_yaw(self) static int l_get_look_yaw(lua_State *L); + // get_look_pitch2(self) + static int l_get_look_vertical(lua_State *L); + + // get_look_yaw2(self) + static int l_get_look_horizontal(lua_State *L); + + // set_look_vertical(self, radians) + static int l_set_look_vertical(lua_State *L); + + // set_look_horizontal(self, radians) + static int l_set_look_horizontal(lua_State *L); + + // DEPRECATED // set_look_pitch(self, radians) static int l_set_look_pitch(lua_State *L); + // DEPRECATED // set_look_yaw(self, radians) static int l_set_look_yaw(lua_State *L); diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index f6c1725de..667ac7272 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -18,16 +18,20 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "lua_api/l_particles.h" +#include "lua_api/l_object.h" #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "server.h" +#include "particles.h" // add_particle({pos=, velocity=, acceleration=, expirationtime=, -// size=, collisiondetection=, vertical=, texture=, player=}) +// size=, collisiondetection=, collision_removal=, vertical=, +// texture=, player=}) // pos/velocity/acceleration = {x=num, y=num, z=num} // expirationtime = num (seconds) // size = num // collisiondetection = bool +// collision_removal = bool // vertical = bool // texture = e.g."default_wood.png" int ModApiParticles::l_add_particle(lua_State *L) @@ -41,8 +45,8 @@ int ModApiParticles::l_add_particle(lua_State *L) float expirationtime, size; expirationtime = size = 1; - bool collisiondetection, vertical; - collisiondetection = vertical = false; + bool collisiondetection, vertical, collision_removal; + collisiondetection = vertical = collision_removal = false; std::string texture = ""; std::string playername = ""; @@ -94,12 +98,14 @@ int ModApiParticles::l_add_particle(lua_State *L) size = getfloatfield_default(L, 1, "size", 1); collisiondetection = getboolfield_default(L, 1, "collisiondetection", collisiondetection); + collision_removal = getboolfield_default(L, 1, + "collision_removal", collision_removal); vertical = getboolfield_default(L, 1, "vertical", vertical); texture = getstringfield_default(L, 1, "texture", ""); playername = getstringfield_default(L, 1, "playername", ""); } - getServer(L)->spawnParticle(playername, pos, vel, acc, - expirationtime, size, collisiondetection, vertical, texture); + getServer(L)->spawnParticle(playername, pos, vel, acc, expirationtime, size, + collisiondetection, collision_removal, vertical, texture); return 1; } @@ -110,6 +116,7 @@ int ModApiParticles::l_add_particle(lua_State *L) // minexptime=, maxexptime=, // minsize=, maxsize=, // collisiondetection=, +// collision_removal=, // vertical=, // texture=, // player=}) @@ -117,6 +124,7 @@ int ModApiParticles::l_add_particle(lua_State *L) // minexptime/maxexptime = num (seconds) // minsize/maxsize = num // collisiondetection = bool +// collision_removal = bool // vertical = bool // texture = e.g."default_wood.png" int ModApiParticles::l_add_particlespawner(lua_State *L) @@ -129,8 +137,9 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) minpos= maxpos= minvel= maxvel= minacc= maxacc= v3f(0, 0, 0); float time, minexptime, maxexptime, minsize, maxsize; time= minexptime= maxexptime= minsize= maxsize= 1; - bool collisiondetection, vertical; - collisiondetection= vertical= false; + bool collisiondetection, vertical, collision_removal; + collisiondetection = vertical = collision_removal = false; + ServerActiveObject *attached = NULL; std::string texture = ""; std::string playername = ""; @@ -189,6 +198,16 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) maxsize = getfloatfield_default(L, 1, "maxsize", maxsize); collisiondetection = getboolfield_default(L, 1, "collisiondetection", collisiondetection); + collision_removal = getboolfield_default(L, 1, + "collision_removal", collision_removal); + + lua_getfield(L, 1, "attached"); + if (!lua_isnil(L, -1)) { + ObjectRef *ref = ObjectRef::checkobject(L, -1); + lua_pop(L, 1); + attached = ObjectRef::getobject(ref); + } + vertical = getboolfield_default(L, 1, "vertical", vertical); texture = getstringfield_default(L, 1, "texture", ""); playername = getstringfield_default(L, 1, "playername", ""); @@ -201,6 +220,8 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) minexptime, maxexptime, minsize, maxsize, collisiondetection, + collision_removal, + attached, vertical, texture, playername); lua_pushnumber(L, id); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 59d3f5c70..b6d44e0ff 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -45,6 +45,15 @@ int ModApiServer::l_get_server_status(lua_State *L) return 1; } +// get_server_uptime() +int ModApiServer::l_get_server_uptime(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + lua_pushnumber(L, getServer(L)->getUptime()); + return 1; +} + + // print(text) int ModApiServer::l_print(lua_State *L) { @@ -106,7 +115,7 @@ int ModApiServer::l_get_player_ip(lua_State *L) { NO_MAP_LOCK_REQUIRED; const char * name = luaL_checkstring(L, 1); - Player *player = getEnv(L)->getPlayer(name); + RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); if(player == NULL) { lua_pushnil(L); // no such player @@ -133,9 +142,8 @@ int ModApiServer::l_get_player_information(lua_State *L) NO_MAP_LOCK_REQUIRED; const char * name = luaL_checkstring(L, 1); - Player *player = getEnv(L)->getPlayer(name); - if(player == NULL) - { + RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); + if (player == NULL) { lua_pushnil(L); // no such player return 1; } @@ -278,15 +286,15 @@ int ModApiServer::l_ban_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; const char * name = luaL_checkstring(L, 1); - Player *player = getEnv(L)->getPlayer(name); - if(player == NULL) - { + RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); + if (player == NULL) { lua_pushboolean(L, false); // no such player return 1; } try { - Address addr = getServer(L)->getPeerAddress(getEnv(L)->getPlayer(name)->peer_id); + Address addr = getServer(L)->getPeerAddress( + dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name)->peer_id); std::string ip_str = addr.serializeString(); getServer(L)->setIpBanned(ip_str, name); } @@ -314,9 +322,9 @@ int ModApiServer::l_kick_player(lua_State *L) { message = "Kicked."; } - Player *player = getEnv(L)->getPlayer(name); - if (player == NULL) - { + + RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); + if (player == NULL) { lua_pushboolean(L, false); // No such player return 1; } @@ -508,6 +516,7 @@ void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); API_FCT(get_server_status); + API_FCT(get_server_uptime); API_FCT(get_worldpath); API_FCT(is_singleplayer); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 06a5ddc24..1ad46d440 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -30,6 +30,9 @@ private: // get_server_status() static int l_get_server_status(lua_State *L); + // get_server_uptime() + static int l_get_server_uptime(lua_State *L); + // get_worldpath() static int l_get_worldpath(lua_State *L); diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 35b82b435..d3fe03005 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -118,6 +118,11 @@ int LuaSettings::l_write(lua_State* L) NO_MAP_LOCK_REQUIRED; LuaSettings* o = checkobject(L, 1); + if (!o->m_write_allowed) { + throw LuaError("Settings: writing " + o->m_filename + + " not allowed with mod security on."); + } + bool success = o->m_settings->updateConfigFile(o->m_filename.c_str()); lua_pushboolean(L, success); @@ -142,8 +147,9 @@ int LuaSettings::l_to_table(lua_State* L) return 1; } -LuaSettings::LuaSettings(const char* filename) +LuaSettings::LuaSettings(const char* filename, bool write_allowed) { + m_write_allowed = write_allowed; m_filename = std::string(filename); m_settings = new Settings(); @@ -188,9 +194,10 @@ void LuaSettings::Register(lua_State* L) int LuaSettings::create_object(lua_State* L) { NO_MAP_LOCK_REQUIRED; + bool write_allowed = true; const char* filename = luaL_checkstring(L, 1); - CHECK_SECURE_PATH_OPTIONAL(L, filename); - LuaSettings* o = new LuaSettings(filename); + CHECK_SECURE_PATH_POSSIBLE_WRITE(L, filename, &write_allowed); + LuaSettings* o = new LuaSettings(filename, write_allowed); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); diff --git a/src/script/lua_api/l_settings.h b/src/script/lua_api/l_settings.h index cb0c09a73..bca333e31 100644 --- a/src/script/lua_api/l_settings.h +++ b/src/script/lua_api/l_settings.h @@ -53,11 +53,12 @@ private: // to_table(self) -> {[key1]=value1,...} static int l_to_table(lua_State* L); + bool m_write_allowed; Settings* m_settings; std::string m_filename; public: - LuaSettings(const char* filename); + LuaSettings(const char* filename, bool write_allowed); ~LuaSettings(); // LuaSettings(filename) diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index c3e6c8964..26e2b985c 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "cpp_api/s_async.h" #include "serialization.h" -#include "json/json.h" +#include <json/json.h> #include "cpp_api/s_security.h" #include "porting.h" #include "debug.h" @@ -32,8 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "settings.h" #include "util/auth.h" +#include "util/base64.h" +#include "config.h" +#include "version.h" #include <algorithm> + // log([level,] text) // Writes a line to the logger. // The one-argument version logs to infostream. @@ -219,7 +223,7 @@ int ModApiUtil::l_write_json(lua_State *L) int ModApiUtil::l_get_dig_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::map<std::string, int> groups; + ItemGroupList groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); if(lua_isnoneornil(L, 3)) @@ -234,7 +238,7 @@ int ModApiUtil::l_get_dig_params(lua_State *L) int ModApiUtil::l_get_hit_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::map<std::string, int> groups; + UNORDERED_MAP<std::string, int> groups; read_groups(L, 1, groups); ToolCapabilities tp = read_tool_capabilities(L, 2); if(lua_isnoneornil(L, 3)) @@ -245,6 +249,35 @@ int ModApiUtil::l_get_hit_params(lua_State *L) return 1; } +// check_password_entry(name, entry, password) +int ModApiUtil::l_check_password_entry(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + std::string name = luaL_checkstring(L, 1); + std::string entry = luaL_checkstring(L, 2); + std::string password = luaL_checkstring(L, 3); + + if (base64_is_valid(entry)) { + std::string hash = translate_password(name, password); + lua_pushboolean(L, hash == entry); + return 1; + } + + std::string salt; + std::string verifier; + + if (!decode_srp_verifier_and_salt(entry, &verifier, &salt)) { + // invalid format + warningstream << "Invalid password format for " << name << std::endl; + lua_pushboolean(L, false); + return 1; + } + std::string gen_verifier = generate_srp_verifier(name, password, salt); + + lua_pushboolean(L, gen_verifier == verifier); + return 1; +} + // get_password_hash(name, raw_password) int ModApiUtil::l_get_password_hash(lua_State *L) { @@ -272,12 +305,14 @@ int ModApiUtil::l_is_yes(lua_State *L) return 1; } +// get_builtin_path() int ModApiUtil::l_get_builtin_path(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string path = porting::path_share + DIR_DELIM + "builtin"; lua_pushstring(L, path.c_str()); + return 1; } @@ -320,12 +355,40 @@ int ModApiUtil::l_decompress(lua_State *L) return 1; } +// encode_base64(string) +int ModApiUtil::l_encode_base64(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + size_t size; + const char *data = luaL_checklstring(L, 1, &size); + + std::string out = base64_encode((const unsigned char *)(data), size); + + lua_pushlstring(L, out.data(), out.size()); + return 1; +} + +// decode_base64(string) +int ModApiUtil::l_decode_base64(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + size_t size; + const char *data = luaL_checklstring(L, 1, &size); + + std::string out = base64_decode(std::string(data, size)); + + lua_pushlstring(L, out.data(), out.size()); + 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); + CHECK_SECURE_PATH(L, path, true); lua_pushboolean(L, fs::CreateAllDirs(path)); return 1; } @@ -337,7 +400,7 @@ int ModApiUtil::l_get_dir_list(lua_State *L) 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); + CHECK_SECURE_PATH(L, path, false); std::vector<fs::DirListNode> list = fs::GetDirListing(path); @@ -388,8 +451,9 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) // Check secure.trusted_mods const char *mod_name = lua_tostring(L, -1); std::string trusted_mods = g_settings->get("secure.trusted_mods"); - trusted_mods.erase(std::remove(trusted_mods.begin(), - trusted_mods.end(), ' '), trusted_mods.end()); + trusted_mods.erase(std::remove_if(trusted_mods.begin(), + trusted_mods.end(), static_cast<int(*)(int)>(&std::isspace)), + trusted_mods.end()); std::vector<std::string> mod_list = str_split(trusted_mods, ','); if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) { @@ -401,6 +465,26 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) return 1; } +// get_version() +int ModApiUtil::l_get_version(lua_State *L) +{ + lua_createtable(L, 0, 3); + int table = lua_gettop(L); + + lua_pushstring(L, PROJECT_NAME_C); + lua_setfield(L, table, "project"); + + lua_pushstring(L, g_version_string); + lua_setfield(L, table, "string"); + + if (strcmp(g_version_string, g_version_hash)) { + lua_pushstring(L, g_version_hash); + lua_setfield(L, table, "hash"); + } + + return 1; +} + void ModApiUtil::Initialize(lua_State *L, int top) { @@ -420,6 +504,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_dig_params); API_FCT(get_hit_params); + API_FCT(check_password_entry); API_FCT(get_password_hash); API_FCT(is_yes); @@ -433,6 +518,11 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_dir_list); API_FCT(request_insecure_environment); + + API_FCT(encode_base64); + API_FCT(decode_base64); + + API_FCT(get_version); } void ModApiUtil::InitializeAsync(AsyncEngine& engine) @@ -459,5 +549,10 @@ void ModApiUtil::InitializeAsync(AsyncEngine& engine) ASYNC_API_FCT(mkdir); ASYNC_API_FCT(get_dir_list); + + ASYNC_API_FCT(encode_base64); + ASYNC_API_FCT(decode_base64); + + ASYNC_API_FCT(get_version); } diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 6fac7e7eb..9910704b3 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -71,6 +71,9 @@ private: // get_hit_params(groups, tool_capabilities[, time_from_last_punch]) static int l_get_hit_params(lua_State *L); + // check_password_entry(name, entry, password) + static int l_check_password_entry(lua_State *L); + // get_password_hash(name, raw_password) static int l_get_password_hash(lua_State *L); @@ -95,6 +98,15 @@ private: // request_insecure_environment() static int l_request_insecure_environment(lua_State *L); + // encode_base64(string) + static int l_encode_base64(lua_State *L); + + // decode_base64(string) + static int l_decode_base64(lua_State *L); + + // get_version() + static int l_get_version(lua_State *L); + public: static void Initialize(lua_State *L, int top); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index f13866408..bdf720f0a 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -190,7 +190,7 @@ int LuaVoxelManip::l_calc_lighting(lua_State *L) Mapgen mg; mg.vm = vm; mg.ndef = ndef; - mg.water_level = emerge->params.water_level; + mg.water_level = emerge->mgparams->water_level; mg.calcLighting(pmin, pmax, fpmin, fpmax, propagate_shadow); @@ -277,11 +277,17 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); + bool use_buffer = lua_istable(L, 2); + MMVManip *vm = o->vm; u32 volume = vm->m_area.getVolume(); - lua_newtable(L); + if (use_buffer) + lua_pushvalue(L, 2); + else + lua_newtable(L); + for (u32 i = 0; i != volume; i++) { lua_Integer param2 = vm->m_data[i].param2; lua_pushinteger(L, param2); diff --git a/src/serialization.cpp b/src/serialization.cpp index 79f66fcae..d30e83726 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" #include "util/serialize.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(WIN32_NO_ZLIB_WINAPI) #define ZLIB_WINAPI #endif #include "zlib.h" diff --git a/src/server.cpp b/src/server.cpp index a3b686c25..c9d5c7129 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -184,9 +184,7 @@ Server::Server( { m_liquid_transform_timer = 0.0; m_liquid_transform_every = 1.0; - m_print_info_timer = 0.0; m_masterserver_timer = 0.0; - m_objectdata_timer = 0.0; m_emergethread_trigger_timer = 0.0; m_savemap_timer = 0.0; @@ -266,9 +264,6 @@ Server::Server( //lock environment MutexAutoLock envlock(m_env_mutex); - // Load mapgen params from Settings - m_emerge->loadMapgenParams(); - // Create the Map (loads map_meta.txt, overriding configured mapgen params) ServerMap *servermap = new ServerMap(path_world, this, m_emerge); @@ -331,8 +326,11 @@ Server::Server( m_clients.setEnv(m_env); + if (!servermap->settings_mgr.makeMapgenParams()) + FATAL_ERROR("Couldn't create any mapgen type"); + // Initialize mapgens - m_emerge->initMapgens(); + m_emerge->initMapgens(servermap->getMapgenParams()); m_enable_rollback_recording = g_settings->getBool("enable_rollback_recording"); if (m_enable_rollback_recording) { @@ -358,6 +356,7 @@ Server::Server( add_legacy_abms(m_env, m_nodedef); m_liquid_transform_every = g_settings->getFloat("liquid_update"); + m_max_chatmessage_length = g_settings->getU16("chat_message_max_size"); } Server::~Server() @@ -402,11 +401,8 @@ Server::~Server() m_emerge->stopThreads(); // Delete things in the reverse order of creation - delete m_env; - - // N.B. the EmergeManager should be deleted after the Environment since Map - // depends on EmergeManager to write its current params to the map meta delete m_emerge; + delete m_env; delete m_rollback; delete m_banmanager; delete m_event; @@ -655,7 +651,7 @@ void Server::AsyncRunStep(bool initial_step) m_env->getGameTime(), m_lag, m_gamespec.id, - m_emerge->params.mg_name, + Mapgen::getMapgenName(m_emerge->mgparams->mgtype), m_mods); counter = 0.01; } @@ -671,7 +667,7 @@ void Server::AsyncRunStep(bool initial_step) MutexAutoLock envlock(m_env_mutex); m_clients.lock(); - std::map<u16, RemoteClient*> clients = m_clients.getClientList(); + UNORDERED_MAP<u16, RemoteClient*> clients = m_clients.getClientList(); ScopeProfiler sp(g_profiler, "Server: checking added and deleted objs"); // Radius inside which objects are active @@ -687,8 +683,7 @@ void Server::AsyncRunStep(bool initial_step) if (player_radius == 0 && is_transfer_limited) player_radius = radius; - for (std::map<u16, RemoteClient*>::iterator - i = clients.begin(); + for (UNORDERED_MAP<u16, RemoteClient*>::iterator i = clients.begin(); i != clients.end(); ++i) { RemoteClient *client = i->second; @@ -697,8 +692,8 @@ void Server::AsyncRunStep(bool initial_step) if (client->getState() < CS_DefinitionsSent) continue; - Player *player = m_env->getPlayer(client->peer_id); - if(player == NULL) { + RemotePlayer *player = m_env->getPlayer(client->peer_id); + if (player == NULL) { // This can happen if the client timeouts somehow /*warningstream<<FUNCTION_NAME<<": Client " <<client->peer_id @@ -706,11 +701,19 @@ void Server::AsyncRunStep(bool initial_step) continue; } + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) + continue; + + s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE); + if (my_radius <= 0) my_radius = radius; + //infostream << "Server: Active Radius " << my_radius << std::endl; + std::queue<u16> removed_objects; std::queue<u16> added_objects; - m_env->getRemovedActiveObjects(player, radius, player_radius, + m_env->getRemovedActiveObjects(playersao, my_radius, player_radius, client->m_known_objects, removed_objects); - m_env->getAddedActiveObjects(player, radius, player_radius, + m_env->getAddedActiveObjects(playersao, my_radius, player_radius, client->m_known_objects, added_objects); // Ignore if nothing happened @@ -797,7 +800,7 @@ void Server::AsyncRunStep(bool initial_step) // Key = object id // Value = data sent by object - std::map<u16, std::vector<ActiveObjectMessage>* > buffered_messages; + UNORDERED_MAP<u16, std::vector<ActiveObjectMessage>* > buffered_messages; // Get active object messages from environment for(;;) { @@ -806,7 +809,7 @@ void Server::AsyncRunStep(bool initial_step) break; std::vector<ActiveObjectMessage>* message_list = NULL; - std::map<u16, std::vector<ActiveObjectMessage>* >::iterator n; + UNORDERED_MAP<u16, std::vector<ActiveObjectMessage>* >::iterator n; n = buffered_messages.find(aom.id); if (n == buffered_messages.end()) { message_list = new std::vector<ActiveObjectMessage>; @@ -819,16 +822,15 @@ void Server::AsyncRunStep(bool initial_step) } m_clients.lock(); - std::map<u16, RemoteClient*> clients = m_clients.getClientList(); + UNORDERED_MAP<u16, RemoteClient*> clients = m_clients.getClientList(); // Route data to every client - for (std::map<u16, RemoteClient*>::iterator - i = clients.begin(); + for (UNORDERED_MAP<u16, RemoteClient*>::iterator i = clients.begin(); 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::vector<ActiveObjectMessage>* >::iterator + for (UNORDERED_MAP<u16, std::vector<ActiveObjectMessage>* >::iterator j = buffered_messages.begin(); j != buffered_messages.end(); ++j) { // If object is not known by client, skip it @@ -872,7 +874,7 @@ void Server::AsyncRunStep(bool initial_step) m_clients.unlock(); // Clear buffered_messages - for(std::map<u16, std::vector<ActiveObjectMessage>* >::iterator + for (UNORDERED_MAP<u16, std::vector<ActiveObjectMessage>* >::iterator i = buffered_messages.begin(); i != buffered_messages.end(); ++i) { delete i->second; @@ -1114,30 +1116,13 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) SendPlayerBreath(peer_id); // Show death screen if necessary - if(player->isDead()) + if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); // Note things in chat if not in simple singleplayer mode if(!m_simple_singleplayer_mode) { // Send information about server to player in chat SendChatMessage(peer_id, getStatusString()); - - // Send information about joining in chat - { - std::string name = "unknown"; - Player *player = m_env->getPlayer(peer_id); - if(player != NULL) - name = player->getName(); - - std::wstring message; - message += L"*** "; - message += narrow_to_wide(name); - message += L" joined the game."; - SendChatMessage(PEER_ID_INEXISTENT,message); - if (m_admin_chat) - m_admin_chat->outgoing_queue.push_back( - new ChatEventNick(CET_NICK_ADD, name)); - } } Address addr = getPeerAddress(player->peer_id); std::string ip_str = addr.serializeString(); @@ -1146,11 +1131,11 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) Print out action */ { - std::vector<std::string> names = m_clients.getPlayerNames(); + const std::vector<std::string> &names = m_clients.getPlayerNames(); - actionstream<<player->getName() <<" joins game. List of players: "; + actionstream << player->getName() << " joins game. List of players: "; - for (std::vector<std::string>::iterator i = names.begin(); + for (std::vector<std::string>::const_iterator i = names.begin(); i != names.end(); ++i) { actionstream << *i << " "; } @@ -1277,7 +1262,7 @@ Inventory* Server::getInventory(const InventoryLocation &loc) break; case InventoryLocation::PLAYER: { - Player *player = m_env->getPlayer(loc.name.c_str()); + RemotePlayer *player = dynamic_cast<RemotePlayer *>(m_env->getPlayer(loc.name.c_str())); if(!player) return NULL; PlayerSAO *playersao = player->getPlayerSAO(); @@ -1317,9 +1302,12 @@ void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend) if (!playerSend) return; - Player *player = m_env->getPlayer(loc.name.c_str()); - if(!player) + RemotePlayer *player = + dynamic_cast<RemotePlayer *>(m_env->getPlayer(loc.name.c_str())); + + if (!player) return; + PlayerSAO *playersao = player->getPlayerSAO(); if(!playersao) return; @@ -1663,8 +1651,12 @@ void Server::SendShowFormspecMessage(u16 peer_id, const std::string &formspec, DSTACK(FUNCTION_NAME); NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0 , peer_id); - - pkt.putLongString(FORMSPEC_VERSION_STRING + formspec); + if (formspec == "" ){ + //the client should close the formspec + pkt.putLongString(""); + } else { + pkt.putLongString(FORMSPEC_VERSION_STRING + formspec); + } pkt << formname; Send(&pkt); @@ -1673,7 +1665,8 @@ void Server::SendShowFormspecMessage(u16 peer_id, const std::string &formspec, // Spawns a particle on peer with peer_id void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, bool collisiondetection, - bool vertical, std::string texture) + bool collision_removal, + bool vertical, const std::string &texture) { DSTACK(FUNCTION_NAME); @@ -1683,6 +1676,7 @@ void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f accelerat << size << collisiondetection; pkt.putLongString(texture); pkt << vertical; + pkt << collision_removal; if (peer_id != PEER_ID_INEXISTENT) { Send(&pkt); @@ -1695,7 +1689,8 @@ void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f accelerat // Adds a ParticleSpawner on peer with peer_id void Server::SendAddParticleSpawner(u16 peer_id, 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) + float minsize, float maxsize, bool collisiondetection, bool collision_removal, + u16 attached_id, bool vertical, const std::string &texture, u32 id) { DSTACK(FUNCTION_NAME); @@ -1708,6 +1703,8 @@ void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3 pkt.putLongString(texture); pkt << id << vertical; + pkt << collision_removal; + pkt << attached_id; if (peer_id != PEER_ID_INEXISTENT) { Send(&pkt); @@ -1873,20 +1870,20 @@ void Server::SendPlayerBreath(u16 peer_id) void Server::SendMovePlayer(u16 peer_id) { DSTACK(FUNCTION_NAME); - Player *player = m_env->getPlayer(peer_id); + RemotePlayer *player = m_env->getPlayer(peer_id); assert(player); + PlayerSAO *sao = player->getPlayerSAO(); + assert(sao); NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); - pkt << player->getPosition() << player->getPitch() << player->getYaw(); + pkt << sao->getBasePosition() << sao->getPitch() << sao->getYaw(); { - v3f pos = player->getPosition(); - f32 pitch = player->getPitch(); - f32 yaw = player->getYaw(); + v3f pos = sao->getBasePosition(); verbosestream << "Server: Sending TOCLIENT_MOVE_PLAYER" << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" - << " pitch=" << pitch - << " yaw=" << yaw + << " pitch=" << sao->getPitch() + << " yaw=" << sao->getYaw() << std::endl; } @@ -1912,7 +1909,7 @@ void Server::SendEyeOffset(u16 peer_id, v3f first, v3f third) } void Server::SendPlayerPrivileges(u16 peer_id) { - Player *player = m_env->getPlayer(peer_id); + RemotePlayer *player = m_env->getPlayer(peer_id); assert(player); if(player->peer_id == PEER_ID_INEXISTENT) return; @@ -1933,7 +1930,7 @@ void Server::SendPlayerPrivileges(u16 peer_id) void Server::SendPlayerInventoryFormspec(u16 peer_id) { - Player *player = m_env->getPlayer(peer_id); + RemotePlayer *player = m_env->getPlayer(peer_id); assert(player); if(player->peer_id == PEER_ID_INEXISTENT) return; @@ -1978,7 +1975,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, std::vector<u16> dst_clients; if(params.to_player != "") { - Player *player = m_env->getPlayer(params.to_player.c_str()); + RemotePlayer *player = m_env->getPlayer(params.to_player.c_str()); if(!player){ infostream<<"Server::playSound: Player \""<<params.to_player <<"\" not found"<<std::endl; @@ -1994,14 +1991,17 @@ s32 Server::playSound(const SimpleSoundSpec &spec, else { 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); - if(!player) + for (std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { + RemotePlayer *player = m_env->getPlayer(*i); + if (!player) continue; - if(pos_exists) { - if(player->getPosition().getDistanceFrom(pos) > + PlayerSAO *sao = player->getPlayerSAO(); + if (!sao) + continue; + + if (pos_exists) { + if(sao->getBasePosition().getDistanceFrom(pos) > params.max_hear_distance) continue; } @@ -2033,16 +2033,15 @@ s32 Server::playSound(const SimpleSoundSpec &spec, void Server::stopSound(s32 handle) { // Get sound reference - std::map<s32, ServerPlayingSound>::iterator i = - m_playing_sounds.find(handle); - if(i == m_playing_sounds.end()) + UNORDERED_MAP<s32, ServerPlayingSound>::iterator i = m_playing_sounds.find(handle); + if (i == m_playing_sounds.end()) return; ServerPlayingSound &psound = i->second; NetworkPacket pkt(TOCLIENT_STOP_SOUND, 4); pkt << handle; - for(std::set<u16>::iterator i = psound.clients.begin(); + for (UNORDERED_SET<u16>::iterator i = psound.clients.begin(); i != psound.clients.end(); ++i) { // Send as reliable m_clients.send(*i, 0, &pkt, true); @@ -2061,14 +2060,17 @@ void Server::sendRemoveNode(v3s16 p, u16 ignore_id, pkt << p; std::vector<u16> clients = m_clients.getClientIDs(); - for(std::vector<u16>::iterator i = clients.begin(); - i != clients.end(); ++i) { + for (std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { if (far_players) { // Get player - if(Player *player = m_env->getPlayer(*i)) { + if (RemotePlayer *player = m_env->getPlayer(*i)) { + PlayerSAO *sao = player->getPlayerSAO(); + if (!sao) + continue; + // If player is far away, only set modified blocks not sent - v3f player_pos = player->getPosition(); - if(player_pos.getDistanceFrom(p_f) > maxd) { + v3f player_pos = sao->getBasePosition(); + if (player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(*i); continue; } @@ -2088,14 +2090,16 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, v3f p_f = intToFloat(p, BS); std::vector<u16> clients = m_clients.getClientIDs(); - for(std::vector<u16>::iterator i = clients.begin(); - i != clients.end(); ++i) { - - if(far_players) { + for(std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { + if (far_players) { // Get player - if(Player *player = m_env->getPlayer(*i)) { + if (RemotePlayer *player = m_env->getPlayer(*i)) { + PlayerSAO *sao = player->getPlayerSAO(); + if (!sao) + continue; + // If player is far away, only set modified blocks not sent - v3f player_pos = player->getPosition(); + v3f player_pos = sao->getBasePosition(); if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(*i); continue; @@ -2339,7 +2343,7 @@ void Server::sendMediaAnnouncement(u16 peer_id) NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); pkt << (u16) m_media.size(); - for (std::map<std::string, MediaInfo>::iterator i = m_media.begin(); + for (UNORDERED_MAP<std::string, MediaInfo>::iterator i = m_media.begin(); i != m_media.end(); ++i) { pkt << i->first << i->second.sha1_digest; } @@ -2384,7 +2388,7 @@ void Server::sendRequestedMedia(u16 peer_id, 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; @@ -2487,11 +2491,16 @@ void Server::sendDetachedInventory(const std::string &name, u16 peer_id) 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, &pkt, true); + const std::string &check = m_detached_inventories_player[name]; + if (peer_id == PEER_ID_INEXISTENT) { + if (check == "") + return m_clients.sendToAll(0, &pkt, true); + RemotePlayer *p = m_env->getPlayer(check.c_str()); + if (p) + m_clients.send(p->peer_id, 0, &pkt, true); + } else { + if (check == "" || getPlayerName(peer_id) == check) + Send(&pkt); } } @@ -2645,44 +2654,26 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) /* Clear references to playing sounds */ - for(std::map<s32, ServerPlayingSound>::iterator - i = m_playing_sounds.begin(); - i != m_playing_sounds.end();) - { + for (UNORDERED_MAP<s32, ServerPlayingSound>::iterator + i = m_playing_sounds.begin(); i != m_playing_sounds.end();) { ServerPlayingSound &psound = i->second; psound.clients.erase(peer_id); - if(psound.clients.empty()) + if (psound.clients.empty()) m_playing_sounds.erase(i++); else ++i; } - Player *player = m_env->getPlayer(peer_id); - - // Collect information about leaving in chat - { - if(player != NULL && reason != CDR_DENY) - { - std::wstring name = narrow_to_wide(player->getName()); - message += L"*** "; - message += name; - message += L" left the game."; - if(reason == CDR_TIMEOUT) - message += L" (timed out)"; - } - } + RemotePlayer *player = m_env->getPlayer(peer_id); /* Run scripts and remove from environment */ - { - if(player != NULL) - { - PlayerSAO *playersao = player->getPlayerSAO(); - assert(playersao); + if (player != NULL) { + PlayerSAO *playersao = player->getPlayerSAO(); + assert(playersao); - m_script->on_leaveplayer(playersao); + m_script->on_leaveplayer(playersao, reason == CDR_TIMEOUT); - playersao->disconnected(); - } + playersao->disconnected(); } /* @@ -2696,8 +2687,8 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) for(std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { // Get player - Player *player = m_env->getPlayer(*i); - if(!player) + RemotePlayer *player = m_env->getPlayer(*i); + if (!player) continue; // Get name of player @@ -2724,7 +2715,7 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) SendChatMessage(PEER_ID_INEXISTENT,message); } -void Server::UpdateCrafting(Player* player) +void Server::UpdateCrafting(RemotePlayer *player) { DSTACK(FUNCTION_NAME); @@ -2734,7 +2725,8 @@ void Server::UpdateCrafting(Player* player) loc.setPlayer(player->getName()); 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); + m_env->getScriptIface()->item_CraftPredict(preview, player->getPlayerSAO(), + (&player->inventory)->getList("craft"), loc); // Put the new preview in InventoryList *plist = player->inventory.getList("craftpreview"); @@ -2763,8 +2755,7 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) } std::wstring Server::handleChat(const std::string &name, const std::wstring &wname, - const std::wstring &wmessage, bool check_shout_priv, - u16 peer_id_to_avoid_sending) + const std::wstring &wmessage, bool check_shout_priv, RemotePlayer *player) { // If something goes wrong, this player is to blame RollbackScopeActor rollback_scope(m_rollback, @@ -2782,6 +2773,28 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna if (ate) return L""; + if (player) { + switch (player->canSendChatMessage()) { + case RPLAYER_CHATRESULT_FLOODING: { + std::wstringstream ws; + ws << L"You cannot send more messages. You are limited to " + << g_settings->getFloat("chat_message_limit_per_10sec") + << L" messages per 10 seconds."; + return ws.str(); + } + case RPLAYER_CHATRESULT_KICK: + DenyAccess_Legacy(player->peer_id, L"You have been kicked due to message flooding."); + return L""; + case RPLAYER_CHATRESULT_OK: break; + default: FATAL_ERROR("Unhandled chat filtering result found."); + } + } + + if (m_max_chatmessage_length > 0 && wmessage.length() > m_max_chatmessage_length) { + return L"Your message exceed the maximum chat message limit set on the server. " + L"It was refused. Send a shorter message"; + } + // Commands are implemented in Lua, so only catch invalid // commands that were not "eaten" and send an error back if (wmessage[0] == L'/') { @@ -2816,6 +2829,7 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna std::vector<u16> clients = m_clients.getClientIDs(); + u16 peer_id_to_avoid_sending = (player ? player->peer_id : PEER_ID_INEXISTENT); for (u16 i = 0; i < clients.size(); i++) { u16 cid = clients[i]; if (cid != peer_id_to_avoid_sending) @@ -2854,16 +2868,16 @@ RemoteClient* Server::getClientNoEx(u16 peer_id, ClientState state_min) std::string Server::getPlayerName(u16 peer_id) { - Player *player = m_env->getPlayer(peer_id); - if(player == NULL) + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player == NULL) return "[id="+itos(peer_id)+"]"; return player->getName(); } PlayerSAO* Server::getPlayerSAO(u16 peer_id) { - Player *player = m_env->getPlayer(peer_id); - if(player == NULL) + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player == NULL) return NULL; return player->getPlayerSAO(); } @@ -2882,13 +2896,12 @@ std::wstring Server::getStatusString() bool first = true; os<<L", clients={"; std::vector<u16> clients = m_clients.getClientIDs(); - for(std::vector<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); + RemotePlayer *player = m_env->getPlayer(*i); // Get name of player std::wstring name = L"unknown"; - if(player != NULL) + if (player != NULL) name = narrow_to_wide(player->getName()); // Add name to information string if(!first) @@ -2924,12 +2937,12 @@ void Server::reportPrivsModified(const std::string &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); + RemotePlayer *player = m_env->getPlayer(*i); reportPrivsModified(player->getName()); } } else { - Player *player = m_env->getPlayer(name.c_str()); - if(!player) + RemotePlayer *player = m_env->getPlayer(name.c_str()); + if (!player) return; SendPlayerPrivileges(player->peer_id); PlayerSAO *sao = player->getPlayerSAO(); @@ -2943,8 +2956,8 @@ void Server::reportPrivsModified(const std::string &name) void Server::reportInventoryFormspecModified(const std::string &name) { - Player *player = m_env->getPlayer(name.c_str()); - if(!player) + RemotePlayer *player = m_env->getPlayer(name.c_str()); + if (!player) return; SendPlayerInventoryFormspec(player->peer_id); } @@ -2974,7 +2987,7 @@ void Server::notifyPlayer(const char *name, const std::wstring &msg) m_admin_chat->outgoing_queue.push_back(new ChatEventChat("", msg)); } - Player *player = m_env->getPlayer(name); + RemotePlayer *player = m_env->getPlayer(name); if (!player) { return; } @@ -2992,7 +3005,7 @@ bool Server::showFormspec(const char *playername, const std::string &formspec, if (!m_env) return false; - Player *player = m_env->getPlayer(playername); + RemotePlayer *player = m_env->getPlayer(playername); if (!player) return false; @@ -3000,7 +3013,7 @@ bool Server::showFormspec(const char *playername, const std::string &formspec, return true; } -u32 Server::hudAdd(Player *player, HudElement *form) +u32 Server::hudAdd(RemotePlayer *player, HudElement *form) { if (!player) return -1; @@ -3012,7 +3025,7 @@ u32 Server::hudAdd(Player *player, HudElement *form) return id; } -bool Server::hudRemove(Player *player, u32 id) { +bool Server::hudRemove(RemotePlayer *player, u32 id) { if (!player) return false; @@ -3027,7 +3040,7 @@ bool Server::hudRemove(Player *player, u32 id) { return true; } -bool Server::hudChange(Player *player, u32 id, HudElementStat stat, void *data) +bool Server::hudChange(RemotePlayer *player, u32 id, HudElementStat stat, void *data) { if (!player) return false; @@ -3036,7 +3049,7 @@ 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(RemotePlayer *player, u32 flags, u32 mask) { if (!player) return false; @@ -3054,10 +3067,11 @@ bool Server::hudSetFlags(Player *player, u32 flags, u32 mask) return true; } -bool Server::hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount) +bool Server::hudSetHotbarItemcount(RemotePlayer *player, s32 hotbar_itemcount) { if (!player) return false; + if (hotbar_itemcount <= 0 || hotbar_itemcount > HUD_HOTBAR_ITEMCOUNT_MAX) return false; @@ -3068,14 +3082,7 @@ bool Server::hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount) return true; } -s32 Server::hudGetHotbarItemcount(Player *player) -{ - if (!player) - return 0; - return player->getHotbarItemcount(); -} - -void Server::hudSetHotbarImage(Player *player, std::string name) +void Server::hudSetHotbarImage(RemotePlayer *player, std::string name) { if (!player) return; @@ -3084,14 +3091,14 @@ void Server::hudSetHotbarImage(Player *player, std::string name) SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_IMAGE, name); } -std::string Server::hudGetHotbarImage(Player *player) +std::string Server::hudGetHotbarImage(RemotePlayer *player) { if (!player) return ""; return player->getHotbarImage(); } -void Server::hudSetHotbarSelectedImage(Player *player, std::string name) +void Server::hudSetHotbarSelectedImage(RemotePlayer *player, std::string name) { if (!player) return; @@ -3100,16 +3107,8 @@ void Server::hudSetHotbarSelectedImage(Player *player, std::string name) SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_SELECTED_IMAGE, name); } -std::string Server::hudGetHotbarSelectedImage(Player *player) -{ - if (!player) - return ""; - - return player->getHotbarSelectedImage(); -} - -bool Server::setLocalPlayerAnimations(Player *player, - v2s32 animation_frames[4], f32 frame_speed) +bool Server::setLocalPlayerAnimations(RemotePlayer *player, + v2s32 animation_frames[4], f32 frame_speed) { if (!player) return false; @@ -3119,7 +3118,7 @@ bool Server::setLocalPlayerAnimations(Player *player, return true; } -bool Server::setPlayerEyeOffset(Player *player, v3f first, v3f third) +bool Server::setPlayerEyeOffset(RemotePlayer *player, v3f first, v3f third) { if (!player) return false; @@ -3130,7 +3129,7 @@ bool Server::setPlayerEyeOffset(Player *player, v3f first, v3f third) return true; } -bool Server::setSky(Player *player, const video::SColor &bgcolor, +bool Server::setSky(RemotePlayer *player, const video::SColor &bgcolor, const std::string &type, const std::vector<std::string> ¶ms) { if (!player) @@ -3141,7 +3140,7 @@ bool Server::setSky(Player *player, const video::SColor &bgcolor, return true; } -bool Server::overrideDayNightRatio(Player *player, bool do_override, +bool Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, float ratio) { if (!player) @@ -3160,7 +3159,8 @@ void Server::notifyPlayers(const std::wstring &msg) 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) + collisiondetection, bool collision_removal, + bool vertical, const std::string &texture) { // m_env will be NULL if the server is initializing if (!m_env) @@ -3168,20 +3168,22 @@ void Server::spawnParticle(const std::string &playername, v3f pos, u16 peer_id = PEER_ID_INEXISTENT; if (playername != "") { - Player* player = m_env->getPlayer(playername.c_str()); + RemotePlayer *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); + expirationtime, size, collisiondetection, + collision_removal, vertical, 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, + bool collisiondetection, bool collision_removal, + ServerActiveObject *attached, bool vertical, const std::string &texture, const std::string &playername) { // m_env will be NULL if the server is initializing @@ -3190,17 +3192,25 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime, u16 peer_id = PEER_ID_INEXISTENT; if (playername != "") { - Player* player = m_env->getPlayer(playername.c_str()); + RemotePlayer *player = m_env->getPlayer(playername.c_str()); if (!player) return -1; peer_id = player->peer_id; } - u32 id = m_env->addParticleSpawner(spawntime); + u16 attached_id = attached ? attached->getId() : 0; + + u32 id; + if (attached_id == 0) + id = m_env->addParticleSpawner(spawntime); + else + id = m_env->addParticleSpawner(spawntime, attached_id); + SendAddParticleSpawner(peer_id, amount, spawntime, minpos, maxpos, minvel, maxvel, minacc, maxacc, minexptime, maxexptime, minsize, maxsize, - collisiondetection, vertical, texture, id); + collisiondetection, collision_removal, attached_id, vertical, + texture, id); return id; } @@ -3213,7 +3223,7 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) u16 peer_id = PEER_ID_INEXISTENT; if (playername != "") { - Player* player = m_env->getPlayer(playername.c_str()); + RemotePlayer *player = m_env->getPlayer(playername.c_str()); if (!player) return; peer_id = player->peer_id; @@ -3223,13 +3233,7 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -void Server::deleteParticleSpawnerAll(u32 id) -{ - m_env->deleteParticleSpawner(id); - SendDeleteParticleSpawner(PEER_ID_INEXISTENT, id); -} - -Inventory* Server::createDetachedInventory(const std::string &name) +Inventory* Server::createDetachedInventory(const std::string &name, const std::string &player) { if(m_detached_inventories.count(name) > 0){ infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl; @@ -3240,6 +3244,7 @@ Inventory* Server::createDetachedInventory(const std::string &name) Inventory *inv = new Inventory(m_itemdef); sanity_check(inv); m_detached_inventories[name] = inv; + m_detached_inventories_player[name] = player; //TODO find a better way to do this sendDetachedInventory(name,PEER_ID_INEXISTENT); return inv; @@ -3431,11 +3436,10 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version /* Try to get an existing player */ - RemotePlayer *player = static_cast<RemotePlayer*>(m_env->getPlayer(name)); + RemotePlayer *player = m_env->getPlayer(name); // If player is already connected, cancel - if(player != NULL && player->peer_id != 0) - { + if (player != NULL && player->peer_id != 0) { infostream<<"emergePlayer(): Player already connected"<<std::endl; return NULL; } @@ -3443,27 +3447,24 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version /* If player with the wanted peer_id already exists, cancel. */ - if(m_env->getPlayer(peer_id) != NULL) - { + if (m_env->getPlayer(peer_id) != NULL) { infostream<<"emergePlayer(): Player with wrong name but same" " peer_id already exists"<<std::endl; return NULL; } - // Load player if it isn't already loaded - if (!player) { - player = static_cast<RemotePlayer*>(m_env->loadPlayer(name)); - } + // Create a new player active object + PlayerSAO *playersao = new PlayerSAO(m_env, peer_id, isSingleplayer()); + player = m_env->loadPlayer(name, playersao); // Create player if it doesn't exist if (!player) { newplayer = true; - player = new RemotePlayer(this, name); + player = new RemotePlayer(name, this->idef()); // Set player position infostream<<"Server: Finding spawn place for player \"" <<name<<"\""<<std::endl; - v3f pos = findSpawnPos(); - player->setPosition(pos); + playersao->setBasePosition(findSpawnPos()); // Make sure the player is saved player->setModified(true); @@ -3474,18 +3475,14 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version // If the player exists, ensure that they respawn inside legal bounds // This fixes an assert crash when the player can't be added // to the environment - if (objectpos_over_limit(player->getPosition())) { + if (objectpos_over_limit(playersao->getBasePosition())) { actionstream << "Respawn position for player \"" << name << "\" outside limits, resetting" << std::endl; - v3f pos = findSpawnPos(); - player->setPosition(pos); + playersao->setBasePosition(findSpawnPos()); } } - // Create a new player active object - PlayerSAO *playersao = new PlayerSAO(m_env, player, peer_id, - getPlayerEffectivePrivs(player->getName()), - isSingleplayer()); + playersao->initialize(player, getPlayerEffectivePrivs(player->getName())); player->protocol_version = proto_version; diff --git a/src/server.h b/src/server.h index daf51dee1..4425d139b 100644 --- a/src/server.h +++ b/src/server.h @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "chat_interface.h" #include "clientiface.h" +#include "remoteplayer.h" #include "network/networkpacket.h" #include <string> #include <list> @@ -48,7 +49,6 @@ class IWritableCraftDefManager; class BanManager; class EventManager; class Inventory; -class Player; class PlayerSAO; class IRollbackManager; struct RollbackAction; @@ -64,31 +64,6 @@ enum ClientDeletionReason { CDR_DENY }; -class MapEditEventIgnorer -{ -public: - MapEditEventIgnorer(bool *flag): - m_flag(flag) - { - if(*m_flag == false) - *m_flag = true; - else - m_flag = NULL; - } - - ~MapEditEventIgnorer() - { - if(m_flag) - { - assert(*m_flag); - *m_flag = false; - } - } - -private: - bool *m_flag; -}; - class MapEditEventAreaIgnorer { public: @@ -157,7 +132,7 @@ struct ServerSoundParams struct ServerPlayingSound { ServerSoundParams params; - std::set<u16> clients; // peer ids + UNORDERED_SET<u16> clients; // peer ids }; class Server : public con::PeerHandler, public MapEventReceiver, @@ -222,6 +197,10 @@ public: void Send(NetworkPacket* pkt); + // Helper for handleCommand_PlayerPos and handleCommand_Interact + void process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, + NetworkPacket *pkt); + // Both setter and getter need no envlock, // can be called freely from threads void setTimeOfDay(u32 time); @@ -241,13 +220,12 @@ public: // Connection must be locked when called std::wstring getStatusString(); + inline double getUptime() const { return m_uptime.m_value; } // read shutdown state - inline bool getShutdownRequested() - { return m_shutdown_requested; } + inline bool getShutdownRequested() const { return m_shutdown_requested; } // request server to shutdown - inline void requestShutdown() { m_shutdown_requested = true; } void requestShutdown(const std::string &msg, bool reconnect) { m_shutdown_requested = true; @@ -275,7 +253,8 @@ public: void spawnParticle(const std::string &playername, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, - bool collisiondetection, bool vertical, const std::string &texture); + bool collisiondetection, bool collision_removal, + bool vertical, const std::string &texture); u32 addParticleSpawner(u16 amount, float spawntime, v3f minpos, v3f maxpos, @@ -283,14 +262,15 @@ public: v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, - bool collisiondetection, bool vertical, const std::string &texture, + bool collisiondetection, bool collision_removal, + ServerActiveObject *attached, + bool vertical, const std::string &texture, const std::string &playername); void deleteParticleSpawner(const std::string &playername, u32 id); - void deleteParticleSpawnerAll(u32 id); // Creates or resets inventory - Inventory* createDetachedInventory(const std::string &name); + Inventory* createDetachedInventory(const std::string &name, const std::string &player=""); // Envlock and conlock should be locked when using scriptapi GameScripting *getScriptIface(){ return m_script; } @@ -318,11 +298,11 @@ public: IWritableNodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); + const std::vector<ModSpec> &getMods() const { return m_mods; } const ModSpec* getModSpec(const std::string &modname) const; void getModNames(std::vector<std::string> &modlist); std::string getBuiltinLuaPath(); - inline std::string getWorldPath() const - { return m_path_world; } + inline std::string getWorldPath() const { return m_path_world; } inline bool isSingleplayer() { return m_simple_singleplayer_mode; } @@ -334,28 +314,32 @@ public: 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); + u32 hudAdd(RemotePlayer *player, HudElement *element); + bool hudRemove(RemotePlayer *player, u32 id); + bool hudChange(RemotePlayer *player, u32 id, HudElementStat stat, void *value); + bool hudSetFlags(RemotePlayer *player, u32 flags, u32 mask); + bool hudSetHotbarItemcount(RemotePlayer *player, s32 hotbar_itemcount); + s32 hudGetHotbarItemcount(RemotePlayer *player) const + { return player->getHotbarItemcount(); } + void hudSetHotbarImage(RemotePlayer *player, std::string name); + std::string hudGetHotbarImage(RemotePlayer *player); + void hudSetHotbarSelectedImage(RemotePlayer *player, std::string name); + const std::string &hudGetHotbarSelectedImage(RemotePlayer *player) const + { + return player->getHotbarSelectedImage(); + } 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 setLocalPlayerAnimations(RemotePlayer *player, v2s32 animation_frames[4], + f32 frame_speed); + bool setPlayerEyeOffset(RemotePlayer *player, v3f first, v3f third); - bool setSky(Player *player, const video::SColor &bgcolor, + bool setSky(RemotePlayer *player, const video::SColor &bgcolor, const std::string &type, const std::vector<std::string> ¶ms); - bool overrideDayNightRatio(Player *player, bool do_override, - float brightness); + bool overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); @@ -456,7 +440,9 @@ private: v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, - bool collisiondetection, bool vertical, std::string texture, u32 id); + bool collisiondetection, bool collision_removal, + u16 attached_id, + bool vertical, const std::string &texture, u32 id); void SendDeleteParticleSpawner(u16 peer_id, u32 id); @@ -464,7 +450,8 @@ private: void SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, - bool collisiondetection, bool vertical, std::string texture); + bool collisiondetection, bool collision_removal, + bool vertical, const std::string &texture); u32 SendActiveObjectRemoveAdd(u16 peer_id, const std::string &datas); void SendActiveObjectMessages(u16 peer_id, const std::string &datas, bool reliable = true); @@ -475,7 +462,7 @@ private: void DiePlayer(u16 peer_id); void RespawnPlayer(u16 peer_id); void DeleteClient(u16 peer_id, ClientDeletionReason reason); - void UpdateCrafting(Player *player); + void UpdateCrafting(RemotePlayer *player); void handleChatInterfaceEvent(ChatEvent *evt); @@ -483,7 +470,7 @@ private: std::wstring handleChat(const std::string &name, const std::wstring &wname, const std::wstring &wmessage, bool check_shout_priv = false, - u16 peer_id_to_avoid_sending = PEER_ID_INEXISTENT); + RemotePlayer *player = NULL); void handleAdminChat(const ChatEventChat *evt); v3f findSpawnPos(); @@ -518,6 +505,7 @@ private: // If true, do not allow multiple players and hide some multiplayer // functionality bool m_simple_singleplayer_mode; + u16 m_max_chatmessage_length; // Thread can set; step() will throw as ServerError MutexedVariable<std::string> m_async_fatal_error; @@ -525,9 +513,7 @@ private: // Some timers float m_liquid_transform_timer; float m_liquid_transform_every; - float m_print_info_timer; float m_masterserver_timer; - float m_objectdata_timer; float m_emergethread_trigger_timer; float m_savemap_timer; IntervalLimiter m_map_timer_and_unload_interval; @@ -649,12 +635,12 @@ private: u16 m_ignore_map_edit_events_peer_id; // media files known to server - std::map<std::string,MediaInfo> m_media; + UNORDERED_MAP<std::string, MediaInfo> m_media; /* Sounds */ - std::map<s32, ServerPlayingSound> m_playing_sounds; + UNORDERED_MAP<s32, ServerPlayingSound> m_playing_sounds; s32 m_next_sound_id; /* @@ -662,6 +648,8 @@ private: */ // key = name std::map<std::string, Inventory*> m_detached_inventories; + // value = "" (visible to all players) or player name + std::map<std::string, std::string> m_detached_inventories_player; DISABLE_CLASS_COPY(Server); }; diff --git a/src/serverlist.cpp b/src/serverlist.cpp index de7962a68..87ca5dc04 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "log.h" #include "network/networkprotocol.h" -#include "json/json.h" +#include <json/json.h> #include "convert_json.h" #include "httpfetch.h" #include "util/string.h" diff --git a/src/serverlist.h b/src/serverlist.h index 8ffea44cc..0747c3920 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include "config.h" #include "mods.h" -#include "json/json.h" +#include <json/json.h> #ifndef SERVERLIST_HEADER #define SERVERLIST_HEADER diff --git a/src/serverobject.cpp b/src/serverobject.cpp index 236d7e8dc..191247829 100644 --- a/src/serverobject.cpp +++ b/src/serverobject.cpp @@ -98,4 +98,3 @@ bool ServerActiveObject::setWieldedItem(const ItemStack &item) } return false; } - diff --git a/src/serverobject.h b/src/serverobject.h index 597eb63a8..9884eb0a1 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -44,7 +44,6 @@ Some planning class ServerEnvironment; struct ItemStack; -class Player; struct ToolCapabilities; struct ObjectProperties; @@ -69,24 +68,24 @@ public: // environment virtual bool environmentDeletes() const { return true; } - + // Create a certain type of ServerActiveObject static ServerActiveObject* create(ActiveObjectType type, ServerEnvironment *env, u16 id, v3f pos, const std::string &data); - + /* Some simple getters/setters */ v3f getBasePosition(){ return m_base_position; } void setBasePosition(v3f pos){ m_base_position = pos; } ServerEnvironment* getEnv(){ return m_env; } - + /* Some more dynamic interface */ - - virtual void setPos(v3f pos) + + virtual void setPos(const v3f &pos) { setBasePosition(pos); } // continuous: if true, object does not stop immediately at pos virtual void moveTo(v3f pos, bool continuous) @@ -96,7 +95,7 @@ public: virtual float getMinimumSavedMovement(); virtual std::string getDescription(){return "SAO";} - + /* Step object in time. Messages added to messages are sent to client over network. @@ -108,13 +107,13 @@ public: packet. */ virtual void step(float dtime, bool send_recommended){} - + /* The return value of this is passed to the client-side object when it is created */ virtual std::string getClientInitializationData(u16 protocol_version){return "";} - + /* The return value of this is passed to the server-side object when it is created (converted from static to active - actually @@ -131,7 +130,7 @@ public: */ virtual bool isStaticAllowed() const {return true;} - + // Returns tool wear virtual int punch(v3f dir, const ToolCapabilities *toolcap=NULL, @@ -167,8 +166,8 @@ public: {} virtual void removeAttachmentChild(int child_id) {} - virtual std::set<int> getAttachmentChildIds() - { return std::set<int>(); } + virtual UNORDERED_SET<int> getAttachmentChildIds() + { return UNORDERED_SET<int>(); } virtual ObjectProperties* accessObjectProperties() { return NULL; } virtual void notifyObjectPropertiesModified() @@ -189,6 +188,15 @@ public: { return 0; } virtual ItemStack getWieldedItem() const; virtual bool setWieldedItem(const ItemStack &item); + inline void attachParticleSpawner(u32 id) + { + m_attached_particle_spawners.insert(id); + } + inline void detachParticleSpawner(u32 id) + { + m_attached_particle_spawners.erase(id); + } + /* Number of players which know about this object. Object won't be @@ -207,7 +215,7 @@ public: - This can be set to true by anything else too. */ bool m_removed; - + /* This is set to true when an object should be removed from the active object list but couldn't be removed because the id has to be @@ -218,7 +226,7 @@ public: list. */ bool m_pending_deactivation; - + /* Whether the object's static data has been stored to a block */ @@ -228,12 +236,12 @@ public: a copy of the static data resides. */ v3s16 m_static_block; - + /* Queue of messages to be sent to the client */ std::queue<ActiveObjectMessage> m_messages_out; - + protected: // Used for creating objects based on type typedef ServerActiveObject* (*Factory) @@ -243,6 +251,7 @@ protected: ServerEnvironment *m_env; v3f m_base_position; + UNORDERED_SET<u32> m_attached_particle_spawners; private: // Used for creating objects based on type diff --git a/src/settings.cpp b/src/settings.cpp index 56afa6133..c4c3c9073 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -196,9 +196,8 @@ void Settings::writeLines(std::ostream &os, u32 tab_depth) const { MutexAutoLock lock(m_mutex); - for (std::map<std::string, SettingsEntry>::const_iterator - it = m_settings.begin(); - it != m_settings.end(); ++it) + for (SettingEntries::const_iterator it = m_settings.begin(); + it != m_settings.end(); ++it) printEntry(os, it->first, it->second, tab_depth); } @@ -231,7 +230,7 @@ void Settings::printEntry(std::ostream &os, const std::string &name, bool Settings::updateConfigObject(std::istream &is, std::ostream &os, const std::string &end, u32 tab_depth) { - std::map<std::string, SettingsEntry>::const_iterator it; + SettingEntries::const_iterator it; std::set<std::string> present_entries; std::string line, name, value; bool was_modified = false; @@ -381,7 +380,7 @@ const SettingsEntry &Settings::getEntry(const std::string &name) const { MutexAutoLock lock(m_mutex); - std::map<std::string, SettingsEntry>::const_iterator n; + SettingEntries::const_iterator n; if ((n = m_settings.find(name)) == m_settings.end()) { if ((n = m_defaults.find(name)) == m_defaults.end()) throw SettingNotFoundException("Setting [" + name + "] not found."); @@ -572,9 +571,8 @@ bool Settings::exists(const std::string &name) const std::vector<std::string> Settings::getNames() const { std::vector<std::string> names; - for (std::map<std::string, SettingsEntry>::const_iterator - i = m_settings.begin(); - i != m_settings.end(); ++i) { + for (SettingEntries::const_iterator i = m_settings.begin(); + i != m_settings.end(); ++i) { names.push_back(i->first); } return names; @@ -880,7 +878,7 @@ bool Settings::remove(const std::string &name) { MutexAutoLock lock(m_mutex); - std::map<std::string, SettingsEntry>::iterator it = m_settings.find(name); + SettingEntries::iterator it = m_settings.find(name); if (it != m_settings.end()) { delete it->second.group; m_settings.erase(it); @@ -912,7 +910,6 @@ void Settings::updateValue(const Settings &other, const std::string &name) try { std::string val = other.get(name); - m_settings[name] = val; } catch (SettingNotFoundException &e) { } @@ -968,8 +965,9 @@ void Settings::updateNoLock(const Settings &other) void Settings::clearNoLock() { - std::map<std::string, SettingsEntry>::const_iterator it; - for (it = m_settings.begin(); it != m_settings.end(); ++it) + + for (SettingEntries::const_iterator it = m_settings.begin(); + it != m_settings.end(); ++it) delete it->second.group; m_settings.clear(); @@ -978,46 +976,45 @@ void Settings::clearNoLock() void Settings::clearDefaultsNoLock() { - std::map<std::string, SettingsEntry>::const_iterator it; - for (it = m_defaults.begin(); it != m_defaults.end(); ++it) + for (SettingEntries::const_iterator 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) +void Settings::registerChangedCallback(const std::string &name, + SettingsChangedCallback cbf, void *userdata) { - MutexAutoLock lock(m_callbackMutex); + MutexAutoLock lock(m_callback_mutex); m_callbacks[name].push_back(std::make_pair(cbf, userdata)); } -void Settings::deregisterChangedCallback(std::string name, setting_changed_callback cbf, void *userdata) +void Settings::deregisterChangedCallback(const std::string &name, + SettingsChangedCallback cbf, void *userdata) { - MutexAutoLock lock(m_callbackMutex); - std::map<std::string, std::vector<std::pair<setting_changed_callback, void*> > >::iterator iterToVector = m_callbacks.find(name); - if (iterToVector != m_callbacks.end()) - { - std::vector<std::pair<setting_changed_callback, void*> > &vector = iterToVector->second; + MutexAutoLock lock(m_callback_mutex); + SettingsCallbackMap::iterator it_cbks = m_callbacks.find(name); - std::vector<std::pair<setting_changed_callback, void*> >::iterator position = - std::find(vector.begin(), vector.end(), std::make_pair(cbf, userdata)); + if (it_cbks != m_callbacks.end()) { + SettingsCallbackList &cbks = it_cbks->second; - if (position != vector.end()) - vector.erase(position); + SettingsCallbackList::iterator position = + std::find(cbks.begin(), cbks.end(), std::make_pair(cbf, userdata)); + + if (position != cbks.end()) + cbks.erase(position); } } -void Settings::doCallbacks(const std::string name) +void Settings::doCallbacks(const std::string &name) const { - MutexAutoLock lock(m_callbackMutex); - std::map<std::string, std::vector<std::pair<setting_changed_callback, void*> > >::iterator iterToVector = m_callbacks.find(name); - if (iterToVector != m_callbacks.end()) - { - std::vector<std::pair<setting_changed_callback, void*> >::iterator iter; - for (iter = iterToVector->second.begin(); iter != iterToVector->second.end(); ++iter) - { - (iter->first)(name, iter->second); - } + MutexAutoLock lock(m_callback_mutex); + + SettingsCallbackMap::const_iterator it_cbks = m_callbacks.find(name); + if (it_cbks != m_callbacks.end()) { + SettingsCallbackList::const_iterator it; + for (it = it_cbks->second.begin(); it != it_cbks->second.end(); ++it) + (it->first)(name, it->second); } } diff --git a/src/settings.h b/src/settings.h index 80d41fd79..b19733514 100644 --- a/src/settings.h +++ b/src/settings.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "threading/mutex.h" #include <string> -#include <map> +#include "util/cpp11_container.h" #include <list> #include <set> @@ -35,8 +35,17 @@ struct NoiseParams; 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 &name, void *data); +// Type for a settings changed callback function +typedef void (*SettingsChangedCallback)(const std::string &name, void *data); + +typedef std::vector< + std::pair< + SettingsChangedCallback, + void * + > +> SettingsCallbackList; + +typedef UNORDERED_MAP<std::string, SettingsCallbackList> SettingsCallbackMap; enum ValueType { VALUETYPE_STRING, @@ -89,6 +98,8 @@ struct SettingsEntry { bool is_group; }; +typedef UNORDERED_MAP<std::string, SettingsEntry> SettingEntries; + class Settings { public: Settings() {} @@ -209,24 +220,28 @@ public: 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); - void deregisterChangedCallback(std::string name, setting_changed_callback cbf, void *userdata = NULL); -private: + void registerChangedCallback(const std::string &name, + SettingsChangedCallback cbf, void *userdata = NULL); + void deregisterChangedCallback(const std::string &name, + SettingsChangedCallback cbf, void *userdata = NULL); +private: void updateNoLock(const Settings &other); void clearNoLock(); void clearDefaultsNoLock(); - void doCallbacks(std::string name); + void doCallbacks(const std::string &name) const; + + SettingEntries m_settings; + SettingEntries m_defaults; - std::map<std::string, SettingsEntry> m_settings; - std::map<std::string, SettingsEntry> m_defaults; + SettingsCallbackMap m_callbacks; - std::map<std::string, std::vector<std::pair<setting_changed_callback,void*> > > m_callbacks; + mutable Mutex m_callback_mutex; - mutable Mutex m_callbackMutex; - mutable Mutex m_mutex; // All methods that access m_settings/m_defaults directly should lock this. + // All methods that access m_settings/m_defaults directly should lock this. + mutable Mutex m_mutex; }; diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index f7e14dd65..39223d9ce 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -1,6 +1,6 @@ // This file is automatically generated // It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files -// To update it, refer to the bottom of builtin/mainmenu/tab_settings.lua +// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua fake_function() { gettext("Client"); @@ -14,7 +14,7 @@ fake_function() { gettext("Noclip"); gettext("If enabled together with fly mode, player is able to fly through solid nodes.\nThis requires the \"noclip\" privilege on the server."); gettext("Cinematic mode"); - gettext("Smooths camera when moving and looking around.\nUseful for recording videos."); + gettext("Smooths camera when looking around. Also called look or mouse smoothing.\nUseful for recording videos."); gettext("Camera smoothing"); gettext("Smooths rotation of camera. 0 to disable."); gettext("Camera smoothing in cinematic mode"); @@ -35,6 +35,12 @@ fake_function() { gettext("Enable random user input (only used for testing)."); gettext("Continuous forward"); gettext("Continuous forward movement (only used for testing)."); + gettext("Enable Joysticks"); + gettext("Enable Joysticks"); + gettext("Joystick button repetition interval"); + gettext("The time in seconds it takes between repeated events\nwhen holding down a joystick button combination."); + gettext("Joystick frustum sensitivity"); + gettext("The sensitivity of the joystick axes for moving the\ningame view frustum around."); gettext("Forward key"); gettext("Key for moving the player forward.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Backward key"); @@ -65,6 +71,8 @@ fake_function() { gettext("Key for toggling fast mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Noclip key"); gettext("Key for toggling noclip mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Autorun key"); + gettext("Key for toggling autorun.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Cinematic mode key"); gettext("Key for toggling cinematic mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Minimap key"); @@ -80,7 +88,7 @@ fake_function() { gettext("Fog toggle key"); gettext("Key for toggling the display of the fog.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Camera update toggle key"); - gettext("Key for toggling the camrea update. Only used for development\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Key for toggling the camera update. Only used for development\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Debug info toggle key"); gettext("Key for toggling the display of debug info.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); gettext("Profiler toggle key"); @@ -146,7 +154,7 @@ fake_function() { gettext("Experimental option, might cause visible spaces between blocks\nwhen set to higher number than 0."); gettext("Shaders"); gettext("Shaders"); - gettext("Shaders allow advanced visul effects and may increase performance on some video cards.\nThy only work with the OpenGL video backend."); + gettext("Shaders allow advanced visual effects and may increase performance on some video cards.\nThy only work with the OpenGL video backend."); gettext("Tone Mapping"); gettext("Filmic tone mapping"); gettext("Enables filmic tone mapping"); @@ -188,7 +196,7 @@ fake_function() { gettext("FPS in pause menu"); gettext("Maximum FPS when game is paused."); gettext("Viewing range"); - gettext("View distance in nodes.\nMin = 20"); + gettext("View distance in nodes."); gettext("Screen width"); gettext("Width component of the initial window size."); gettext("Screen height"); @@ -201,6 +209,8 @@ fake_function() { gettext("Vertical screen synchronization."); gettext("Field of view"); gettext("Field of view in degrees."); + gettext("Field of view for zoom"); + gettext("Field of view while zooming in degrees.\nThis requires the \"zoom\" privilege on the server."); gettext("Gamma"); gettext("Adjust the gamma encoding for the light tables. Lower numbers are brighter.\nThis setting is for the client only and is ignored by the server."); gettext("Texture path"); @@ -247,6 +257,8 @@ fake_function() { gettext("The strength (darkness) of node ambient-occlusion shading.\nLower is darker, Higher is lighter. The valid range of values for this\nsetting is 0.25 to 4.0 inclusive. If the value is out of range it will be\nset to the nearest valid value."); gettext("Inventory items animations"); gettext("Enables animation of inventory items."); + gettext("Fog Start"); + gettext("Fraction of the visible distance at which fog starts to be rendered"); gettext("Menus"); gettext("Clouds in menu"); gettext("Use a cloud animation for the main menu background."); @@ -255,7 +267,7 @@ fake_function() { gettext("GUI scaling filter"); gettext("When gui_scaling_filter is true, all GUI images need to be\nfiltered in software, but some images are generated directly\nto hardware (e.g. render-to-texture for nodes in inventory)."); gettext("GUI scaling filter txr2img"); - gettext("When gui_scaling_filter_txr2img is true, copy those images\nfrom hardware to software for scaling. When false, fall back\nto the old scaling method, for video drivers that don't\npropery support downloading textures back from hardware."); + gettext("When gui_scaling_filter_txr2img is true, copy those images\nfrom hardware to software for scaling. When false, fall back\nto the old scaling method, for video drivers that don't\nproperly support downloading textures back from hardware."); gettext("Tooltip delay"); gettext("Delay showing tooltips, stated in milliseconds."); gettext("Freetype fonts"); @@ -306,6 +318,8 @@ fake_function() { gettext("Automaticaly report to the serverlist."); gettext("Serverlist URL"); gettext("Announce to this serverlist.\nIf you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net."); + gettext("Disable escape sequences"); + gettext("Disable escape sequences, e.g. chat coloring.\nUse this if you want to run a server with pre-0.4.14 clients and you want to disable\nthe escape sequences generated by mods."); gettext("Network"); gettext("Server port"); gettext("Network port to listen (UDP).\nThis value will be overridden when starting from the main menu."); @@ -318,10 +332,11 @@ fake_function() { gettext("IPv6 server"); gettext("Enable/disable running an IPv6 server. An IPv6 server may be restricted\nto IPv6 clients, depending on system configuration.\nIgnored if bind_address is set."); gettext("Advanced"); - gettext("Maximum simultaneously blocks send per client"); - gettext("How many blocks are flying in the wire simultaneously per client."); - gettext("Maximum simultaneously bocks send total"); - gettext("How many blocks are flying in the wire simultaneously for the whole server."); + gettext("Maximum simultaneous block sends per client"); + gettext("Maximum number of blocks that are simultaneously sent per client."); + gettext("Maximum simultaneous block sends total"); + gettext("Maximum number of blocks that are simultaneously sent in total."); + gettext("Delay in sending blocks after building"); gettext("To reduce lag, block transfers are slowed down when a player is building something.\nThis determines how long they are slowed down after placing or removing a node."); gettext("Max. packets per iteration"); gettext("Maximum number of packets sent per send step, if you have a slow connection\ntry reducing it, but don't reduce it to a number below double of targeted\nclient number."); @@ -397,17 +412,11 @@ fake_function() { gettext("Advanced"); gettext("Deprecated Lua API handling"); gettext("Handling for deprecated lua api calls:\n- legacy: (try to) mimic old behaviour (default for release).\n- log: mimic and log backtrace of deprecated call (default for debug).\n- error: abort on usage of deprecated call (suggested for mod developers)."); - gettext("Mod profiling"); - gettext("Useful for mod developers."); - gettext("Detailed mod profiling"); - gettext("Detailed mod profile data. Useful for mod developers."); - gettext("Profiling print interval"); - gettext("Profiler data print interval. 0 = disable. Useful for developers."); gettext("Max. clearobjects extra blocks"); gettext("Number of extra blocks that can be loaded by /clearobjects at once.\nThis is a trade-off between sqlite transaction overhead and\nmemory consumption (4096=100MB, as a rule of thumb)."); gettext("Unload unused server data"); gettext("How much the server will wait before unloading unused mapblocks.\nHigher value is smoother, but will use more RAM."); - gettext("Maxmimum objects per block"); + gettext("Maximum objects per block"); gettext("Maximum number of statically stored objects in a block."); gettext("Synchronous SQLite"); gettext("See http://www.sqlite.org/pragma.html#pragma_synchronous"); @@ -427,6 +436,8 @@ fake_function() { gettext("The time (in seconds) that the liquids queue may grow beyond processing\ncapacity until an attempt is made to decrease its size by dumping old queue\nitems. A value of 0 disables the functionality."); gettext("Liquid update tick"); gettext("Liquid update interval in seconds."); + gettext("block send optimize distance"); + gettext("At this distance the server will aggressively optimize which blocks are sent to clients.\nSmall values potentially improve performance a lot, at the expense of visible rendering glitches.\n(some blocks will not be rendered under water and in caves, as well as sometimes on land)\nSetting this to a value greater than max_block_send_distance disables this optimization.\nStated in mapblocks (16 nodes)"); gettext("Mapgen"); gettext("Mapgen name"); gettext("Name of map generator to be used when creating a new world.\nCreating a world in the main menu will override this."); @@ -437,7 +448,7 @@ fake_function() { gettext("Map generation limit"); gettext("Where the map generator stops.\nPlease note:\n- Limited to 31000 (setting above has no effect)\n- The map generator works in groups of 80x80x80 nodes (5x5x5 MapBlocks).\n- Those groups have an offset of -32, -32 nodes from the origin.\n- Only groups which are within the map_generation_limit are generated"); gettext("Mapgen flags"); - gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations.\nThe default flags set in the engine are: caves, light, decorations\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); + gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Advanced"); gettext("Chunk size"); gettext("Size of chunks to be generated at once by mapgen, stated in mapblocks (16 nodes)."); @@ -466,7 +477,7 @@ fake_function() { gettext("Mapgen v5 cave2 noise parameters"); gettext("Mapgen v6"); gettext("Mapgen v6 flags"); - gettext("Map generation attributes specific to Mapgen v6.\nWhen snowbiomes are enabled jungles are automatically enabled, the 'jungles' flag is ignored.\nThe default flags set in the engine are: biomeblend, mudflow\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen v6.\nWhen snowbiomes are enabled jungles are automatically enabled, the 'jungles' flag is ignored.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen v6 desert frequency"); gettext("Controls size of deserts and beaches in Mapgen v6.\nWhen snowbiomes are enabled 'mgv6_freq_desert' is ignored."); gettext("Mapgen v6 beach frequency"); @@ -483,23 +494,33 @@ fake_function() { gettext("Mapgen v6 apple trees noise parameters"); gettext("Mapgen v7"); gettext("Mapgen v7 flags"); - gettext("Map generation attributes specific to Mapgen v7.\nThe 'ridges' flag controls the rivers.\nThe default flags set in the engine are: mountains, ridges\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen v7.\nThe 'ridges' flag enables the rivers.\nFloatlands are currently experimental and subject to change.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen v7 cave width"); gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Mapgen v7 floatland mountain density"); + gettext("Controls the density of floatland mountain terrain.\nIs an offset added to the 'np_mountain' noise value."); + gettext("Mapgen v7 floatland mountain height"); + gettext("Typical maximum height, above and below midpoint, of floatland mountain terrain."); + gettext("Mapgen v7 floatland level"); + gettext("Y-level of floatland midpoint and lake surface."); + gettext("Mapgen v7 shadow limit"); + gettext("Y-level to which floatland shadows extend."); gettext("Mapgen v7 terrain base noise parameters"); gettext("Mapgen v7 terrain altitude noise parameters"); gettext("Mapgen v7 terrain persistation noise parameters"); gettext("Mapgen v7 height select noise parameters"); gettext("Mapgen v7 filler depth noise parameters"); gettext("Mapgen v7 mount height noise parameters"); - gettext("Mapgen v7 ridge water noise parameters"); + gettext("Mapgen v7 river course noise parameters"); + gettext("Mapgen v7 floatland base terrain noise parameters"); + gettext("Mapgen v7 floatland base terrain height noise parameters"); gettext("Mapgen v7 mountain noise parameters"); - gettext("Mapgen v7 ridge noise parameters"); + gettext("Mapgen v7 river channel wall noise parameters"); gettext("Mapgen v7 cave1 noise parameters"); gettext("Mapgen v7 cave2 noise parameters"); gettext("Mapgen flat"); gettext("Mapgen flat flags"); - gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills can be added to the flat world.\nThe default flags set in the engine are: none\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills can be added to the flat world.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen flat ground level"); gettext("Y of flat ground."); gettext("Mapgen flat large cave depth"); @@ -547,7 +568,7 @@ fake_function() { gettext("Mapgen Valleys"); gettext("General"); gettext("Valleys C Flags"); - gettext("Map generation attributes specific to Mapgen Valleys.\n'altitude_chill' makes higher elevations colder, which may cause biome issues.\n'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool,\nit may interfere with delicately adjusted biomes.\nThe default flags set in the engine are: altitude_chill, humid_rivers\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen Valleys.\n'altitude_chill' makes higher elevations colder, which may cause biome issues.\n'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool,\nit may interfere with delicately adjusted biomes.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Altitude Chill"); gettext("The altitude at which temperature drops by 20C"); gettext("Large cave depth"); @@ -591,7 +612,31 @@ fake_function() { gettext("Trusted mods"); gettext("Comma-separated list of trusted mods that are allowed to access insecure\nfunctions even when mod security is on (via request_insecure_environment())."); gettext("HTTP Mods"); - gettext("Comma-seperated list of mods that are allowed to access HTTP APIs, which\nallow them to upload and download data to/from the internet."); + gettext("Comma-separated list of mods that are allowed to access HTTP APIs, which\nallow them to upload and download data to/from the internet."); + gettext("Advanced"); + gettext("Profiling"); + gettext("Load the game profiler"); + gettext("Load the game profiler to collect game profiling data.\nProvides a /profiler command to access the compiled profile.\nUseful for mod developers and server operators."); + gettext("Default report format"); + gettext("The default format in which profiles are being saved,\nwhen calling `/profiler save [format]` without format."); + gettext("Report path"); + gettext("The file path relative to your worldpath in which profiles will be saved to.\n"); + gettext("Instrumentation"); + gettext("Entity methods"); + gettext("Instrument the methods of entities on registration."); + gettext("Active Block Modifiers"); + gettext("Instrument the action function of Active Block Modifiers on registration."); + gettext("Loading Block Modifiers"); + gettext("Instrument the action function of Loading Block Modifiers on registration."); + gettext("Chatcommands"); + gettext("Instrument chatcommands on registration."); + gettext("Global callbacks"); + gettext("Instrument global callback functions on registration.\n(anything you pass to a minetest.register_*() function)"); + gettext("Advanced"); + gettext("Builtin"); + gettext("Instrument builtin.\nThis is usually only needed by core/builtin contributors"); + gettext("Profiler"); + gettext("Have the profiler instrument itself:\n* Instrument an empty function.\nThis estimates the overhead, that instrumentation is adding (+1 function call).\n* Instrument the sampler being used to update the statistics."); gettext("Client and Server"); gettext("Player name"); gettext("Name of the player.\nWhen running a server, clients connecting with this name are admins.\nWhen starting from the main menu, this is overridden."); @@ -617,4 +662,6 @@ fake_function() { gettext("Modstore download URL"); gettext("Modstore mods list URL"); gettext("Modstore details URL"); + gettext("Engine profiling data print interval"); + gettext("Print the engine's profiling data in regular intervals (in seconds). 0 = disable. Useful for developers."); } diff --git a/src/shader.cpp b/src/shader.cpp index e13ab8df3..c0ecf738d 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -167,29 +167,27 @@ private: } }; + /* ShaderCallback: Sets constants that can be used in shaders */ -class IShaderConstantSetterRegistry -{ -public: - virtual ~IShaderConstantSetterRegistry(){}; - virtual void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel, const std::string &name) = 0; -}; - class ShaderCallback : public video::IShaderConstantSetCallBack { - IShaderConstantSetterRegistry *m_scsr; - std::string m_name; + std::vector<IShaderConstantSetter*> m_setters; public: - ShaderCallback(IShaderConstantSetterRegistry *scsr, const std::string &name): - m_scsr(scsr), - m_name(name) - {} - ~ShaderCallback() {} + ShaderCallback(const std::vector<IShaderConstantSetterFactory*> &factories) + { + for (u32 i = 0; i < factories.size(); ++i) + m_setters.push_back(factories[i]->create()); + } + + ~ShaderCallback() + { + for (u32 i = 0; i < m_setters.size(); ++i) + delete m_setters[i]; + } virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) { @@ -198,18 +196,25 @@ public: bool is_highlevel = userData; - m_scsr->onSetConstants(services, is_highlevel, m_name); + for (u32 i = 0; i < m_setters.size(); ++i) + m_setters[i]->onSetConstants(services, is_highlevel); } }; + /* MainShaderConstantSetter: Set basic constants required for almost everything */ class MainShaderConstantSetter : public IShaderConstantSetter { + CachedVertexShaderSetting<float, 16> m_world_view_proj; + CachedVertexShaderSetting<float, 16> m_world; + public: - MainShaderConstantSetter(IrrlichtDevice *device) + MainShaderConstantSetter() : + m_world_view_proj("mWorldViewProj"), + m_world("mWorld") {} ~MainShaderConstantSetter() {} @@ -219,47 +224,40 @@ public: video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver); - // set inverted world matrix - core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD); - invWorld.makeInverse(); - if(is_highlevel) - services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16); - else - services->setVertexShaderConstant(invWorld.pointer(), 0, 4); - - // set clip matrix + // Set clip matrix core::matrix4 worldViewProj; worldViewProj = driver->getTransform(video::ETS_PROJECTION); worldViewProj *= driver->getTransform(video::ETS_VIEW); worldViewProj *= driver->getTransform(video::ETS_WORLD); - if(is_highlevel) - services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16); - else - services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4); - - // set transposed world matrix - core::matrix4 transWorld = driver->getTransform(video::ETS_WORLD); - transWorld = transWorld.getTransposed(); - if(is_highlevel) - services->setVertexShaderConstant("mTransWorld", transWorld.pointer(), 16); + if (is_highlevel) + m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services); else - services->setVertexShaderConstant(transWorld.pointer(), 8, 4); + services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4); - // set world matrix + // Set world matrix core::matrix4 world = driver->getTransform(video::ETS_WORLD); - if(is_highlevel) - services->setVertexShaderConstant("mWorld", world.pointer(), 16); + if (is_highlevel) + m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services); else - services->setVertexShaderConstant(world.pointer(), 8, 4); + services->setVertexShaderConstant(world.pointer(), 4, 4); } }; + +class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ +public: + virtual IShaderConstantSetter* create() + { return new MainShaderConstantSetter(); } +}; + + /* ShaderSource */ -class ShaderSource : public IWritableShaderSource, public IShaderConstantSetterRegistry +class ShaderSource : public IWritableShaderSource { public: ShaderSource(IrrlichtDevice *device); @@ -302,22 +300,17 @@ public: // Shall be called from the main thread. void rebuildShaders(); - void addGlobalConstantSetter(IShaderConstantSetter *setter) + void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) { - m_global_setters.push_back(setter); + m_setter_factories.push_back(setter); } - void onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel, const std::string &name); - private: // The id of the thread that is allowed to use irrlicht directly threadid_t m_main_thread; // The irrlicht device IrrlichtDevice *m_device; - // The set-constants callback - ShaderCallback *m_shader_callback; // Cache of source shaders // This should be only accessed from the main thread @@ -332,9 +325,11 @@ private: // Queued shader fetches (to be processed by the main thread) RequestQueue<std::string, u32, u8, u8> m_get_shader_queue; - // Global constant setters - // TODO: Delete these in the destructor - std::vector<IShaderConstantSetter*> m_global_setters; + // Global constant setter factories + std::vector<IShaderConstantSetterFactory *> m_setter_factories; + + // Shader callbacks + std::vector<ShaderCallback *> m_callbacks; }; IWritableShaderSource* createShaderSource(IrrlichtDevice *device) @@ -347,8 +342,8 @@ IWritableShaderSource* createShaderSource(IrrlichtDevice *device) */ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, - IrrlichtDevice *device, - video::IShaderConstantSetCallBack *callback, + IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks, + const std::vector<IShaderConstantSetterFactory*> &setter_factories, SourceShaderCache *sourcecache); /* @@ -364,28 +359,24 @@ ShaderSource::ShaderSource(IrrlichtDevice *device): { assert(m_device); // Pre-condition - m_shader_callback = new ShaderCallback(this, "default"); - m_main_thread = thr_get_current_thread_id(); // Add a dummy ShaderInfo as the first index, named "" m_shaderinfo_cache.push_back(ShaderInfo()); // Add main global constant setter - addGlobalConstantSetter(new MainShaderConstantSetter(device)); + addShaderConstantSetterFactory(new MainShaderConstantSetterFactory()); } ShaderSource::~ShaderSource() { - for (std::vector<IShaderConstantSetter*>::iterator iter = m_global_setters.begin(); - iter != m_global_setters.end(); ++iter) { + for (std::vector<ShaderCallback *>::iterator iter = m_callbacks.begin(); + iter != m_callbacks.end(); ++iter) { delete *iter; } - m_global_setters.clear(); - - if (m_shader_callback) { - m_shader_callback->drop(); - m_shader_callback = NULL; + for (std::vector<IShaderConstantSetterFactory *>::iterator iter = m_setter_factories.begin(); + iter != m_setter_factories.end(); ++iter) { + delete *iter; } } @@ -461,8 +452,8 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, return 0; } - ShaderInfo info = generate_shader(name, material_type, drawtype, m_device, - m_shader_callback, &m_sourcecache); + ShaderInfo info = generate_shader(name, material_type, drawtype, + m_device, m_callbacks, m_setter_factories, &m_sourcecache); /* Add shader to caches (add dummy shaders too) @@ -527,22 +518,16 @@ void ShaderSource::rebuildShaders() ShaderInfo *info = &m_shaderinfo_cache[i]; if(info->name != ""){ *info = generate_shader(info->name, info->material_type, - info->drawtype, m_device, m_shader_callback, &m_sourcecache); + info->drawtype, m_device, m_callbacks, + m_setter_factories, &m_sourcecache); } } } -void ShaderSource::onSetConstants(video::IMaterialRendererServices *services, - bool is_highlevel, const std::string &name) -{ - for(u32 i=0; i<m_global_setters.size(); i++){ - IShaderConstantSetter *setter = m_global_setters[i]; - setter->onSetConstants(services, is_highlevel); - } -} ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, - IrrlichtDevice *device, video::IShaderConstantSetCallBack *callback, + IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks, + const std::vector<IShaderConstantSetterFactory*> &setter_factories, SourceShaderCache *sourcecache) { ShaderInfo shaderinfo; @@ -572,7 +557,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, } bool enable_shaders = g_settings->getBool("enable_shaders"); - if(!enable_shaders) + if (!enable_shaders) return shaderinfo; video::IVideoDriver* driver = device->getVideoDriver(); @@ -766,6 +751,10 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, if (g_settings->getBool("tone_mapping")) shaders_header += "#define ENABLE_TONE_MAPPING\n"; + shaders_header += "#define FOG_START "; + shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); + shaders_header += "\n"; + // Call addHighLevelShaderMaterial() or addShaderMaterial() const c8* vertex_program_ptr = 0; const c8* pixel_program_ptr = 0; @@ -782,6 +771,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, geometry_program = shaders_header + geometry_program; geometry_program_ptr = geometry_program.c_str(); } + ShaderCallback *cb = new ShaderCallback(setter_factories); s32 shadermat = -1; if(is_highlevel){ infostream<<"Compiling high level shaders for "<<name<<std::endl; @@ -798,7 +788,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, scene::EPT_TRIANGLES, // Geometry shader input scene::EPT_TRIANGLE_STRIP, // Geometry shader output 0, // Support maximum number of vertices - callback, // Set-constant callback + cb, // Set-constant callback shaderinfo.base_material, // Base material 1 // Userdata passed to callback ); @@ -810,6 +800,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, dumpShaderProgram(warningstream, "Vertex", vertex_program); dumpShaderProgram(warningstream, "Pixel", pixel_program); dumpShaderProgram(warningstream, "Geometry", geometry_program); + delete cb; return shaderinfo; } } @@ -818,7 +809,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, shadermat = gpu->addShaderMaterial( vertex_program_ptr, // Vertex shader program pixel_program_ptr, // Pixel shader program - callback, // Set-constant callback + cb, // Set-constant callback shaderinfo.base_material, // Base material 0 // Userdata passed to callback ); @@ -830,9 +821,11 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, <<std::endl; dumpShaderProgram(warningstream, "Vertex", vertex_program); dumpShaderProgram(warningstream,"Pixel", pixel_program); + delete cb; return shaderinfo; } } + callbacks.push_back(cb); // HACK, TODO: investigate this better // Grab the material renderer once more so minetest doesn't crash on exit diff --git a/src/shader.h b/src/shader.h index b8aa88bce..766871f02 100644 --- a/src/shader.h +++ b/src/shader.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SHADER_HEADER #define SHADER_HEADER +#include <IMaterialRendererServices.h> #include "irrlichttypes_extrabloated.h" #include "threads.h" #include <string> @@ -43,8 +44,7 @@ class IGameDef; std::string getShaderPath(const std::string &name_of_shader, const std::string &filename); -struct ShaderInfo -{ +struct ShaderInfo { std::string name; video::E_MATERIAL_TYPE base_material; video::E_MATERIAL_TYPE material; @@ -66,20 +66,66 @@ namespace irr { namespace video { class IMaterialRendererServices; } } -class IShaderConstantSetter -{ + +class IShaderConstantSetter { public: virtual ~IShaderConstantSetter(){}; virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) = 0; }; + +class IShaderConstantSetterFactory { +public: + virtual ~IShaderConstantSetterFactory() {}; + virtual IShaderConstantSetter* create() = 0; +}; + + +template <typename T, std::size_t count=1> +class CachedShaderSetting { + const char *m_name; + T m_sent[count]; + bool has_been_set; + bool is_pixel; +protected: + CachedShaderSetting(const char *name, bool is_pixel) : + m_name(name), has_been_set(false), is_pixel(is_pixel) + {} +public: + void set(const T value[count], video::IMaterialRendererServices *services) + { + if (has_been_set && std::equal(m_sent, m_sent + count, value)) + return; + if (is_pixel) + services->setPixelShaderConstant(m_name, value, count); + else + services->setVertexShaderConstant(m_name, value, count); + std::copy(value, value + count, m_sent); + has_been_set = true; + } +}; + +template <typename T, std::size_t count = 1> +class CachedPixelShaderSetting : public CachedShaderSetting<T, count> { +public: + CachedPixelShaderSetting(const char *name) : + CachedShaderSetting<T, count>(name, true){} +}; + +template <typename T, std::size_t count = 1> +class CachedVertexShaderSetting : public CachedShaderSetting<T, count> { +public: + CachedVertexShaderSetting(const char *name) : + CachedShaderSetting<T, count>(name, false){} +}; + + /* ShaderSource creates and caches shaders. */ -class IShaderSource -{ +class IShaderSource { public: IShaderSource(){} virtual ~IShaderSource(){} @@ -90,8 +136,7 @@ public: const u8 material_type, const u8 drawtype){return 0;} }; -class IWritableShaderSource : public IShaderSource -{ +class IWritableShaderSource : public IShaderSource { public: IWritableShaderSource(){} virtual ~IWritableShaderSource(){} @@ -105,7 +150,7 @@ public: virtual void insertSourceShader(const std::string &name_of_shader, const std::string &filename, const std::string &program)=0; virtual void rebuildShaders()=0; - virtual void addGlobalConstantSetter(IShaderConstantSetter *setter)=0; + virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0; }; IWritableShaderSource* createShaderSource(IrrlichtDevice *device); diff --git a/src/sky.cpp b/src/sky.cpp index 682ff05e3..211a2dcdc 100644 --- a/src/sky.cpp +++ b/src/sky.cpp @@ -4,37 +4,37 @@ #include "ICameraSceneNode.h" #include "S3DVertex.h" #include "client/tile.h" -#include "noise.h" // easeCurve +#include "noise.h" // easeCurve #include "profiler.h" #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, ITextureSource *tsrc): scene::ISceneNode(parent, mgr, id), m_visible(true), - m_fallback_bg_color(255,255,255,255), + m_fallback_bg_color(255, 255, 255, 255), m_first_update(true), m_brightness(0.5), m_cloud_brightness(0.5), - m_bgcolor_bright_f(1,1,1,1), - m_skycolor_bright_f(1,1,1,1), - m_cloudcolor_bright_f(1,1,1,1) + m_bgcolor_bright_f(1, 1, 1, 1), + m_skycolor_bright_f(1, 1, 1, 1), + m_cloudcolor_bright_f(1, 1, 1, 1) { setAutomaticCulling(scene::EAC_OFF); - m_box.MaxEdge.set(0,0,0); - m_box.MinEdge.set(0,0,0); + m_box.MaxEdge.set(0, 0, 0); + m_box.MinEdge.set(0, 0, 0); - // create material + // Create material video::SMaterial mat; mat.Lighting = false; mat.ZBuffer = video::ECFN_NEVER; mat.ZWriteEnable = false; - mat.AntiAliasing=0; + mat.AntiAliasing = 0; mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; mat.BackfaceCulling = false; @@ -59,14 +59,15 @@ Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ? tsrc->getTexture("moon_tonemap.png") : NULL; - if (m_sun_texture){ + if (m_sun_texture) { m_materials[3] = mat; m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; if (m_sun_tonemap) m_materials[3].Lighting = true; } - if (m_moon_texture){ + + if (m_moon_texture) { m_materials[4] = mat; m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -74,11 +75,11 @@ Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, m_materials[4].Lighting = true; } - for(u32 i=0; i<SKY_STAR_COUNT; i++){ + for (u32 i = 0; i < SKY_STAR_COUNT; i++) { m_stars[i] = v3f( - myrand_range(-10000,10000), - myrand_range(-10000,10000), - myrand_range(-10000,10000) + myrand_range(-10000, 10000), + myrand_range(-10000, 10000), + myrand_range(-10000, 10000) ); m_stars[i].normalize(); } @@ -86,6 +87,7 @@ Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); } + void Sky::OnRegisterSceneNode() { if (IsVisible) @@ -94,10 +96,10 @@ void Sky::OnRegisterSceneNode() scene::ISceneNode::OnRegisterSceneNode(); } -//! renders the node. + void Sky::render() { - if(!m_visible) + if (!m_visible) return; video::IVideoDriver* driver = SceneManager->getVideoDriver(); @@ -108,7 +110,7 @@ void Sky::render() ScopeProfiler sp(g_profiler, "Sky::render()", SPT_AVG); - // draw perspective skybox + // Draw perspective skybox core::matrix4 translate(AbsoluteTransformation); translate.setTranslation(camera->getAbsolutePosition()); @@ -120,16 +122,15 @@ void Sky::render() driver->setTransform(video::ETS_WORLD, translate * scale); - if(m_sunlight_seen) - { + if (m_sunlight_seen) { float sunsize = 0.07; video::SColorf suncolor_f(1, 1, 0, 1); suncolor_f.r = 1; - suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7+m_time_brightness*(0.5))); - suncolor_f.b = MYMAX(0.0, m_brightness*0.95); + suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5)); + suncolor_f.b = MYMAX(0.0, m_brightness * 0.95); video::SColorf suncolor2_f(1, 1, 1, 1); suncolor_f.r = 1; - suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85+m_time_brightness*(0.5))); + suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85 + m_time_brightness * 0.5)); suncolor_f.b = MYMAX(0.0, m_brightness); float moonsize = 0.04; @@ -139,12 +140,12 @@ void Sky::render() float nightlength = 0.415; float wn = nightlength / 2; float wicked_time_of_day = 0; - if(m_time_of_day > wn && m_time_of_day < 1.0 - wn) - wicked_time_of_day = (m_time_of_day - wn)/(1.0-wn*2)*0.5 + 0.25; - else if(m_time_of_day < 0.5) + if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) + wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; + else if (m_time_of_day < 0.5) wicked_time_of_day = m_time_of_day / wn * 0.25; else - wicked_time_of_day = 1.0 - ((1.0-m_time_of_day) / wn * 0.25); + wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25); /*std::cerr<<"time_of_day="<<m_time_of_day<<" -> " <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/ @@ -154,98 +155,108 @@ void Sky::render() video::SColor mooncolor2 = mooncolor2_f.toSColor(); // Calculate offset normalized to the X dimension of a 512x1 px tonemap - float offset=(1.0-fabs(sin((m_time_of_day - 0.5)*irr::core::PI)))*511; + float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511; - if (m_sun_tonemap){ + if (m_sun_tonemap) { u8 * texels = (u8 *)m_sun_tonemap->lock(); video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255,texel->getRed(),texel->getGreen(), texel->getBlue()); + video::SColor texel_color (255, texel->getRed(), + texel->getGreen(), texel->getBlue()); m_sun_tonemap->unlock(); m_materials[3].EmissiveColor = texel_color; } - if (m_moon_tonemap){ + + if (m_moon_tonemap) { u8 * texels = (u8 *)m_moon_tonemap->lock(); video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4); - video::SColor texel_color (255,texel->getRed(),texel->getGreen(), texel->getBlue()); + video::SColor texel_color (255, texel->getRed(), + texel->getGreen(), texel->getBlue()); m_moon_tonemap->unlock(); m_materials[4].EmissiveColor = texel_color; } const f32 t = 1.0f; const f32 o = 0.0f; - static const u16 indices[4] = {0,1,2,3}; + static const u16 indices[4] = {0, 1, 2, 3}; video::S3DVertex vertices[4]; driver->setMaterial(m_materials[1]); - //video::SColor cloudyfogcolor(255,255,255,255); video::SColor cloudyfogcolor = m_bgcolor; - //video::SColor cloudyfogcolor = m_bgcolor.getInterpolated(m_skycolor, 0.5); - // Draw far cloudy fog thing - for(u32 j=0; j<4; j++) - { + // Draw far cloudy fog thing blended with skycolor + for (u32 j = 0; j < 4; j++) { video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); - vertices[0] = video::S3DVertex(-1, 0.08,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( 1, 0.08,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.12,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.12,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ - if(j==0) + vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { + if (j == 0) // Don't switch {} - else if(j==1) + else if (j == 1) // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); - else if(j==2) + else if (j == 2) // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); else - // Switch from -Z (south) to -Z (north) + // Switch from -Z (south) to +Z (north) vertices[i].Pos.rotateXZBy(-180); } driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - for(u32 j=0; j<4; j++) - { + + // Draw far cloudy fog thing + for (u32 j = 0; j < 4; j++) { video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1,-1.0,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( 1,-1.0,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.08,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.08,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ - if(j==0) + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { + if (j == 0) // Don't switch {} - else if(j==1) + else if (j == 1) // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); - else if(j==2) + else if (j == 2) // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); else - // Switch from -Z (south) to -Z (north) + // Switch from -Z (south) to +Z (north) vertices[i].Pos.rotateXZBy(-180); } driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } + // Draw bottom far cloudy fog thing + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); + vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); + vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); + driver->setMaterial(m_materials[2]); + // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png) { float mid1 = 0.25; - float mid = (wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1)); + float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1); float a_ = 1.0 - fabs(wicked_time_of_day - mid) * 35.0; float a = easeCurve(MYMAX(0, MYMIN(1, a_))); //std::cerr<<"a_="<<a_<<" a="<<a<<std::endl; - video::SColor c(255,255,255,255); - float y = -(1.0 - a) * 0.2; - vertices[0] = video::S3DVertex(-1,-0.05+y,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( 1,-0.05+y,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.2+y,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.2+y,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ - if(wicked_time_of_day < 0.5) + video::SColor c(255, 255, 255, 255); + float y = -(1.0 - a) * 0.22; + vertices[0] = video::S3DVertex(-1, -0.05 + y, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -0.05 + y, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.2 + y, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.2 + y, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { + if (wicked_time_of_day < 0.5) // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); else @@ -256,17 +267,17 @@ void Sky::render() } // Draw sun - if(wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85){ - if (!m_sun_texture){ + if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) { + if (!m_sun_texture) { driver->setMaterial(m_materials[1]); float d = sunsize * 1.7; video::SColor c = suncolor; - c.setAlpha(0.05*255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c.setAlpha(0.05 * 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -275,12 +286,12 @@ void Sky::render() d = sunsize * 1.2; c = suncolor; - c.setAlpha(0.15*255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c.setAlpha(0.15 * 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -288,11 +299,11 @@ void Sky::render() driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); d = sunsize; - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, suncolor, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, suncolor, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, suncolor, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, suncolor, t, o); - for(u32 i=0; i<4; i++){ + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -300,11 +311,11 @@ void Sky::render() driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); d = sunsize * 0.7; - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, suncolor2, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, suncolor2, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, suncolor2, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, suncolor2, t, o); - for(u32 i=0; i<4; i++){ + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor2, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor2, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -315,14 +326,14 @@ void Sky::render() float d = sunsize * 1.7; video::SColor c; if (m_sun_tonemap) - c = video::SColor (0,0,0,0); + c = video::SColor (0, 0, 0, 0); else - c = video::SColor (255,255,255,255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c = video::SColor (255, 255, 255, 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for(u32 i = 0; i < 4; i++) { // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -332,18 +343,17 @@ void Sky::render() } // Draw moon - if(wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) - { - if (!m_moon_texture){ + if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) { + if (!m_moon_texture) { driver->setMaterial(m_materials[1]); float d = moonsize * 1.9; video::SColor c = mooncolor; - c.setAlpha(0.05*255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c.setAlpha(0.05 * 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -352,12 +362,12 @@ void Sky::render() d = moonsize * 1.3; c = mooncolor; - c.setAlpha(0.15*255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c.setAlpha(0.15 * 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -365,11 +375,11 @@ void Sky::render() driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); d = moonsize; - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, mooncolor, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, mooncolor, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, mooncolor, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, mooncolor, t, o); - for(u32 i=0; i<4; i++){ + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, mooncolor, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, mooncolor, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -377,11 +387,11 @@ void Sky::render() driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); float d2 = moonsize * 0.6; - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, mooncolor2, t, t); - vertices[1] = video::S3DVertex( d2,-d,-1, 0,0,1, mooncolor2, o, t); - vertices[2] = video::S3DVertex( d2, d2,-1, 0,0,1, mooncolor2, o, o); - vertices[3] = video::S3DVertex(-d, d2,-1, 0,0,1, mooncolor2, t, o); - for(u32 i=0; i<4; i++){ + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor2, t, t); + vertices[1] = video::S3DVertex( d2,-d, -1, 0, 0, 1, mooncolor2, o, t); + vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o); + vertices[3] = video::S3DVertex(-d, d2, -1, 0, 0, 1, mooncolor2, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -392,14 +402,14 @@ void Sky::render() float d = moonsize * 1.9; video::SColor c; if (m_moon_tonemap) - c = video::SColor (0,0,0,0); + c = video::SColor (0, 0, 0, 0); else - c = video::SColor (255,255,255,255); - vertices[0] = video::S3DVertex(-d,-d,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( d,-d,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( d, d,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-d, d,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ + c = video::SColor (255, 255, 255, 255); + vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { // Switch from -Z (south) to -X (west) vertices[i].Pos.rotateXZBy(-90); vertices[i].Pos.rotateXYBy(wicked_time_of_day * 360 - 90); @@ -408,64 +418,63 @@ void Sky::render() } } - // Stars + // Draw stars driver->setMaterial(m_materials[1]); - do{ + do { float starbrightness = MYMAX(0, MYMIN(1, - (0.285 - fabs(wicked_time_of_day < 0.5 ? - wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10)); + (0.285 - fabs(wicked_time_of_day < 0.5 ? + wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10)); float f = starbrightness; float d = 0.007; - video::SColor starcolor(255, f*90,f*90,f*90); - if(starcolor.getBlue() < m_skycolor.getBlue()) + video::SColor starcolor(255, f * 90, f * 90, f * 90); + if (starcolor.getBlue() < m_skycolor.getBlue()) break; - u16 indices[SKY_STAR_COUNT*4]; - video::S3DVertex vertices[SKY_STAR_COUNT*4]; - for(u32 i=0; i<SKY_STAR_COUNT; i++){ - indices[i*4+0] = i*4+0; - indices[i*4+1] = i*4+1; - indices[i*4+2] = i*4+2; - indices[i*4+3] = i*4+3; + u16 indices[SKY_STAR_COUNT * 4]; + video::S3DVertex vertices[SKY_STAR_COUNT * 4]; + for (u32 i = 0; i < SKY_STAR_COUNT; i++) { + indices[i * 4 + 0] = i * 4 + 0; + indices[i * 4 + 1] = i * 4 + 1; + indices[i * 4 + 2] = i * 4 + 2; + indices[i * 4 + 3] = i * 4 + 3; v3f p = m_stars[i]; core::CMatrix4<f32> a; - a.buildRotateFromTo(v3f(0,1,0), v3f(d,1+d/2,0)); + a.buildRotateFromTo(v3f(0, 1, 0), v3f(d, 1 + d / 2, 0)); v3f p1 = p; a.rotateVect(p1); - a.buildRotateFromTo(v3f(0,1,0), v3f(d,1,d)); + a.buildRotateFromTo(v3f(0, 1, 0), v3f(d, 1, d)); v3f p2 = p; a.rotateVect(p2); - a.buildRotateFromTo(v3f(0,1,0), v3f(0,1-d/2,d)); + a.buildRotateFromTo(v3f(0, 1, 0), v3f(0, 1 - d / 2, d)); v3f p3 = p; a.rotateVect(p3); p.rotateXYBy(wicked_time_of_day * 360 - 90); p1.rotateXYBy(wicked_time_of_day * 360 - 90); p2.rotateXYBy(wicked_time_of_day * 360 - 90); p3.rotateXYBy(wicked_time_of_day * 360 - 90); - vertices[i*4+0].Pos = p; - vertices[i*4+0].Color = starcolor; - vertices[i*4+1].Pos = p1; - vertices[i*4+1].Color = starcolor; - vertices[i*4+2].Pos = p2; - vertices[i*4+2].Color = starcolor; - vertices[i*4+3].Pos = p3; - vertices[i*4+3].Color = starcolor; + vertices[i * 4 + 0].Pos = p; + vertices[i * 4 + 0].Color = starcolor; + vertices[i * 4 + 1].Pos = p1; + vertices[i * 4 + 1].Color = starcolor; + vertices[i * 4 + 2].Pos = p2; + vertices[i * 4 + 2].Color = starcolor; + vertices[i * 4 + 3].Pos = p3; + vertices[i * 4 + 3].Color = starcolor; } - driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT*4, - indices, SKY_STAR_COUNT, video::EVT_STANDARD, - scene::EPT_QUADS, video::EIT_16BIT); - }while(0); + driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4, + indices, SKY_STAR_COUNT, video::EVT_STANDARD, + scene::EPT_QUADS, video::EIT_16BIT); + } while(0); - for(u32 j=0; j<2; j++) - { - //video::SColor c = m_skycolor; + // Draw far cloudy fog thing below east and west horizons + for (u32 j = 0; j < 2; j++) { video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1,-1.0,-1, 0,0,1, c, t, t); - vertices[1] = video::S3DVertex( 1,-1.0,-1, 0,0,1, c, o, t); - vertices[2] = video::S3DVertex( 1,-0.02,-1, 0,0,1, c, o, o); - vertices[3] = video::S3DVertex(-1,-0.02,-1, 0,0,1, c, t, o); - for(u32 i=0; i<4; i++){ - //if(wicked_time_of_day < 0.5) - if(j==0) + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o); + for (u32 i = 0; i < 4; i++) { + //if (wicked_time_of_day < 0.5) + if (j == 0) // Switch from -Z (south) to +X (east) vertices[i].Pos.rotateXZBy(90); else @@ -477,20 +486,21 @@ void Sky::render() } } + void Sky::update(float time_of_day, float time_brightness, float direct_brightness, bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch) { // Stabilize initial brightness and color values by flooding updates - if(m_first_update){ + if (m_first_update) { /*dstream<<"First update with time_of_day="<<time_of_day <<" time_brightness="<<time_brightness <<" direct_brightness="<<direct_brightness <<" sunlight_seen="<<sunlight_seen<<std::endl;*/ m_first_update = false; - for(u32 i=0; i<100; i++){ + for (u32 i = 0; i < 100; i++) { update(time_of_day, time_brightness, direct_brightness, - sunlight_seen, cam_mode, yaw, pitch); + sunlight_seen, cam_mode, yaw, pitch); } return; } @@ -501,39 +511,42 @@ void Sky::update(float time_of_day, float time_brightness, bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35); - //video::SColorf bgcolor_bright_normal_f(170./255,200./255,230./255, 1.0); - video::SColorf bgcolor_bright_normal_f(155./255,193./255,240./255, 1.0); - video::SColorf bgcolor_bright_indoor_f(100./255,100./255,100./255, 1.0); - //video::SColorf bgcolor_bright_dawn_f(0.666,200./255*0.7,230./255*0.5,1.0); - //video::SColorf bgcolor_bright_dawn_f(0.666,0.549,0.220,1.0); - //video::SColorf bgcolor_bright_dawn_f(0.666*1.2,0.549*1.0,0.220*1.0, 1.0); - //video::SColorf bgcolor_bright_dawn_f(0.666*1.2,0.549*1.0,0.220*1.2,1.0); - video::SColorf bgcolor_bright_dawn_f - (155./255*1.2,193./255,240./255, 1.0); - - video::SColorf skycolor_bright_normal_f = - video::SColor(255, 140, 186, 250); - video::SColorf skycolor_bright_dawn_f = - video::SColor(255, 180, 186, 250); + /* + Development colours + + video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0); + video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0); + video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0); + video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0); + video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0); + + video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4); + video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44); + video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5); + */ + + video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240); + video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100); + video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240); + video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255); + + video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250); + video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250); + video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255); - video::SColorf cloudcolor_bright_normal_f = - video::SColor(255, 240,240,255); - //video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4); - //video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44); - //video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5); - video::SColorf cloudcolor_bright_dawn_f(1.0, 0.875, 0.75); + video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 240, 240, 255); + video::SColorf cloudcolor_bright_dawn_f = video::SColor(255, 255, 223, 191); float cloud_color_change_fraction = 0.95; - if(sunlight_seen){ - if(fabs(time_brightness - m_brightness) < 0.2){ + if (sunlight_seen) { + if (fabs(time_brightness - m_brightness) < 0.2) { m_brightness = m_brightness * 0.95 + time_brightness * 0.05; } else { m_brightness = m_brightness * 0.80 + time_brightness * 0.20; cloud_color_change_fraction = 0.0; } - } - else{ - if(direct_brightness < m_brightness) + } else { + if (direct_brightness < m_brightness) m_brightness = m_brightness * 0.95 + direct_brightness * 0.05; else m_brightness = m_brightness * 0.98 + direct_brightness * 0.02; @@ -541,29 +554,37 @@ void Sky::update(float time_of_day, float time_brightness, m_clouds_visible = true; float color_change_fraction = 0.98; - if(sunlight_seen){ - if(is_dawn){ + if (sunlight_seen) { + if (is_dawn) { // Dawn m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated( - bgcolor_bright_dawn_f, color_change_fraction); + bgcolor_bright_dawn_f, color_change_fraction); m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated( - skycolor_bright_dawn_f, color_change_fraction); + skycolor_bright_dawn_f, color_change_fraction); m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated( - cloudcolor_bright_dawn_f, color_change_fraction); + cloudcolor_bright_dawn_f, color_change_fraction); } else { - m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated( + if (time_brightness < 0.07) { // Night + m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated( + bgcolor_bright_night_f, color_change_fraction); + m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated( + skycolor_bright_night_f, color_change_fraction); + } else { // Day + m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated( bgcolor_bright_normal_f, color_change_fraction); - m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated( + m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated( skycolor_bright_normal_f, color_change_fraction); + } + m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated( - cloudcolor_bright_normal_f, color_change_fraction); + cloudcolor_bright_normal_f, color_change_fraction); } } else { m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated( - bgcolor_bright_indoor_f, color_change_fraction); + bgcolor_bright_indoor_f, color_change_fraction); m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated( - bgcolor_bright_indoor_f, color_change_fraction); + bgcolor_bright_indoor_f, color_change_fraction); m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated( - cloudcolor_bright_normal_f, color_change_fraction); + cloudcolor_bright_normal_f, color_change_fraction); m_clouds_visible = false; } @@ -572,63 +593,71 @@ void Sky::update(float time_of_day, float time_brightness, 255, bgcolor_bright.getRed() * m_brightness, bgcolor_bright.getGreen() * m_brightness, - bgcolor_bright.getBlue() * m_brightness); + bgcolor_bright.getBlue() * m_brightness + ); video::SColor skycolor_bright = m_skycolor_bright_f.toSColor(); m_skycolor = video::SColor( 255, skycolor_bright.getRed() * m_brightness, skycolor_bright.getGreen() * m_brightness, - skycolor_bright.getBlue() * m_brightness); + skycolor_bright.getBlue() * m_brightness + ); // Horizon coloring based on sun and moon direction during sunset and sunrise video::SColor pointcolor = video::SColor(255, 255, 255, m_bgcolor.getAlpha()); if (m_directional_colored_fog) { - if (m_horizon_blend() != 0) - { - // calculate hemisphere value from yaw, (inverted in third person front view) + if (m_horizon_blend() != 0) { + // Calculate hemisphere value from yaw, (inverted in third person front view) s8 dir_factor = 1; if (cam_mode > CAMERA_MODE_THIRD) dir_factor = -1; - f32 pointcolor_blend = wrapDegrees_0_360( yaw*dir_factor + 90); + f32 pointcolor_blend = wrapDegrees_0_360(yaw * dir_factor + 90); if (pointcolor_blend > 180) pointcolor_blend = 360 - pointcolor_blend; pointcolor_blend /= 180; - // bound view angle to determine where transition starts and ends - pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) * 1.375; - // combine the colors when looking up or down, otherwise turning looks weird - pointcolor_blend += (0.5 - pointcolor_blend) * (1 - MYMIN((90 - std::abs(pitch)) / 90 * 1.5, 1)); - // invert direction to match where the sun and moon are rising + // Bound view angle to determine where transition starts and ends + pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) * + 1.375; + // Combine the colors when looking up or down, otherwise turning looks weird + pointcolor_blend += (0.5 - pointcolor_blend) * + (1 - MYMIN((90 - std::fabs(pitch)) / 90 * 1.5, 1)); + // Invert direction to match where the sun and moon are rising if (m_time_of_day > 0.5) pointcolor_blend = 1 - pointcolor_blend; - // horizon colors of sun and moon + // Horizon colors of sun and moon f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1); video::SColorf pointcolor_sun_f(1, 1, 1, 1); - if (m_sun_tonemap) - { - pointcolor_sun_f.r = pointcolor_light * (float)m_materials[3].EmissiveColor.getRed() / 255; - pointcolor_sun_f.b = pointcolor_light * (float)m_materials[3].EmissiveColor.getBlue() / 255; - pointcolor_sun_f.g = pointcolor_light * (float)m_materials[3].EmissiveColor.getGreen() / 255; - } - else - { + if (m_sun_tonemap) { + pointcolor_sun_f.r = pointcolor_light * + (float)m_materials[3].EmissiveColor.getRed() / 255; + pointcolor_sun_f.b = pointcolor_light * + (float)m_materials[3].EmissiveColor.getBlue() / 255; + pointcolor_sun_f.g = pointcolor_light * + (float)m_materials[3].EmissiveColor.getGreen() / 255; + } else { pointcolor_sun_f.r = pointcolor_light * 1; - pointcolor_sun_f.b = pointcolor_light * (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75); - pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 + (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625); + pointcolor_sun_f.b = pointcolor_light * + (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75); + pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 + + (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625); } - video::SColorf pointcolor_moon_f(0.5 * pointcolor_light, 0.6 * pointcolor_light, 0.8 * pointcolor_light, 1); - if (m_moon_tonemap) - { - pointcolor_moon_f.r = pointcolor_light * (float)m_materials[4].EmissiveColor.getRed() / 255; - pointcolor_moon_f.b = pointcolor_light * (float)m_materials[4].EmissiveColor.getBlue() / 255; - pointcolor_moon_f.g = pointcolor_light * (float)m_materials[4].EmissiveColor.getGreen() / 255; + video::SColorf pointcolor_moon_f(0.5 * pointcolor_light, + 0.6 * pointcolor_light, 0.8 * pointcolor_light, 1); + if (m_moon_tonemap) { + pointcolor_moon_f.r = pointcolor_light * + (float)m_materials[4].EmissiveColor.getRed() / 255; + pointcolor_moon_f.b = pointcolor_light * + (float)m_materials[4].EmissiveColor.getBlue() / 255; + pointcolor_moon_f.g = pointcolor_light * + (float)m_materials[4].EmissiveColor.getGreen() / 255; } video::SColor pointcolor_sun = pointcolor_sun_f.toSColor(); video::SColor pointcolor_moon = pointcolor_moon_f.toSColor(); - // calculate the blend color + // Calculate the blend color pointcolor = m_mix_scolor(pointcolor_moon, pointcolor_sun, pointcolor_blend); } m_bgcolor = m_mix_scolor(m_bgcolor, pointcolor, m_horizon_blend() * 0.5); @@ -636,29 +665,29 @@ void Sky::update(float time_of_day, float time_brightness, } float cloud_direct_brightness = 0; - if(sunlight_seen) { + if (sunlight_seen) { if (!m_directional_colored_fog) { cloud_direct_brightness = time_brightness; - if(time_brightness >= 0.2 && time_brightness < 0.7) + if (time_brightness >= 0.2 && time_brightness < 0.7) cloud_direct_brightness *= 1.3; - } - else { - cloud_direct_brightness = MYMIN(m_horizon_blend() * 0.15 + m_time_brightness, 1); + } else { + cloud_direct_brightness = MYMIN(m_horizon_blend() * 0.15 + + m_time_brightness, 1); } } else { cloud_direct_brightness = direct_brightness; } + m_cloud_brightness = m_cloud_brightness * cloud_color_change_fraction + - cloud_direct_brightness * (1.0 - cloud_color_change_fraction); + cloud_direct_brightness * (1.0 - cloud_color_change_fraction); m_cloudcolor_f = video::SColorf( - m_cloudcolor_bright_f.r * m_cloud_brightness, - m_cloudcolor_bright_f.g * m_cloud_brightness, - m_cloudcolor_bright_f.b * m_cloud_brightness, - 1.0); + m_cloudcolor_bright_f.r * m_cloud_brightness, + m_cloudcolor_bright_f.g * m_cloud_brightness, + m_cloudcolor_bright_f.b * m_cloud_brightness, + 1.0 + ); if (m_directional_colored_fog) { - m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f, video::SColorf(pointcolor), m_horizon_blend() * 0.25); + m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f, + video::SColorf(pointcolor), m_horizon_blend() * 0.25); } - } - - diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index e2b6d937a..317667f52 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -41,9 +41,9 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include "log.h" #include "util/numeric.h" // myrand() #include "porting.h" -#include <map> #include <vector> #include <fstream> +#include "util/cpp11_container.h" #define BUFFER_SIZE 30000 @@ -144,6 +144,7 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile, ov_clear(oggFile); infostream << "Audio: Error decoding " << filename_for_logging << std::endl; + delete snd; return NULL; } @@ -270,8 +271,8 @@ private: ALCdevice *m_device; ALCcontext *m_context; int m_next_id; - std::map<std::string, std::vector<SoundBuffer*> > m_buffers; - std::map<int, PlayingSound*> m_sounds_playing; + UNORDERED_MAP<std::string, std::vector<SoundBuffer*> > m_buffers; + UNORDERED_MAP<int, PlayingSound*> m_sounds_playing; v3f m_listener_pos; public: bool m_is_initialized; @@ -336,7 +337,7 @@ public: alcCloseDevice(m_device); m_device = NULL; - for (std::map<std::string, std::vector<SoundBuffer*> >::iterator i = m_buffers.begin(); + for (UNORDERED_MAP<std::string, std::vector<SoundBuffer*> >::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) { for (std::vector<SoundBuffer*>::iterator iter = (*i).second.begin(); iter != (*i).second.end(); ++iter) { @@ -350,7 +351,7 @@ public: void addBuffer(const std::string &name, SoundBuffer *buf) { - std::map<std::string, std::vector<SoundBuffer*> >::iterator i = + UNORDERED_MAP<std::string, std::vector<SoundBuffer*> >::iterator i = m_buffers.find(name); if(i != m_buffers.end()){ i->second.push_back(buf); @@ -364,7 +365,7 @@ public: SoundBuffer* getBuffer(const std::string &name) { - std::map<std::string, std::vector<SoundBuffer*> >::iterator i = + UNORDERED_MAP<std::string, std::vector<SoundBuffer*> >::iterator i = m_buffers.find(name); if(i == m_buffers.end()) return NULL; @@ -442,8 +443,7 @@ public: void deleteSound(int id) { - std::map<int, PlayingSound*>::iterator i = - m_sounds_playing.find(id); + UNORDERED_MAP<int, PlayingSound*>::iterator i = m_sounds_playing.find(id); if(i == m_sounds_playing.end()) return; PlayingSound *sound = i->second; @@ -483,10 +483,8 @@ public: <<m_sounds_playing.size()<<" playing sounds, " <<m_buffers.size()<<" sound names loaded"<<std::endl; std::set<int> del_list; - for(std::map<int, PlayingSound*>::iterator - i = m_sounds_playing.begin(); - i != m_sounds_playing.end(); ++i) - { + for(UNORDERED_MAP<int, PlayingSound*>::iterator i = m_sounds_playing.begin(); + i != m_sounds_playing.end(); ++i) { int id = i->first; PlayingSound *sound = i->second; // If not playing, remove it @@ -582,9 +580,8 @@ public: } void updateSoundPosition(int id, v3f pos) { - std::map<int, PlayingSound*>::iterator i = - m_sounds_playing.find(id); - if(i == m_sounds_playing.end()) + UNORDERED_MAP<int, PlayingSound*>::iterator i = m_sounds_playing.find(id); + if (i == m_sounds_playing.end()) return; PlayingSound *sound = i->second; diff --git a/src/subgame.cpp b/src/subgame.cpp index 7e9a0b368..55bbd3954 100644 --- a/src/subgame.cpp +++ b/src/subgame.cpp @@ -313,8 +313,8 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp Settings conf; MapgenParams params; - params.load(*g_settings); - params.save(conf); + params.readParams(g_settings); + params.writeParams(&conf); conf.writeLines(oss); oss << "[end_of_params]\n"; diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp index c86a960fa..a8c4ebaef 100644 --- a/src/terminal_chat_console.cpp +++ b/src/terminal_chat_console.cpp @@ -345,9 +345,11 @@ void TerminalChatConsole::step(int ch) if (p.first > m_log_level) continue; - m_chat_backend.addMessage( - utf8_to_wide(Logger::getLevelLabel(p.first)), - utf8_to_wide(p.second)); + std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first)); + if (!g_settings->getBool("disable_escape_sequences")) { + error_message = L"\x1b(c@red)" + error_message + L"\x1b(c@white)"; + } + m_chat_backend.addMessage(error_message, utf8_to_wide(p.second)); } // handle input @@ -438,7 +440,7 @@ void TerminalChatConsole::draw_text() continue; for (u32 i = 0; i < line.fragments.size(); ++i) { const ChatFormattedFragment& fragment = line.fragments[i]; - addstr(wide_to_utf8(fragment.text).c_str()); + addstr(wide_to_utf8(fragment.text.getString()).c_str()); } } } diff --git a/src/threading/event.cpp b/src/threading/event.cpp index 165f9d83f..0d5928f10 100644 --- a/src/threading/event.cpp +++ b/src/threading/event.cpp @@ -27,8 +27,8 @@ DEALINGS IN THE SOFTWARE. Event::Event() { -#if __cplusplus < 201103L -# ifdef _WIN32 +#ifndef USE_CPP11_MUTEX +# if USE_WIN_MUTEX event = CreateEvent(NULL, false, false, NULL); # else pthread_cond_init(&cv, NULL); @@ -38,10 +38,10 @@ Event::Event() #endif } -#if __cplusplus < 201103L +#ifndef USE_CPP11_MUTEX Event::~Event() { -#ifdef _WIN32 +#if USE_WIN_MUTEX CloseHandle(event); #else pthread_cond_destroy(&cv); @@ -53,13 +53,13 @@ Event::~Event() void Event::wait() { -#if __cplusplus >= 201103L +#if USE_CPP11_MUTEX MutexAutoLock lock(mutex); while (!notified) { cv.wait(lock); } notified = false; -#elif defined(_WIN32) +#elif USE_WIN_MUTEX WaitForSingleObject(event, INFINITE); #else pthread_mutex_lock(&mutex); @@ -74,11 +74,11 @@ void Event::wait() void Event::signal() { -#if __cplusplus >= 201103L +#if USE_CPP11_MUTEX MutexAutoLock lock(mutex); notified = true; cv.notify_one(); -#elif defined(_WIN32) +#elif USE_WIN_MUTEX SetEvent(event); #else pthread_mutex_lock(&mutex); diff --git a/src/threading/event.h b/src/threading/event.h index dd5164576..26cb8997a 100644 --- a/src/threading/event.h +++ b/src/threading/event.h @@ -26,17 +26,12 @@ DEALINGS IN THE SOFTWARE. #ifndef THREADING_EVENT_H #define THREADING_EVENT_H -#if __cplusplus >= 201103L +#include "threads.h" + +#if USE_CPP11_MUTEX #include <condition_variable> #include "threading/mutex.h" #include "threading/mutex_auto_lock.h" -#elif defined(_WIN32) - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - #include <windows.h> -#else - #include <pthread.h> #endif @@ -49,18 +44,18 @@ DEALINGS IN THE SOFTWARE. class Event { public: Event(); -#if __cplusplus < 201103L +#ifndef USE_CPP11_MUTEX ~Event(); #endif void wait(); void signal(); private: -#if __cplusplus >= 201103L +#if USE_CPP11_MUTEX std::condition_variable cv; Mutex mutex; bool notified; -#elif defined(_WIN32) +#elif USE_WIN_MUTEX HANDLE event; #else pthread_cond_t cv; diff --git a/src/threading/mutex.cpp b/src/threading/mutex.cpp index f2b07bec3..0908b5d37 100644 --- a/src/threading/mutex.cpp +++ b/src/threading/mutex.cpp @@ -23,14 +23,13 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Windows std::mutex is much slower than the critical section API -#if __cplusplus < 201103L || defined(_WIN32) +#include "threads.h" + +#ifndef USE_CPP11_MUTEX #include "threading/mutex.h" -#ifndef _WIN32 - #include <cassert> -#endif +#include <cassert> #define UNUSED(expr) do { (void)(expr); } while (0) @@ -47,7 +46,7 @@ Mutex::Mutex(bool recursive) void Mutex::init_mutex(bool recursive) { -#ifdef _WIN32 +#if USE_WIN_MUTEX // Windows critical sections are recursive by default UNUSED(recursive); @@ -69,7 +68,7 @@ void Mutex::init_mutex(bool recursive) Mutex::~Mutex() { -#ifdef _WIN32 +#if USE_WIN_MUTEX DeleteCriticalSection(&mutex); #else int ret = pthread_mutex_destroy(&mutex); @@ -80,7 +79,7 @@ Mutex::~Mutex() void Mutex::lock() { -#ifdef _WIN32 +#if USE_WIN_MUTEX EnterCriticalSection(&mutex); #else int ret = pthread_mutex_lock(&mutex); @@ -91,7 +90,7 @@ void Mutex::lock() void Mutex::unlock() { -#ifdef _WIN32 +#if USE_WIN_MUTEX LeaveCriticalSection(&mutex); #else int ret = pthread_mutex_unlock(&mutex); @@ -104,5 +103,5 @@ RecursiveMutex::RecursiveMutex() : Mutex(true) {} -#endif +#endif // C++11 diff --git a/src/threading/mutex.h b/src/threading/mutex.h index dadbd050c..fb5c029fc 100644 --- a/src/threading/mutex.h +++ b/src/threading/mutex.h @@ -26,14 +26,15 @@ DEALINGS IN THE SOFTWARE. #ifndef THREADING_MUTEX_H #define THREADING_MUTEX_H -// Windows std::mutex is much slower than the critical section API -#if __cplusplus >= 201103L && !defined(_WIN32) +#include "threads.h" + +#if USE_CPP11_MUTEX #include <mutex> using Mutex = std::mutex; using RecursiveMutex = std::recursive_mutex; #else -#ifdef _WIN32 +#if USE_WIN_MUTEX #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif @@ -41,7 +42,7 @@ DEALINGS IN THE SOFTWARE. #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> -#else // pthread +#else #include <pthread.h> #endif @@ -59,9 +60,9 @@ protected: Mutex(bool recursive); void init_mutex(bool recursive); private: -#ifdef _WIN32 +#if USE_WIN_MUTEX CRITICAL_SECTION mutex; -#else // pthread +#else pthread_mutex_t mutex; #endif @@ -76,6 +77,6 @@ public: DISABLE_CLASS_COPY(RecursiveMutex); }; -#endif // C++11 +#endif // C++11 #endif diff --git a/src/threading/mutex_auto_lock.h b/src/threading/mutex_auto_lock.h index 25caf7e14..d79c68a93 100644 --- a/src/threading/mutex_auto_lock.h +++ b/src/threading/mutex_auto_lock.h @@ -26,7 +26,9 @@ DEALINGS IN THE SOFTWARE. #ifndef THREADING_MUTEX_AUTO_LOCK_H #define THREADING_MUTEX_AUTO_LOCK_H -#if __cplusplus >= 201103L +#include "threads.h" + +#if USE_CPP11_MUTEX #include <mutex> using MutexAutoLock = std::unique_lock<std::mutex>; using RecursiveMutexAutoLock = std::unique_lock<std::recursive_mutex>; diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 5161a6c01..fbe4ba1f3 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -54,7 +54,7 @@ DEALINGS IN THE SOFTWARE. // for setName -#if defined(linux) || defined(__linux) +#if defined(__linux__) #include <sys/prctl.h> #elif defined(__FreeBSD__) || defined(__OpenBSD__) #include <pthread_np.h> @@ -70,7 +70,7 @@ DEALINGS IN THE SOFTWARE. // for bindToProcessor #if __FreeBSD_version >= 702106 typedef cpuset_t cpu_set_t; -#elif defined(__linux) || defined(linux) +#elif defined(__linux__) #include <sched.h> #elif defined(__sun) || defined(sun) #include <sys/types.h> @@ -198,7 +198,7 @@ bool Thread::kill() m_running = false; -#ifdef _WIN32 +#if USE_WIN_THREADS TerminateThread(m_thread_handle, 0); CloseHandle(m_thread_handle); #else @@ -261,7 +261,7 @@ DWORD WINAPI Thread::threadProc(LPVOID param) void Thread::setName(const std::string &name) { -#if defined(linux) || defined(__linux) +#if defined(__linux__) // It would be cleaner to do this with pthread_setname_np, // which was added to glibc in version 2.12, but some major @@ -310,10 +310,16 @@ void Thread::setName(const std::string &name) unsigned int Thread::getNumberOfProcessors() { -#if __cplusplus >= 201103L +#if USE_CPP11_THREADS return std::thread::hardware_concurrency(); +#elif USE_WIN_THREADS + + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; + #elif defined(_SC_NPROCESSORS_ONLN) return sysconf(_SC_NPROCESSORS_ONLN); @@ -335,12 +341,6 @@ unsigned int Thread::getNumberOfProcessors() return get_nprocs(); -#elif defined(_WIN32) - - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; - #elif defined(PTW32_VERSION) || defined(__hpux) return pthread_num_processors_np(); @@ -359,11 +359,11 @@ bool Thread::bindToProcessor(unsigned int proc_number) return false; -#elif defined(_WIN32) +#elif USE_WIN_THREADS return SetThreadAffinityMask(getThreadHandle(), 1 << proc_number); -#elif __FreeBSD_version >= 702106 || defined(__linux) || defined(linux) +#elif __FreeBSD_version >= 702106 || defined(__linux__) cpu_set_t cpuset; @@ -407,7 +407,7 @@ bool Thread::bindToProcessor(unsigned int proc_number) bool Thread::setPriority(int prio) { -#if defined(_WIN32) +#if USE_WIN_THREADS return SetThreadPriority(getThreadHandle(), prio); diff --git a/src/threading/thread.h b/src/threading/thread.h index de800ecb7..14a0e13ab 100644 --- a/src/threading/thread.h +++ b/src/threading/thread.h @@ -32,9 +32,6 @@ DEALINGS IN THE SOFTWARE. #include "threads.h" #include <string> -#if USE_CPP11_THREADS - #include <thread> // for std::thread -#endif #ifdef _AIX #include <sys/thread.h> // for tid_t #endif @@ -157,9 +154,11 @@ private: Atomic<bool> m_running; Mutex m_mutex; -#ifndef USE_CPP11_THREADS +#if USE_CPP11_THREADS + std::thread *m_thread_obj; +#else threadhandle_t m_thread_handle; -# if _WIN32 +# if USE_WIN_THREADS threadid_t m_thread_id; # endif #endif @@ -172,10 +171,6 @@ private: tid_t m_kernel_thread_id; #endif -#if USE_CPP11_THREADS - std::thread *m_thread_obj; -#endif - DISABLE_CLASS_COPY(Thread); }; diff --git a/src/threads.h b/src/threads.h index d4306f631..ce98593cd 100644 --- a/src/threads.h +++ b/src/threads.h @@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define THREADS_HEADER // -// Determine which threading API we will use +// Determine which threading APIs we will use // #if __cplusplus >= 201103L #define USE_CPP11_THREADS 1 @@ -31,11 +31,27 @@ with this program; if not, write to the Free Software Foundation, Inc., #define USE_POSIX_THREADS 1 #endif +#if defined(_WIN32) + // Prefer critical section API because std::mutex is much slower on Windows + #define USE_WIN_MUTEX 1 +#elif __cplusplus >= 201103L + #define USE_CPP11_MUTEX 1 +#else + #define USE_POSIX_MUTEX 1 +#endif + /////////////// #if USE_CPP11_THREADS #include <thread> +#elif USE_POSIX_THREADS + #include <pthread.h> +#else + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include <windows.h> #endif #include "threading/mutex.h" diff --git a/src/tool.cpp b/src/tool.cpp index 54b9f15f4..20b71fb31 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -34,24 +34,23 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const writeF1000(os, full_punch_interval); writeS16(os, max_drop_level); writeU32(os, groupcaps.size()); - for(std::map<std::string, ToolGroupCap>::const_iterator - i = groupcaps.begin(); i != groupcaps.end(); ++i){ + for (ToolGCMap::const_iterator i = groupcaps.begin(); i != groupcaps.end(); ++i) { const std::string *name = &i->first; const ToolGroupCap *cap = &i->second; os<<serializeString(*name); writeS16(os, cap->uses); writeS16(os, cap->maxlevel); writeU32(os, cap->times.size()); - for(std::map<int, float>::const_iterator - i = cap->times.begin(); i != cap->times.end(); ++i){ + for (UNORDERED_MAP<int, float>::const_iterator + i = cap->times.begin(); i != cap->times.end(); ++i) { writeS16(os, i->first); writeF1000(os, i->second); } } if(protocol_version > 17){ writeU32(os, damageGroups.size()); - for(std::map<std::string, s16>::const_iterator - i = damageGroups.begin(); i != damageGroups.end(); ++i){ + for (DamageGroup::const_iterator i = damageGroups.begin(); + i != damageGroups.end(); ++i) { os<<serializeString(i->first); writeS16(os, i->second); } @@ -106,7 +105,7 @@ DigParams getDigParams(const ItemGroupList &groups, default: break; } - + // Values to be returned (with a bit of conversion) bool result_diggable = false; float result_time = 0.0; @@ -115,8 +114,8 @@ DigParams getDigParams(const ItemGroupList &groups, int level = itemgroup_get(groups, "level"); //infostream<<"level="<<level<<std::endl; - for(std::map<std::string, ToolGroupCap>::const_iterator - i = tp->groupcaps.begin(); i != tp->groupcaps.end(); ++i){ + for (ToolGCMap::const_iterator i = tp->groupcaps.begin(); + i != tp->groupcaps.end(); ++i) { const std::string &name = i->first; //infostream<<"group="<<name<<std::endl; const ToolGroupCap &cap = i->second; @@ -163,8 +162,8 @@ HitParams getHitParams(const ItemGroupList &armor_groups, s16 damage = 0; float full_punch_interval = tp->full_punch_interval; - for(std::map<std::string, s16>::const_iterator - i = tp->damageGroups.begin(); i != tp->damageGroups.end(); ++i){ + for (DamageGroup::const_iterator i = tp->damageGroups.begin(); + i != tp->damageGroups.end(); ++i) { s16 armor = itemgroup_get(armor_groups, i->first); damage += i->second * rangelim(time_from_last_punch / full_punch_interval, 0.0, 1.0) * armor / 100.0; @@ -197,7 +196,7 @@ PunchDamageResult getPunchDamage( do_hit = false; } } - + PunchDamageResult result; if(do_hit) { diff --git a/src/tool.h b/src/tool.h index 509561a16..ebba5b749 100644 --- a/src/tool.h +++ b/src/tool.h @@ -23,12 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include <string> #include <iostream> -#include <map> +#include "util/cpp11_container.h" #include "itemgroup.h" struct ToolGroupCap { - std::map<int, float> times; + UNORDERED_MAP<int, float> times; int maxlevel; int uses; @@ -39,8 +39,8 @@ struct ToolGroupCap bool getTime(int rating, float *time) const { - std::map<int, float>::const_iterator i = times.find(rating); - if(i == times.end()){ + UNORDERED_MAP<int, float>::const_iterator i = times.find(rating); + if (i == times.end()) { *time = 0; return false; } @@ -50,22 +50,19 @@ struct ToolGroupCap }; -// CLANG SUCKS DONKEY BALLS -typedef std::map<std::string, struct ToolGroupCap> ToolGCMap; -typedef std::map<std::string, s16> DamageGroup; +typedef UNORDERED_MAP<std::string, struct ToolGroupCap> ToolGCMap; +typedef UNORDERED_MAP<std::string, s16> DamageGroup; struct ToolCapabilities { float full_punch_interval; int max_drop_level; - // CLANG SUCKS DONKEY BALLS ToolGCMap groupcaps; DamageGroup damageGroups; ToolCapabilities( float full_punch_interval_=1.4, int max_drop_level_=1, - // CLANG SUCKS DONKEY BALLS ToolGCMap groupcaps_=ToolGCMap(), DamageGroup damageGroups_=DamageGroup() ): diff --git a/src/touchscreengui.cpp b/src/touchscreengui.cpp index f51b2d5fa..8d210c63a 100644 --- a/src/touchscreengui.cpp +++ b/src/touchscreengui.cpp @@ -177,8 +177,10 @@ void AutoHideButtonBar::init(ISimpleTextureSource* tsrc, AutoHideButtonBar::~AutoHideButtonBar() { - m_starter.guibutton->setVisible(false); - m_starter.guibutton->drop(); + if (m_starter.guibutton) { + m_starter.guibutton->setVisible(false); + m_starter.guibutton->drop(); + } } void AutoHideButtonBar::addButton(touch_gui_button_id button_id, @@ -414,7 +416,7 @@ void AutoHideButtonBar::show() TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver): m_device(device), m_guienv(device->getGUIEnvironment()), - m_camera_yaw(0.0), + m_camera_yaw_change(0.0), m_camera_pitch(0.0), m_visible(false), m_move_id(-1), @@ -835,17 +837,11 @@ void TouchScreenGUI::translateEvent(const SEvent &event) /* adapt to similar behaviour as pc screen */ double d = g_settings->getFloat("mouse_sensitivity") *4; - double old_yaw = m_camera_yaw; + double old_yaw = m_camera_yaw_change; double old_pitch = m_camera_pitch; - m_camera_yaw -= dx * d; - m_camera_pitch = MYMIN(MYMAX( m_camera_pitch + (dy * d),-180),180); - - while (m_camera_yaw < 0) - m_camera_yaw += 360; - - while (m_camera_yaw > 360) - m_camera_yaw -= 360; + m_camera_yaw_change -= dx * d; + m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180); // update shootline m_shootline = m_device diff --git a/src/touchscreengui.h b/src/touchscreengui.h index d8106a260..53fc6d683 100644 --- a/src/touchscreengui.h +++ b/src/touchscreengui.h @@ -75,7 +75,7 @@ struct button_info { float repeatdelay; irr::EKEY_CODE keycode; std::vector<int> ids; - IGUIButton* guibutton; + IGUIButton* guibutton = NULL; bool immediate_release; }; @@ -147,8 +147,14 @@ public: void init(ISimpleTextureSource* tsrc); - double getYaw() { return m_camera_yaw; } + double getYawChange() { + double res = m_camera_yaw_change; + m_camera_yaw_change = 0; + return res; + } + double getPitch() { return m_camera_pitch; } + line3d<f32> getShootline() { return m_shootline; } void step(float dtime); @@ -170,7 +176,7 @@ private: bool m_visible; // is the gui visible /* value in degree */ - double m_camera_yaw; + double m_camera_yaw_change; double m_camera_pitch; line3d<f32> m_shootline; diff --git a/src/treegen.cpp b/src/treegen.cpp index 208f34552..f37bf0e86 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -31,7 +31,7 @@ namespace treegen { void make_tree(MMVManip &vmanip, v3s16 p0, - bool is_apple_tree, INodeDefManager *ndef, int seed) + bool is_apple_tree, INodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine @@ -149,7 +149,7 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, TreeDef tree_definition) { MapNode dirtnode(ndef->getId("mapgen_dirt")); - int seed; + s32 seed; if (tree_definition.explicit_seed) seed = tree_definition.seed + 14002; else @@ -649,7 +649,7 @@ v3f transposeMatrix(irr::core::matrix4 M, v3f v) } -void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) +void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine @@ -748,7 +748,7 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed } -void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) +void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed) { /* NOTE: Tree-placing code is currently duplicated in the engine @@ -770,9 +770,9 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) MapNode snownode(c_snow); PseudoRandom pr(seed); - s16 trunk_h = pr.range(9, 13); + u16 trunk_h = pr.range(9, 13); v3s16 p1 = p0; - for (s16 ii = 0; ii < trunk_h; ii++) { + for (u16 ii = 0; ii < trunk_h; ii++) { if (vmanip.m_area.contains(p1)) { u32 vi = vmanip.m_area.index(p1); vmanip.m_data[vi] = treenode; @@ -790,7 +790,7 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) leaves_d[i] = 0; // Upper branches - s16 dev = 3; + u16 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)); diff --git a/src/treegen.h b/src/treegen.h index 4b0089d1e..4e6f95e67 100644 --- a/src/treegen.h +++ b/src/treegen.h @@ -54,19 +54,19 @@ namespace treegen { bool thin_branches; MapNode fruitnode; int fruit_chance; - int seed; + s32 seed; bool explicit_seed; }; // Add default tree void make_tree(MMVManip &vmanip, v3s16 p0, - bool is_apple_tree, INodeDefManager *ndef, int seed); + bool is_apple_tree, INodeDefManager *ndef, s32 seed); // Add jungle tree void make_jungletree(MMVManip &vmanip, v3s16 p0, - INodeDefManager *ndef, int seed); + INodeDefManager *ndef, s32 seed); // Add pine tree void make_pine_tree(MMVManip &vmanip, v3s16 p0, - INodeDefManager *ndef, int seed); + INodeDefManager *ndef, s32 seed); // Add L-Systems tree (used by engine) treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index a07ed8ba5..7ad38099c 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -6,11 +6,13 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.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_player.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp @@ -22,3 +24,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelalgorithms.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelmanipulator.cpp PARENT_SCOPE) + +set (UNITTEST_CLIENT_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp + PARENT_SCOPE) diff --git a/src/unittest/test_filepath.cpp b/src/unittest/test_filepath.cpp index 6ea7ac076..ac2d69b5a 100644 --- a/src/unittest/test_filepath.cpp +++ b/src/unittest/test_filepath.cpp @@ -252,6 +252,9 @@ void TestFilePath::testRemoveRelativePathComponent() path = p("."); result = fs::RemoveRelativePathComponents(path); UASSERT(result == ""); + path = p("../a"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == ""); path = p("./subdir/../.."); result = fs::RemoveRelativePathComponents(path); UASSERT(result == ""); diff --git a/src/unittest/test_keycode.cpp b/src/unittest/test_keycode.cpp new file mode 100644 index 000000000..dd3d75a5b --- /dev/null +++ b/src/unittest/test_keycode.cpp @@ -0,0 +1,129 @@ +/* +Minetest +Copyright (C) 2016 sfan5 <sfan5@live.de> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <string> +#include "exceptions.h" +#include "keycode.h" + +class TestKeycode : public TestBase { +public: + TestKeycode() { TestManager::registerTestModule(this); } + const char *getName() { return "TestKeycode"; } + + void runTests(IGameDef *gamedef); + + void testCreateFromString(); + void testCreateFromSKeyInput(); + void testCompare(); +}; + +static TestKeycode g_test_instance; + +void TestKeycode::runTests(IGameDef *gamedef) +{ + TEST(testCreateFromString); + TEST(testCreateFromSKeyInput); + TEST(testCompare); +} + +//////////////////////////////////////////////////////////////////////////////// + +#define UASSERTEQ_STR(one, two) UASSERT(strcmp(one, two) == 0) + +void TestKeycode::testCreateFromString() +{ + KeyPress k; + + // Character key, from char + k = KeyPress("R"); + UASSERTEQ_STR(k.sym(), "KEY_KEY_R"); + UASSERTCMP(int, >, strlen(k.name()), 0); // should have human description + + // Character key, from identifier + k = KeyPress("KEY_KEY_B"); + UASSERTEQ_STR(k.sym(), "KEY_KEY_B"); + UASSERTCMP(int, >, strlen(k.name()), 0); + + // Non-Character key, from identifier + k = KeyPress("KEY_UP"); + UASSERTEQ_STR(k.sym(), "KEY_UP"); + UASSERTCMP(int, >, strlen(k.name()), 0); + + k = KeyPress("KEY_F6"); + UASSERTEQ_STR(k.sym(), "KEY_F6"); + UASSERTCMP(int, >, strlen(k.name()), 0); + + // Irrlicht-unknown key, from char + k = KeyPress("/"); + UASSERTEQ_STR(k.sym(), "/"); + UASSERTCMP(int, >, strlen(k.name()), 0); +} + +void TestKeycode::testCreateFromSKeyInput() +{ + KeyPress k; + irr::SEvent::SKeyInput in; + + // Character key + in.Key = irr::KEY_KEY_3; + in.Char = L'3'; + k = KeyPress(in); + UASSERTEQ_STR(k.sym(), "KEY_KEY_3"); + + // Non-Character key + in.Key = irr::KEY_RSHIFT; + in.Char = L'\0'; + k = KeyPress(in); + UASSERTEQ_STR(k.sym(), "KEY_RSHIFT"); + + // Irrlicht-unknown key + in.Key = irr::KEY_KEY_CODES_COUNT; + in.Char = L'?'; + k = KeyPress(in); + UASSERTEQ_STR(k.sym(), "?"); + + // prefer_character mode + in.Key = irr::KEY_COMMA; + in.Char = L'G'; + k = KeyPress(in, true); + UASSERTEQ_STR(k.sym(), "KEY_KEY_G"); +} + +void TestKeycode::testCompare() +{ + // Basic comparison + UASSERT(KeyPress("5") == KeyPress("KEY_KEY_5")); + UASSERT(!(KeyPress("5") == KeyPress("KEY_NUMPAD_5"))); + + // Matching char suffices + // note: This is a real-world example, Irrlicht maps XK_equal to irr::KEY_PLUS on Linux + irr::SEvent::SKeyInput in; + in.Key = irr::KEY_PLUS; + in.Char = L'='; + UASSERT(KeyPress("=") == KeyPress(in)); + + // Matching keycode suffices + irr::SEvent::SKeyInput in2; + in.Key = in2.Key = irr::KEY_OEM_CLEAR; + in.Char = L'\0'; + in2.Char = L';'; + UASSERT(KeyPress(in) == KeyPress(in2)); +} diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp new file mode 100644 index 000000000..4f5ac80f2 --- /dev/null +++ b/src/unittest/test_map_settings_manager.cpp @@ -0,0 +1,261 @@ + /* +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 "noise.h" +#include "settings.h" +#include "mapgen_v5.h" +#include "util/sha1.h" +#include "map_settings_manager.h" + +class TestMapSettingsManager : public TestBase { +public: + TestMapSettingsManager() { TestManager::registerTestModule(this); } + const char *getName() { return "TestMapSettingsManager"; } + + void makeUserConfig(Settings *conf); + std::string makeMetaFile(bool make_corrupt); + + void runTests(IGameDef *gamedef); + + void testMapSettingsManager(); + void testMapMetaSaveLoad(); + void testMapMetaFailures(); +}; + +static TestMapSettingsManager g_test_instance; + +void TestMapSettingsManager::runTests(IGameDef *gamedef) +{ + TEST(testMapSettingsManager); + TEST(testMapMetaSaveLoad); + TEST(testMapMetaFailures); +} + +//////////////////////////////////////////////////////////////////////////////// + + +void check_noise_params(const NoiseParams *np1, const NoiseParams *np2) +{ + UASSERTEQ(float, np1->offset, np2->offset); + UASSERTEQ(float, np1->scale, np2->scale); + UASSERT(np1->spread == np2->spread); + UASSERTEQ(s32, np1->seed, np2->seed); + UASSERTEQ(u16, np1->octaves, np2->octaves); + UASSERTEQ(float, np1->persist, np2->persist); + UASSERTEQ(float, np1->lacunarity, np2->lacunarity); + UASSERTEQ(u32, np1->flags, np2->flags); +} + + +std::string read_file_to_string(const std::string &filepath) +{ + std::string buf; + FILE *f = fopen(filepath.c_str(), "rb"); + if (!f) + return ""; + + fseek(f, 0, SEEK_END); + + long filesize = ftell(f); + if (filesize == -1) { + fclose(f); + return ""; + } + rewind(f); + + buf.resize(filesize); + + UASSERTEQ(size_t, fread(&buf[0], 1, filesize, f), 1); + + fclose(f); + return buf; +} + + +void TestMapSettingsManager::makeUserConfig(Settings *conf) +{ + conf->set("mg_name", "v7"); + conf->set("seed", "5678"); + conf->set("water_level", "20"); + conf->set("mgv5_np_factor", "0, 12, (500, 250, 500), 920382, 5, 0.45, 3.0"); + conf->set("mgv5_np_height", "0, 15, (500, 250, 500), 841746, 5, 0.5, 3.0"); + conf->set("mgv5_np_filler_depth", "20, 1, (150, 150, 150), 261, 4, 0.7, 1.0"); + conf->set("mgv5_np_ground", "-43, 40, (80, 80, 80), 983240, 4, 0.55, 2.0"); +} + + +std::string TestMapSettingsManager::makeMetaFile(bool make_corrupt) +{ + std::string metafile = getTestTempFile(); + + const char *metafile_contents = + "mg_name = v5\n" + "seed = 1234\n" + "mg_flags = light\n" + "mgv5_np_filler_depth = 20, 1, (150, 150, 150), 261, 4, 0.7, 1.0\n" + "mgv5_np_height = 20, 10, (250, 250, 250), 84174, 4, 0.5, 1.0\n"; + + FILE *f = fopen(metafile.c_str(), "wb"); + UASSERT(f != NULL); + + fputs(metafile_contents, f); + if (!make_corrupt) + fputs("[end_of_params]\n", f); + + fclose(f); + + return metafile; +} + + +void TestMapSettingsManager::testMapSettingsManager() +{ + Settings user_settings; + makeUserConfig(&user_settings); + + std::string test_mapmeta_path = makeMetaFile(false); + + MapSettingsManager mgr(&user_settings, test_mapmeta_path); + std::string value; + + UASSERT(mgr.getMapSetting("mg_name", &value)); + UASSERT(value == "v7"); + + // Pretend we're initializing the ServerMap + UASSERT(mgr.loadMapMeta()); + + // Pretend some scripts are requesting mapgen params + UASSERT(mgr.getMapSetting("mg_name", &value)); + UASSERT(value == "v5"); + UASSERT(mgr.getMapSetting("seed", &value)); + UASSERT(value == "1234"); + UASSERT(mgr.getMapSetting("water_level", &value)); + UASSERT(value == "20"); + + // Pretend we have some mapgen settings configured from the scripting + UASSERT(mgr.setMapSetting("water_level", "15")); + UASSERT(mgr.setMapSetting("seed", "02468")); + UASSERT(mgr.setMapSetting("mg_flags", "nolight", true)); + + NoiseParams script_np_filler_depth(0, 100, v3f(200, 100, 200), 261, 4, 0.7, 2.0); + NoiseParams script_np_factor(0, 100, v3f(50, 50, 50), 920381, 3, 0.45, 2.0); + NoiseParams script_np_height(0, 100, v3f(450, 450, 450), 84174, 4, 0.5, 2.0); + NoiseParams meta_np_height(20, 10, v3f(250, 250, 250), 84174, 4, 0.5, 1.0); + NoiseParams user_np_ground(-43, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED); + + mgr.setMapSettingNoiseParams("mgv5_np_filler_depth", &script_np_filler_depth, true); + mgr.setMapSettingNoiseParams("mgv5_np_height", &script_np_height); + mgr.setMapSettingNoiseParams("mgv5_np_factor", &script_np_factor); + + // Now make our Params and see if the values are correctly sourced + MapgenParams *params = mgr.makeMapgenParams(); + UASSERT(params->mgtype == MAPGEN_V5); + UASSERT(params->chunksize == 5); + UASSERT(params->water_level == 15); + UASSERT(params->seed == 1234); + UASSERT((params->flags & MG_LIGHT) == 0); + + MapgenV5Params *v5params = (MapgenV5Params *)params; + + check_noise_params(&v5params->np_filler_depth, &script_np_filler_depth); + check_noise_params(&v5params->np_factor, &script_np_factor); + check_noise_params(&v5params->np_height, &meta_np_height); + check_noise_params(&v5params->np_ground, &user_np_ground); + + UASSERT(mgr.setMapSetting("foobar", "25") == false); + + // Pretend the ServerMap is shutting down + UASSERT(mgr.saveMapMeta()); + + // Make sure our interface expectations are met + UASSERT(mgr.mapgen_params == params); + UASSERT(mgr.makeMapgenParams() == params); + +#if 0 + // TODO(paramat or hmmmm): change this to compare the result against a static file + + // Load the resulting map_meta.txt and make sure it contains what we expect + unsigned char expected_contents_hash[20] = { + 0x48, 0x3f, 0x88, 0x5a, 0xc0, 0x7a, 0x14, 0x48, 0xa4, 0x71, + 0x78, 0x56, 0x95, 0x2d, 0xdc, 0x6a, 0xf7, 0x61, 0x36, 0x5f + }; + + SHA1 ctx; + std::string metafile_contents = read_file_to_string(test_mapmeta_path); + ctx.addBytes(&metafile_contents[0], metafile_contents.size()); + unsigned char *sha1_result = ctx.getDigest(); + int resultdiff = memcmp(sha1_result, expected_contents_hash, 20); + free(sha1_result); + + UASSERT(!resultdiff); +#endif +} + + +void TestMapSettingsManager::testMapMetaSaveLoad() +{ + Settings conf; + std::string path = getTestTempDirectory() + + DIR_DELIM + "foobar" + DIR_DELIM + "map_meta.txt"; + + // Create a set of mapgen params and save them to map meta + conf.set("seed", "12345"); + conf.set("water_level", "5"); + MapSettingsManager mgr1(&conf, path); + MapgenParams *params1 = mgr1.makeMapgenParams(); + UASSERT(params1); + UASSERT(mgr1.saveMapMeta()); + + // Now try loading the map meta to mapgen params + conf.set("seed", "67890"); + conf.set("water_level", "32"); + MapSettingsManager mgr2(&conf, path); + UASSERT(mgr2.loadMapMeta()); + MapgenParams *params2 = mgr2.makeMapgenParams(); + UASSERT(params2); + + // Check that both results are correct + UASSERTEQ(u64, params1->seed, 12345); + UASSERTEQ(s16, params1->water_level, 5); + UASSERTEQ(u64, params2->seed, 12345); + UASSERTEQ(s16, params2->water_level, 5); +} + + +void TestMapSettingsManager::testMapMetaFailures() +{ + std::string test_mapmeta_path; + Settings conf; + + // Check to see if it'll fail on a non-existent map meta file + test_mapmeta_path = "woobawooba/fgdfg/map_meta.txt"; + UASSERT(!fs::PathExists(test_mapmeta_path)); + + MapSettingsManager mgr1(&conf, test_mapmeta_path); + UASSERT(!mgr1.loadMapMeta()); + + // Check to see if it'll fail on a corrupt map meta file + test_mapmeta_path = makeMetaFile(true); + UASSERT(fs::PathExists(test_mapmeta_path)); + + MapSettingsManager mgr2(&conf, test_mapmeta_path); + UASSERT(!mgr2.loadMapMeta()); +} diff --git a/src/unittest/test_player.cpp b/src/unittest/test_player.cpp new file mode 100644 index 000000000..85fbc8b2d --- /dev/null +++ b/src/unittest/test_player.cpp @@ -0,0 +1,88 @@ +/* +Minetest +Copyright (C) 2010-2016 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 "test.h" + +#include "exceptions.h" +#include "remoteplayer.h" +#include "content_sao.h" +#include "server.h" + +class TestPlayer : public TestBase { +public: + TestPlayer() { TestManager::registerTestModule(this); } + const char *getName() { return "TestPlayer"; } + + void runTests(IGameDef *gamedef); + + void testSave(IGameDef *gamedef); + void testLoad(IGameDef *gamedef); +}; + +static TestPlayer g_test_instance; + +void TestPlayer::runTests(IGameDef *gamedef) +{ + TEST(testSave, gamedef); + TEST(testLoad, gamedef); +} + +void TestPlayer::testSave(IGameDef *gamedef) +{ + RemotePlayer rplayer("testplayer_save", gamedef->idef()); + PlayerSAO sao(NULL, 1, false); + sao.initialize(&rplayer, std::set<std::string>()); + rplayer.setPlayerSAO(&sao); + sao.setBreath(10); + sao.setHPRaw(8); + sao.setYaw(0.1f); + sao.setPitch(0.6f); + sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f)); + rplayer.save(".", gamedef); + UASSERT(fs::PathExists("testplayer_save")); +} + +void TestPlayer::testLoad(IGameDef *gamedef) +{ + RemotePlayer rplayer("testplayer_load", gamedef->idef()); + PlayerSAO sao(NULL, 1, false); + sao.initialize(&rplayer, std::set<std::string>()); + rplayer.setPlayerSAO(&sao); + sao.setBreath(10); + sao.setHPRaw(8); + sao.setYaw(0.1f); + sao.setPitch(0.6f); + sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f)); + rplayer.save(".", gamedef); + UASSERT(fs::PathExists("testplayer_load")); + + RemotePlayer rplayer_load("testplayer_load", gamedef->idef()); + PlayerSAO sao_load(NULL, 2, false); + std::ifstream is("testplayer_load", std::ios_base::binary); + UASSERT(is.good()); + rplayer_load.deSerialize(is, "testplayer_load", &sao_load); + is.close(); + + UASSERT(strcmp(rplayer_load.getName(), "testplayer_load") == 0); + UASSERT(sao_load.getBreath() == 10); + UASSERT(sao_load.getHP() == 8); + UASSERT(sao_load.getYaw() == 0.1f); + UASSERT(sao_load.getPitch() == 0.6f); + UASSERT(sao_load.getBasePosition() == v3f(450.2f, -15.7f, 68.1f)); +} diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp index a82d734f0..733c7e92a 100644 --- a/src/unittest/test_settings.cpp +++ b/src/unittest/test_settings.cpp @@ -32,7 +32,7 @@ public: void testAllSettings(); static const char *config_text_before; - static const char *config_text_after; + static const std::string config_text_after; }; static TestSettings g_test_instance; @@ -69,7 +69,7 @@ const char *TestSettings::config_text_before = "np_terrain = 5, 40, (250, 250, 250), 12341, 5, 0.7, 2.4\n" "zoop = true"; -const char *TestSettings::config_text_after = +const std::string TestSettings::config_text_after = "leet = 1337\n" "leetleet = 13371337\n" "leetleet_neg = -13371337\n" @@ -197,7 +197,10 @@ void TestSettings::testAllSettings() 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()); +#if __cplusplus < 201103L + // This test only works in older C++ versions than C++11 because we use unordered_map UASSERT(os.str() == config_text_after); +#endif } catch (SettingNotFoundException &e) { UASSERT(!"Setting not found!"); } diff --git a/src/unittest/test_threading.cpp b/src/unittest/test_threading.cpp index f0df85b2d..cdbf9674e 100644 --- a/src/unittest/test_threading.cpp +++ b/src/unittest/test_threading.cpp @@ -39,7 +39,9 @@ static TestThreading g_test_instance; void TestThreading::runTests(IGameDef *gamedef) { +#if !(defined(__MACH__) && defined(__APPLE__)) TEST(testStartStopWait); +#endif TEST(testThreadKill); TEST(testAtomicSemaphoreThread); } @@ -161,6 +163,7 @@ private: void TestThreading::testAtomicSemaphoreThread() { Atomic<u32> val; + val = 0; Semaphore trigger; static const u8 num_threads = 4; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 0e7cbad07..f571ab22c 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -3,6 +3,7 @@ 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}/enriched_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp diff --git a/src/util/areastore.cpp b/src/util/areastore.cpp index 58f08a8c2..cef67da2c 100644 --- a/src/util/areastore.cpp +++ b/src/util/areastore.cpp @@ -95,6 +95,7 @@ void AreaStore::deserialize(std::istream &is) is.read(data, data_len); a.data = std::string(data, data_len); insertArea(&a); + delete [] data; } } diff --git a/src/util/auth.h b/src/util/auth.h index 1fd6ab453..7cdc7d74d 100644 --- a/src/util/auth.h +++ b/src/util/auth.h @@ -45,6 +45,6 @@ std::string encode_srp_verifier(const std::string &verifier, /// Reads the DB-formatted SRP verifier and gets the verifier /// and salt components. bool decode_srp_verifier_and_salt(const std::string &encoded, - std::string *salt, std::string *bytes_v); + std::string *verifier, std::string *salt); #endif diff --git a/src/util/cpp11_container.h b/src/util/cpp11_container.h new file mode 100644 index 000000000..88317c9c4 --- /dev/null +++ b/src/util/cpp11_container.h @@ -0,0 +1,43 @@ +/* +Minetest +Copyright (C) 2016 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 MT_CPP11CONTAINER_HEADER +#define MT_CPP11CONTAINER_HEADER + +#if __cplusplus >= 201103L +#define USE_UNORDERED_CONTAINERS +#endif + +#if _MSC_VER >= 1600 +#define USE_UNORDERED_CONTAINERS +#endif + +#ifdef USE_UNORDERED_CONTAINERS + #include <unordered_map> + #include <unordered_set> + #define UNORDERED_MAP std::unordered_map + #define UNORDERED_SET std::unordered_set +#else + #include <map> + #include <set> + #define UNORDERED_MAP std::map + #define UNORDERED_SET std::set +#endif + +#endif diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp new file mode 100644 index 000000000..a7fc3a828 --- /dev/null +++ b/src/util/enriched_string.cpp @@ -0,0 +1,166 @@ +/* +Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is> +Copyright (C) 2016 Nore, Nathanaƫl Courant <nore@mesecons.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 "enriched_string.h" +#include "util/string.h" +#include "log.h" +using namespace irr::video; + +EnrichedString::EnrichedString() +{ + clear(); +} + +EnrichedString::EnrichedString(const std::wstring &string, + const std::vector<SColor> &colors): + m_string(string), + m_colors(colors), + m_has_background(false) +{} + +EnrichedString::EnrichedString(const std::wstring &s, const SColor &color) +{ + clear(); + addAtEnd(s, color); +} + +EnrichedString::EnrichedString(const wchar_t *str, const SColor &color) +{ + clear(); + addAtEnd(std::wstring(str), color); +} + +void EnrichedString::operator=(const wchar_t *str) +{ + clear(); + addAtEnd(std::wstring(str), SColor(255, 255, 255, 255)); +} + +void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color) +{ + SColor color(initial_color); + size_t i = 0; + while (i < s.length()) { + if (s[i] != L'\x1b') { + m_string += s[i]; + m_colors.push_back(color); + ++i; + continue; + } + ++i; + size_t start_index = i; + size_t length; + if (i == s.length()) { + break; + } + if (s[i] == L'(') { + ++i; + ++start_index; + while (i < s.length() && s[i] != L')') { + if (s[i] == L'\\') { + ++i; + } + ++i; + } + length = i - start_index; + ++i; + } else { + ++i; + length = 1; + } + std::wstring escape_sequence(s, start_index, length); + std::vector<std::wstring> parts = split(escape_sequence, L'@'); + if (parts[0] == L"c") { + if (parts.size() < 2) { + continue; + } + parseColorString(wide_to_utf8(parts[1]), color, true); + } else if (parts[0] == L"b") { + if (parts.size() < 2) { + continue; + } + parseColorString(wide_to_utf8(parts[1]), m_background, true); + m_has_background = true; + } + continue; + } +} + +void EnrichedString::addChar(const EnrichedString &source, size_t i) +{ + m_string += source.m_string[i]; + m_colors.push_back(source.m_colors[i]); +} + +void EnrichedString::addCharNoColor(wchar_t c) +{ + m_string += c; + if (m_colors.empty()) { + m_colors.push_back(SColor(255, 255, 255, 255)); + } else { + m_colors.push_back(m_colors[m_colors.size() - 1]); + } +} + +EnrichedString EnrichedString::operator+(const EnrichedString &other) const +{ + std::vector<SColor> result; + result.insert(result.end(), m_colors.begin(), m_colors.end()); + result.insert(result.end(), other.m_colors.begin(), other.m_colors.end()); + return EnrichedString(m_string + other.m_string, result); +} + +void EnrichedString::operator+=(const EnrichedString &other) +{ + m_string += other.m_string; + m_colors.insert(m_colors.end(), other.m_colors.begin(), other.m_colors.end()); +} + +EnrichedString EnrichedString::substr(size_t pos, size_t len) const +{ + if (pos == m_string.length()) { + return EnrichedString(); + } + if (len == std::string::npos || pos + len > m_string.length()) { + return EnrichedString( + m_string.substr(pos, std::string::npos), + std::vector<SColor>(m_colors.begin() + pos, m_colors.end()) + ); + } else { + return EnrichedString( + m_string.substr(pos, len), + std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len) + ); + } +} + +const wchar_t *EnrichedString::c_str() const +{ + return m_string.c_str(); +} + +const std::vector<SColor> &EnrichedString::getColors() const +{ + return m_colors; +} + +const std::wstring &EnrichedString::getString() const +{ + return m_string; +} diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h new file mode 100644 index 000000000..1aca8948a --- /dev/null +++ b/src/util/enriched_string.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is> +Copyright (C) 2016 Nore, Nathanaƫl Courant <nore@mesecons.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 ENRICHEDSTRING_HEADER +#define ENRICHEDSTRING_HEADER + +#include <string> +#include <vector> +#include <SColor.h> + +class EnrichedString { +public: + EnrichedString(); + EnrichedString(const std::wstring &s, + const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255)); + EnrichedString(const wchar_t *str, + const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255)); + EnrichedString(const std::wstring &string, + const std::vector<irr::video::SColor> &colors); + void operator=(const wchar_t *str); + void addAtEnd(const std::wstring &s, const irr::video::SColor &color); + + // Adds the character source[i] at the end. + // An EnrichedString should always be able to be copied + // to the end of an existing EnrichedString that way. + void addChar(const EnrichedString &source, size_t i); + + // Adds a single character at the end, without specifying its + // color. The color used will be the one from the last character. + void addCharNoColor(wchar_t c); + + EnrichedString substr(size_t pos = 0, size_t len = std::string::npos) const; + EnrichedString operator+(const EnrichedString &other) const; + void operator+=(const EnrichedString &other); + const wchar_t *c_str() const; + const std::vector<irr::video::SColor> &getColors() const; + const std::wstring &getString() const; + inline bool operator==(const EnrichedString &other) const + { + return (m_string == other.m_string && m_colors == other.m_colors); + } + inline bool operator!=(const EnrichedString &other) const + { + return !(*this == other); + } + inline void clear() + { + m_string.clear(); + m_colors.clear(); + m_has_background = false; + } + inline bool empty() const + { + return m_string.empty(); + } + inline size_t size() const + { + return m_string.size(); + } + inline bool hasBackground() const + { + return m_has_background; + } + inline irr::video::SColor getBackground() const + { + return m_background; + } +private: + std::wstring m_string; + std::vector<irr::video::SColor> m_colors; + bool m_has_background; + irr::video::SColor m_background; +}; + +#endif diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 42ebd9022..a9e7ae584 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string.h> #include <iostream> -std::map<u16, std::vector<v3s16> > FacePositionCache::m_cache; +UNORDERED_MAP<u16, std::vector<v3s16> > FacePositionCache::m_cache; Mutex FacePositionCache::m_cache_mutex; // Calculate the borders of a "d-radius" cube // TODO: Make it work without mutex and data races, probably thread-local @@ -188,14 +188,19 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) } /* - blockpos: position of block in block coordinates + blockpos_b: position of block in block coordinates camera_pos: position of camera in nodes camera_dir: an unit vector pointing to camera direction range: viewing range + distance_ptr: return location for distance from the camera */ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, f32 camera_fov, f32 range, f32 *distance_ptr) { + // Maximum radius of a block. The magic number is + // sqrt(3.0) / 2.0 in literal form. + const f32 block_max_radius = 0.866025403784 * MAP_BLOCKSIZE * BS; + v3s16 blockpos_nodes = blockpos_b * MAP_BLOCKSIZE; // Block center position @@ -209,7 +214,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, v3f blockpos_relative = blockpos - camera_pos; // Total distance - f32 d = blockpos_relative.getLength(); + f32 d = MYMAX(0, blockpos_relative.getLength() - block_max_radius); if(distance_ptr) *distance_ptr = d; @@ -218,13 +223,9 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, if(d > range) return false; - // 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) + if(d == 0) return true; // Adjust camera position, for purposes of computing the angle, diff --git a/src/util/numeric.h b/src/util/numeric.h index 615327864..4cdc254c3 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -26,8 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../irr_v3d.h" #include "../irr_aabb3d.h" #include "../threading/mutex.h" +#include "cpp11_container.h" #include <list> -#include <map> #include <vector> @@ -41,26 +41,10 @@ public: static std::vector<v3s16> getFacePositions(u16 d); private: static void generateFacePosition(u16 d); - static std::map<u16, std::vector<v3s16> > m_cache; + static UNORDERED_MAP<u16, std::vector<v3s16> > m_cache; static Mutex m_cache_mutex; }; -class IndentationRaiser -{ -public: - IndentationRaiser(u16 *indentation) - { - m_indentation = indentation; - (*m_indentation)++; - } - ~IndentationRaiser() - { - (*m_indentation)--; - } -private: - u16 *m_indentation; -}; - inline s16 getContainerPos(s16 p, s16 d) { return (p>=0 ? p : p-d+1) / d; @@ -149,23 +133,6 @@ inline bool isInArea(v3s16 p, v3s16 d) #define rangelim(d, min, max) ((d) < (min) ? (min) : ((d)>(max)?(max):(d))) #define myfloor(x) ((x) > 0.0 ? (int)(x) : (int)(x) - 1) -inline v3s16 arealim(v3s16 p, s16 d) -{ - if(p.X < 0) - p.X = 0; - if(p.Y < 0) - p.Y = 0; - if(p.Z < 0) - p.Z = 0; - if(p.X > d-1) - p.X = d-1; - if(p.Y > d-1) - p.Y = d-1; - if(p.Z > d-1) - p.Z = d-1; - return p; -} - // The naive swap performs better than the xor version #define SWAP(t, x, y) do { \ t temp = x; \ diff --git a/src/util/srp.cpp b/src/util/srp.cpp index 0d3c938a3..77c1816e8 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -542,7 +542,7 @@ static SRP_Result fill_buff() if (!fp) return SRP_ERR; - if (fread(g_rand_buff, sizeof(g_rand_buff), 1, fp) != 1) return SRP_ERR; + if (fread(g_rand_buff, sizeof(g_rand_buff), 1, fp) != 1) { fclose(fp); return SRP_ERR; } if (fclose(fp)) return SRP_ERR; #endif return SRP_OK; diff --git a/src/util/string.cpp b/src/util/string.cpp index 2c4143c76..94064ef93 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -314,7 +314,7 @@ std::string wide_to_narrow(const std::wstring &wcs) #endif -std::string urlencode(std::string str) +std::string urlencode(const std::string &str) { // Encodes non-unreserved URI characters by a percent sign // followed by two hex digits. See RFC 3986, section 2.3. @@ -322,17 +322,18 @@ std::string urlencode(std::string str) std::ostringstream oss(std::ios::binary); for (u32 i = 0; i < str.size(); i++) { unsigned char c = str[i]; - if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') + if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { oss << c; - else + } else { oss << "%" << url_hex_chars[(c & 0xf0) >> 4] << url_hex_chars[c & 0x0f]; + } } return oss.str(); } -std::string urldecode(std::string str) +std::string urldecode(const std::string &str) { // Inverse of urlencode std::ostringstream oss(std::ios::binary); @@ -343,18 +344,20 @@ std::string urldecode(std::string str) hex_digit_decode(str[i+2], lowvalue)) { oss << (char) ((highvalue << 4) | lowvalue); i += 2; - } - else + } else { oss << str[i]; + } } return oss.str(); } u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask) { - u32 result = 0, mask = 0; + u32 result = 0; + u32 mask = 0; char *s = &str[0]; - char *flagstr, *strpos = NULL; + char *flagstr; + char *strpos = NULL; while ((flagstr = strtok_r(s, ",", &strpos))) { s = NULL; diff --git a/src/util/string.h b/src/util/string.h index 40ef3e4d3..572c37150 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -21,12 +21,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #define UTIL_STRING_HEADER #include "irrlichttypes_bloated.h" +#include "cpp11_container.h" #include <stdlib.h> #include <string> #include <cstring> #include <vector> #include <map> #include <sstream> +#include <iomanip> #include <cctype> #define STRINGIFY(x) #x @@ -53,7 +55,7 @@ with this program; if not, write to the Free Software Foundation, Inc., (((unsigned char)(x) < 0xe0) ? 2 : \ (((unsigned char)(x) < 0xf0) ? 3 : 4)) -typedef std::map<std::string, std::string> StringMap; +typedef UNORDERED_MAP<std::string, std::string> StringMap; struct FlagDesc { const char *name; @@ -77,8 +79,8 @@ 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 urlencode(std::string str); -std::string urldecode(std::string str); +std::string urlencode(const std::string &str); +std::string urldecode(const std::string &str); u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask); std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask); size_t mystrlcpy(char *dst, const char *src, size_t size); @@ -350,23 +352,57 @@ inline T from_string(const std::string &str) /// Returns a 64-bit signed value represented by the string \p str (decimal). inline s64 stoi64(const std::string &str) { return from_string<s64>(str); } -// TODO: Replace with C++11 std::to_string. +#if __cplusplus < 201103L +namespace std { /// Returns a string representing the value \p val. template <typename T> -inline std::string to_string(T val) +inline string to_string(T val) { - std::ostringstream oss; + ostringstream oss; oss << val; return oss.str(); } +#define DEFINE_STD_TOSTRING_FLOATINGPOINT(T) \ + template <> \ + inline string to_string<T>(T val) \ + { \ + ostringstream oss; \ + oss << std::fixed \ + << std::setprecision(6) \ + << val; \ + return oss.str(); \ + } +DEFINE_STD_TOSTRING_FLOATINGPOINT(float) +DEFINE_STD_TOSTRING_FLOATINGPOINT(double) +DEFINE_STD_TOSTRING_FLOATINGPOINT(long double) + +#undef DEFINE_STD_TOSTRING_FLOATINGPOINT + +/// Returns a wide string representing the value \p val +template <typename T> +inline wstring to_wstring(T val) +{ + return utf8_to_wide(to_string(val)); +} +} +#endif /// Returns a string representing the decimal value of the 32-bit value \p i. -inline std::string itos(s32 i) { return to_string(i); } +inline std::string itos(s32 i) { return std::to_string(i); } /// Returns a string representing the decimal value of the 64-bit value \p i. -inline std::string i64tos(s64 i) { return to_string(i); } +inline std::string i64tos(s64 i) { return std::to_string(i); } + +// std::to_string uses the '%.6f' conversion, which is inconsistent with +// std::ostream::operator<<() and impractical too. ftos() uses the +// more generic and std::ostream::operator<<()-compatible '%G' format. /// Returns a string representing the decimal value of the float value \p f. -inline std::string ftos(float f) { return to_string(f); } +inline std::string ftos(float f) +{ + std::ostringstream oss; + oss << f; + return oss.str(); +} /** @@ -519,6 +555,38 @@ std::basic_string<T> unescape_enriched(const std::basic_string<T> &s) return output; } +template <typename T> +std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim) +{ + std::vector<std::basic_string<T> > tokens; + + std::basic_string<T> current; + bool last_was_escape = false; + for (size_t i = 0; i < s.length(); i++) { + T si = s[i]; + if (last_was_escape) { + current += '\\'; + current += si; + last_was_escape = false; + } else { + if (si == delim) { + tokens.push_back(current); + current = std::basic_string<T>(); + last_was_escape = false; + } else if (si == '\\') { + last_was_escape = true; + } else { + current += si; + last_was_escape = false; + } + } + } + //push last element + tokens.push_back(current); + + return tokens; +} + /** * Checks that all characters in \p to_check are a decimal digits. * diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index f067a221a..93cc33acc 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -19,6 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "voxelalgorithms.h" #include "nodedef.h" +#include "mapblock.h" +#include "map.h" namespace voxalgo { @@ -153,5 +155,597 @@ SunlightPropagateResult propagateSunlight(VoxelManipulator &v, VoxelArea a, return SunlightPropagateResult(bottom_sunlight_valid); } +/*! + * A direction. + * 0=X+ + * 1=Y+ + * 2=Z+ + * 3=Z- + * 4=Y- + * 5=X- + * 6=no direction + * Two directions are opposite only if their sum is 5. + */ +typedef u8 direction; +/*! + * Relative node position. + * This represents a node's position in its map block. + * All coordinates must be between 0 and 15. + */ +typedef v3s16 relative_v3; +/*! + * Position of a map block (block coordinates). + * One block_pos unit is as long as 16 node position units. + */ +typedef v3s16 mapblock_v3; + +//! Contains information about a node whose light is about to change. +struct ChangingLight { + //! Relative position of the node in its map block. + relative_v3 rel_position; + //! Position of the node's block. + mapblock_v3 block_position; + //! Pointer to the node's block. + MapBlock *block; + /*! + * Direction from the node that caused this node's changing + * to this node. + */ + direction source_direction; + + ChangingLight() : + rel_position(), + block_position(), + block(NULL), + source_direction(6) + {} + + ChangingLight(relative_v3 rel_pos, mapblock_v3 block_pos, + MapBlock *b, direction source_dir) : + rel_position(rel_pos), + block_position(block_pos), + block(b), + source_direction(source_dir) + {} +}; + +/*! + * A fast, priority queue-like container to contain ChangingLights. + * The ChangingLights are ordered by the given light levels. + * The brightest ChangingLight is returned first. + */ +struct LightQueue { + //! For each light level there is a vector. + std::vector<ChangingLight> lights[LIGHT_SUN + 1]; + //! Light of the brightest ChangingLight in the queue. + u8 max_light; + + /*! + * Creates a LightQueue. + * \param reserve for each light level that many slots are reserved. + */ + LightQueue(size_t reserve) + { + max_light = LIGHT_SUN; + for (u8 i = 0; i <= LIGHT_SUN; i++) { + lights[i].reserve(reserve); + } + } + + /*! + * Returns the next brightest ChangingLight and + * removes it from the queue. + * If there were no elements in the queue, the given parameters + * remain unmodified. + * \param light light level of the popped ChangingLight + * \param data the ChangingLight that was popped + * \returns true if there was a ChangingLight in the queue. + */ + bool next(u8 &light, ChangingLight &data) + { + while (lights[max_light].empty()) { + if (max_light == 0) { + return false; + } + max_light--; + } + light = max_light; + data = lights[max_light].back(); + lights[max_light].pop_back(); + return true; + } + + /*! + * Adds an element to the queue. + * The parameters are the same as in ChangingLight's constructor. + * \param light light level of the ChangingLight + */ + inline void push(u8 light, const relative_v3 &rel_pos, + const mapblock_v3 &block_pos, MapBlock *block, + direction source_dir) + { + assert(light <= LIGHT_SUN); + lights[light].push_back( + ChangingLight(rel_pos, block_pos, block, source_dir)); + } +}; + +/*! + * This type of light queue is for unlighting. + * A node can be pushed in it only if its raw light is zero. + * This prevents pushing nodes twice into this queue. + * The light of the pushed ChangingLight must be the + * light of the node before unlighting it. + */ +typedef LightQueue UnlightQueue; +/*! + * This type of light queue is for spreading lights. + * While spreading lights, all the nodes in it must + * have the same light as the light level the ChangingLights + * were pushed into this queue with. This prevents unnecessary + * re-pushing of the nodes into the queue. + * If a node doesn't let light trough but emits light, it can be added + * too. + */ +typedef LightQueue ReLightQueue; + +/*! + * neighbor_dirs[i] points towards + * the direction i. + * See the definition of the type "direction" + */ +const static v3s16 neighbor_dirs[6] = { + v3s16(1, 0, 0), // right + v3s16(0, 1, 0), // top + v3s16(0, 0, 1), // back + v3s16(0, 0, -1), // front + v3s16(0, -1, 0), // bottom + v3s16(-1, 0, 0), // left +}; + +/*! + * Transforms the given map block offset by one node towards + * the specified direction. + * \param dir the direction of the transformation + * \param rel_pos the node's relative position in its map block + * \param block_pos position of the node's block + */ +bool step_rel_block_pos(direction dir, relative_v3 &rel_pos, + mapblock_v3 &block_pos) +{ + switch (dir) { + case 0: + if (rel_pos.X < MAP_BLOCKSIZE - 1) { + rel_pos.X++; + } else { + rel_pos.X = 0; + block_pos.X++; + return true; + } + break; + case 1: + if (rel_pos.Y < MAP_BLOCKSIZE - 1) { + rel_pos.Y++; + } else { + rel_pos.Y = 0; + block_pos.Y++; + return true; + } + break; + case 2: + if (rel_pos.Z < MAP_BLOCKSIZE - 1) { + rel_pos.Z++; + } else { + rel_pos.Z = 0; + block_pos.Z++; + return true; + } + break; + case 3: + if (rel_pos.Z > 0) { + rel_pos.Z--; + } else { + rel_pos.Z = MAP_BLOCKSIZE - 1; + block_pos.Z--; + return true; + } + break; + case 4: + if (rel_pos.Y > 0) { + rel_pos.Y--; + } else { + rel_pos.Y = MAP_BLOCKSIZE - 1; + block_pos.Y--; + return true; + } + break; + case 5: + if (rel_pos.X > 0) { + rel_pos.X--; + } else { + rel_pos.X = MAP_BLOCKSIZE - 1; + block_pos.X--; + return true; + } + break; + } + return false; +} + +/* + * Removes all light that is potentially emitted by the specified + * light sources. These nodes will have zero light. + * Returns all nodes whose light became zero but should be re-lighted. + * + * \param bank the light bank in which the procedure operates + * \param from_nodes nodes whose light is removed + * \param light_sources nodes that should be re-lighted + * \param modified_blocks output, all modified map blocks are added to this + */ +void unspread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, + UnlightQueue &from_nodes, ReLightQueue &light_sources, + std::map<v3s16, MapBlock*> &modified_blocks) +{ + // Stores data popped from from_nodes + u8 current_light; + ChangingLight current; + // Data of the current neighbor + mapblock_v3 neighbor_block_pos; + relative_v3 neighbor_rel_pos; + // A dummy boolean + bool is_valid_position; + // Direction of the brightest neighbor of the node + direction source_dir; + while (from_nodes.next(current_light, current)) { + // For all nodes that need unlighting + + // There is no brightest neighbor + source_dir = 6; + // The current node + const MapNode &node = current.block->getNodeNoCheck( + current.rel_position, &is_valid_position); + const ContentFeatures &f = nodemgr->get(node); + // If the node emits light, it behaves like it had a + // brighter neighbor. + u8 brightest_neighbor_light = f.light_source + 1; + for (direction i = 0; i < 6; i++) { + //For each neighbor + + // The node that changed this node has already zero light + // and it can't give light to this node + if (current.source_direction + i == 5) { + continue; + } + // Get the neighbor's position and block + neighbor_rel_pos = current.rel_position; + neighbor_block_pos = current.block_position; + MapBlock *neighbor_block; + if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { + neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); + if (neighbor_block == NULL) { + continue; + } + } else { + neighbor_block = current.block; + } + // Get the neighbor itself + MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos, + &is_valid_position); + const ContentFeatures &neighbor_f = nodemgr->get( + neighbor.getContent()); + u8 neighbor_light = neighbor.getLightRaw(bank, neighbor_f); + // If the neighbor has at least as much light as this node, then + // it won't lose its light, since it should have been added to + // from_nodes earlier, so its light would be zero. + if (neighbor_f.light_propagates && neighbor_light < current_light) { + // Unlight, but only if the node has light. + if (neighbor_light > 0) { + neighbor.setLight(bank, 0, neighbor_f); + neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor); + from_nodes.push(neighbor_light, neighbor_rel_pos, + neighbor_block_pos, neighbor_block, i); + // The current node was modified earlier, so its block + // is in modified_blocks. + if (current.block != neighbor_block) { + modified_blocks[neighbor_block_pos] = neighbor_block; + } + } + } else { + // The neighbor can light up this node. + if (neighbor_light < neighbor_f.light_source) { + neighbor_light = neighbor_f.light_source; + } + if (brightest_neighbor_light < neighbor_light) { + brightest_neighbor_light = neighbor_light; + source_dir = i; + } + } + } + // If the brightest neighbor is able to light up this node, + // then add this node to the output nodes. + if (brightest_neighbor_light > 1 && f.light_propagates) { + brightest_neighbor_light--; + light_sources.push(brightest_neighbor_light, current.rel_position, + current.block_position, current.block, + (source_dir == 6) ? 6 : 5 - source_dir + /* with opposite direction*/); + } + } +} + +/* + * Spreads light from the specified starting nodes. + * + * Before calling this procedure, make sure that all ChangingLights + * in light_sources have as much light on the map as they have in + * light_sources (if the queue contains a node multiple times, the brightest + * occurrence counts). + * + * \param bank the light bank in which the procedure operates + * \param light_sources starting nodes + * \param modified_blocks output, all modified map blocks are added to this + */ +void spread_light(Map *map, INodeDefManager *nodemgr, LightBank bank, + LightQueue &light_sources, std::map<v3s16, MapBlock*> &modified_blocks) +{ + // The light the current node can provide to its neighbors. + u8 spreading_light; + // The ChangingLight for the current node. + ChangingLight current; + // Position of the current neighbor. + mapblock_v3 neighbor_block_pos; + relative_v3 neighbor_rel_pos; + // A dummy boolean. + bool is_valid_position; + while (light_sources.next(spreading_light, current)) { + spreading_light--; + for (direction i = 0; i < 6; i++) { + // This node can't light up its light source + if (current.source_direction + i == 5) { + continue; + } + // Get the neighbor's position and block + neighbor_rel_pos = current.rel_position; + neighbor_block_pos = current.block_position; + MapBlock *neighbor_block; + if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { + neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); + if (neighbor_block == NULL) { + continue; + } + } else { + neighbor_block = current.block; + } + // Get the neighbor itself + MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos, + &is_valid_position); + const ContentFeatures &f = nodemgr->get(neighbor.getContent()); + if (f.light_propagates) { + // Light up the neighbor, if it has less light than it should. + u8 neighbor_light = neighbor.getLightRaw(bank, f); + if (neighbor_light < spreading_light) { + neighbor.setLight(bank, spreading_light, f); + neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor); + light_sources.push(spreading_light, neighbor_rel_pos, + neighbor_block_pos, neighbor_block, i); + // The current node was modified earlier, so its block + // is in modified_blocks. + if (current.block != neighbor_block) { + modified_blocks[neighbor_block_pos] = neighbor_block; + } + } + } + } + } +} + +/*! + * Returns true if the node gets sunlight from the + * node above it. + * + * \param pos position of the node. + */ +bool is_sunlight_above(Map *map, v3s16 pos, INodeDefManager *ndef) +{ + bool sunlight = true; + mapblock_v3 source_block_pos; + relative_v3 source_rel_pos; + getNodeBlockPosWithOffset(pos + v3s16(0, 1, 0), source_block_pos, + source_rel_pos); + // If the node above has sunlight, this node also can get it. + MapBlock *source_block = map->getBlockNoCreateNoEx(source_block_pos); + if (source_block == NULL) { + // But if there is no node above, then use heuristics + MapBlock *node_block = map->getBlockNoCreateNoEx(getNodeBlockPos(pos)); + if (node_block == NULL) { + sunlight = false; + } else { + sunlight = !node_block->getIsUnderground(); + } + } else { + bool is_valid_position; + MapNode above = source_block->getNodeNoCheck(source_rel_pos, + &is_valid_position); + if (is_valid_position) { + if (above.getContent() == CONTENT_IGNORE) { + // Trust heuristics + if (source_block->getIsUnderground()) { + sunlight = false; + } + } else if (above.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) { + // If the node above doesn't have sunlight, this + // node is in shadow. + sunlight = false; + } + } + } + return sunlight; +} + +static const LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT }; + +void update_lighting_nodes(Map *map, INodeDefManager *ndef, + std::vector<std::pair<v3s16, MapNode> > &oldnodes, + std::map<v3s16, MapBlock*> &modified_blocks) +{ + // For node getter functions + bool is_valid_position; + + // Process each light bank separately + for (s32 i = 0; i < 2; i++) { + LightBank bank = banks[i]; + UnlightQueue disappearing_lights(256); + ReLightQueue light_sources(256); + // For each changed node process sunlight and initialize + for (std::vector<std::pair<v3s16, MapNode> >::iterator it = + oldnodes.begin(); it < oldnodes.end(); ++it) { + // Get position and block of the changed node + v3s16 p = it->first; + relative_v3 rel_pos; + mapblock_v3 block_pos; + getNodeBlockPosWithOffset(p, block_pos, rel_pos); + MapBlock *block = map->getBlockNoCreateNoEx(block_pos); + if (block == NULL || block->isDummy()) { + continue; + } + // Get the new node + MapNode n = block->getNodeNoCheck(rel_pos, &is_valid_position); + if (!is_valid_position) { + break; + } + + // Light of the old node + u8 old_light = it->second.getLight(bank, ndef); + + // Add the block of the added node to modified_blocks + modified_blocks[block_pos] = block; + + // Get new light level of the node + u8 new_light = 0; + if (ndef->get(n).light_propagates) { + if (bank == LIGHTBANK_DAY && ndef->get(n).sunlight_propagates + && is_sunlight_above(map, p, ndef)) { + new_light = LIGHT_SUN; + } else { + new_light = ndef->get(n).light_source; + for (int i = 0; i < 6; i++) { + v3s16 p2 = p + neighbor_dirs[i]; + bool is_valid; + MapNode n2 = map->getNodeNoEx(p2, &is_valid); + if (is_valid) { + u8 spread = n2.getLight(bank, ndef); + // If the neighbor is at least as bright as + // this node then its light is not from + // this node. + // Its light can spread to this node. + if (spread > new_light && spread >= old_light) { + new_light = spread - 1; + } + } + } + } + } else { + // If this is an opaque node, it still can emit light. + new_light = ndef->get(n).light_source; + } + + if (new_light > 0) { + light_sources.push(new_light, rel_pos, block_pos, block, 6); + } + + if (new_light < old_light) { + // The node became opaque or doesn't provide as much + // light as the previous one, so it must be unlighted. + + // Add to unlight queue + n.setLight(bank, 0, ndef); + block->setNodeNoCheck(rel_pos, n); + disappearing_lights.push(old_light, rel_pos, block_pos, block, + 6); + + // Remove sunlight, if there was any + if (bank == LIGHTBANK_DAY && old_light == LIGHT_SUN) { + for (s16 y = p.Y - 1;; y--) { + v3s16 n2pos(p.X, y, p.Z); + + MapNode n2; + + n2 = map->getNodeNoEx(n2pos, &is_valid_position); + if (!is_valid_position) + break; + + // If this node doesn't have sunlight, the nodes below + // it don't have too. + if (n2.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) { + break; + } + // Remove sunlight and add to unlight queue. + n2.setLight(LIGHTBANK_DAY, 0, ndef); + map->setNode(n2pos, n2); + relative_v3 rel_pos2; + mapblock_v3 block_pos2; + getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2); + MapBlock *block2 = map->getBlockNoCreateNoEx( + block_pos2); + disappearing_lights.push(LIGHT_SUN, rel_pos2, + block_pos2, block2, + 4 /* The node above caused the change */); + } + } + } else if (new_light > old_light) { + // It is sure that the node provides more light than the previous + // one, unlighting is not necessary. + // Propagate sunlight + if (bank == LIGHTBANK_DAY && new_light == LIGHT_SUN) { + for (s16 y = p.Y - 1;; y--) { + v3s16 n2pos(p.X, y, p.Z); + + MapNode n2; + + n2 = map->getNodeNoEx(n2pos, &is_valid_position); + if (!is_valid_position) + break; + + // This should not happen, but if the node has sunlight + // then the iteration should stop. + if (n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) { + break; + } + // If the node terminates sunlight, stop. + if (!ndef->get(n2).sunlight_propagates) { + break; + } + relative_v3 rel_pos2; + mapblock_v3 block_pos2; + getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2); + MapBlock *block2 = map->getBlockNoCreateNoEx( + block_pos2); + // Mark node for lighting. + light_sources.push(LIGHT_SUN, rel_pos2, block_pos2, + block2, 4); + } + } + } + + } + // Remove lights + unspread_light(map, ndef, bank, disappearing_lights, light_sources, + modified_blocks); + // Initialize light values for light spreading. + for (u8 i = 0; i <= LIGHT_SUN; i++) { + const std::vector<ChangingLight> &lights = light_sources.lights[i]; + for (std::vector<ChangingLight>::const_iterator it = lights.begin(); + it < lights.end(); it++) { + MapNode n = it->block->getNodeNoCheck(it->rel_position, + &is_valid_position); + n.setLight(bank, i, ndef); + it->block->setNodeNoCheck(it->rel_position, n); + } + } + // Spread lights. + spread_light(map, ndef, bank, light_sources, modified_blocks); + } +} + } // namespace voxalgo diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index 2eba6a176..3632546dd 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -25,6 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <set> #include <map> +class Map; +class MapBlock; + namespace voxalgo { @@ -52,6 +55,24 @@ SunlightPropagateResult propagateSunlight(VoxelManipulator &v, VoxelArea a, std::set<v3s16> & light_sources, INodeDefManager *ndef); +/*! + * Updates the lighting on the map. + * The result will be correct only if + * no nodes were changed except the given ones. + * Before calling this procedure make sure that all new nodes on + * the map have zero light level! + * + * \param oldnodes contains the MapNodes that were replaced by the new + * MapNodes and their positions + * \param modified_blocks output, contains all map blocks that + * the function modified + */ +void update_lighting_nodes( + Map *map, + INodeDefManager *ndef, + std::vector<std::pair<v3s16, MapNode> > &oldnodes, + std::map<v3s16, MapBlock*> &modified_blocks); + } // namespace voxalgo #endif |