diff options
Diffstat (limited to 'src')
267 files changed, 9042 insertions, 4696 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7f207244c..c068be575 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,8 +98,8 @@ if(BUILD_CLIENT AND ENABLE_SOUND) endif() endif() - -option(ENABLE_GLES "Use OpenGL ES instead of OpenGL" FALSE) +# TODO: this should be removed one day, we can enable it unconditionally +option(ENABLE_GLES "Enable extra support code for OpenGL ES" FALSE) mark_as_advanced(ENABLE_GLES) option(ENABLE_TOUCH "Enable Touchscreen support" FALSE) @@ -108,21 +108,6 @@ if(ENABLE_TOUCH) endif() if(BUILD_CLIENT) - # transitive dependency from Irrlicht (see longer explanation below) - if(NOT WIN32) - if(ENABLE_GLES) - find_package(OpenGLES2 REQUIRED) - else() - set(OPENGL_GL_PREFERENCE "LEGACY" CACHE STRING - "See CMake Policy CMP0072 for reference. GLVND is broken on some nvidia setups") - set(OpenGL_GL_PREFERENCE ${OPENGL_GL_PREFERENCE}) - - find_package(OpenGL REQUIRED) - endif() - endif() -endif() - -if(BUILD_CLIENT) find_package(Freetype REQUIRED) endif() @@ -151,6 +136,7 @@ if(ENABLE_POSTGRESQL) if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY) set(PostgreSQL_FOUND TRUE) set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR}) + set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY}) endif() else() find_package(PostgreSQL) @@ -345,6 +331,7 @@ add_subdirectory(mapgen) add_subdirectory(network) add_subdirectory(script) add_subdirectory(unittest) +add_subdirectory(benchmark) add_subdirectory(util) add_subdirectory(irrlicht_changes) add_subdirectory(server) @@ -427,6 +414,9 @@ if(BUILD_UNITTESTS) set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS}) endif() +if(BUILD_BENCHMARKS) + set(common_SRCS ${common_SRCS} ${BENCHMARK_SRCS}) +endif() # This gives us the icon and file version information if(WIN32) @@ -467,6 +457,10 @@ if(BUILD_UNITTESTS) set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS}) endif() +if(BUILD_BENCHMARKS) + set(client_SRCS ${client_SRCS} ${BENCHMARK_CLIENT_SRCS}) +endif() + list(SORT client_SRCS) # Server sources @@ -485,6 +479,9 @@ endif() include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/script +) +include_directories(SYSTEM ${ZLIB_INCLUDE_DIR} ${ZSTD_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} @@ -492,7 +489,6 @@ include_directories( ${GMP_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${LUA_BIT_INCLUDE_DIR} - ${PROJECT_SOURCE_DIR}/script ) if(USE_GETTEXT) @@ -500,7 +496,7 @@ if(USE_GETTEXT) endif() if(BUILD_CLIENT) - include_directories( + include_directories(SYSTEM ${FREETYPE_INCLUDE_DIRS} ${SOUND_INCLUDE_DIRS} ${X11_INCLUDE_DIR} @@ -544,18 +540,6 @@ if(BUILD_CLIENT) ) endif() - if(ENABLE_GLES) - target_link_libraries( - ${PROJECT_NAME} - ${OPENGLES2_LIBRARIES} - ${EGL_LIBRARIES} - ) - else() - target_link_libraries( - ${PROJECT_NAME} - ${OPENGL_LIBRARIES} - ) - endif() if(USE_GETTEXT) target_link_libraries( ${PROJECT_NAME} @@ -592,6 +576,9 @@ if(BUILD_CLIENT) if (USE_SPATIAL) target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY}) endif() + if(BUILD_BENCHMARKS) + target_link_libraries(${PROJECT_NAME} catch2) + endif() endif(BUILD_CLIENT) @@ -651,6 +638,9 @@ if(BUILD_SERVER) ${CURL_LIBRARY} ) endif() + if(BUILD_BENCHMARKS) + target_link_libraries(${PROJECT_NAME}server catch2) + endif() endif(BUILD_SERVER) # Blacklisted locales that don't work. @@ -718,15 +708,13 @@ if(MSVC) endif() else() # GCC or compatible compilers such as Clang - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(WARNING_FLAGS "-Wall -Wextra") + set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-unused-parameter -Wno-implicit-fallthrough") if(WARN_ALL) - set(RELEASE_WARNING_FLAGS "-Wall") + set(RELEASE_WARNING_FLAGS "${WARNING_FLAGS}") else() set(RELEASE_WARNING_FLAGS "") endif() - if(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang") - set(WARNING_FLAGS "${WARNING_FLAGS} -Wsign-compare") - endif() if(APPLE AND USE_LUAJIT) # required per http://luajit.org/install.html @@ -737,7 +725,7 @@ else() # Move text segment below LuaJIT's 47-bit limit (see issue #9367) if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") # FreeBSD uses lld, and lld does not support -Ttext-segment, suggesting - # --image-base instead. Not sure if it's equivalent change for the purpose + # --image-base instead. Not sure if it's equivalent change for the purpose # but at least if fixes build on FreeBSD/aarch64 # XXX: the condition should also be changed to check for lld regardless of # os, bit CMake doesn't have anything like CMAKE_LINKER_IS_LLD yet @@ -768,7 +756,7 @@ else() endif() endif() - set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -Wall -pipe -funroll-loops") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${OTHER_FLAGS} -pipe -funroll-loops") if(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os") else() @@ -781,8 +769,9 @@ else() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MATH_FLAGS}") endif() endif() - set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") - set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g") + set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${WARNING_FLAGS} ${OTHER_FLAGS}") if(USE_GPROF) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") @@ -852,13 +841,14 @@ if(WIN32) if(LUA_DLL) install(FILES ${LUA_DLL} DESTINATION ${BINDIR}) endif() - if(BUILD_CLIENT AND IRRLICHT_DLL) - install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR}) - endif() if(BUILD_CLIENT AND USE_GETTEXT AND GETTEXT_DLL) install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR}) endif() endif() + + if(BUILD_CLIENT AND IRRLICHT_DLL) + install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR}) + endif() endif() if(BUILD_CLIENT) diff --git a/src/benchmark/CMakeLists.txt b/src/benchmark/CMakeLists.txt new file mode 100644 index 000000000..5feba345b --- /dev/null +++ b/src/benchmark/CMakeLists.txt @@ -0,0 +1,7 @@ +set (BENCHMARK_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp + PARENT_SCOPE) + +set (BENCHMARK_CLIENT_SRCS + PARENT_SCOPE) diff --git a/src/benchmark/benchmark.cpp b/src/benchmark/benchmark.cpp new file mode 100644 index 000000000..0bc2af368 --- /dev/null +++ b/src/benchmark/benchmark.cpp @@ -0,0 +1,32 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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 "benchmark/benchmark.h" + +// This must be set in just this file +#define CATCH_CONFIG_RUNNER +#include "benchmark_setup.h" + +int run_benchmarks() +{ + int argc = 1; + const char *argv[] = { "MinetestBenchmark", NULL }; + int errCount = Catch::Session().run(argc, argv); + return errCount ? 1 : 0; +} diff --git a/src/benchmark/benchmark.h b/src/benchmark/benchmark.h new file mode 100644 index 000000000..45dd9b6a4 --- /dev/null +++ b/src/benchmark/benchmark.h @@ -0,0 +1,26 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "config.h" + +#if BUILD_BENCHMARKS +extern int run_benchmarks(); +#endif diff --git a/src/benchmark/benchmark_serialize.cpp b/src/benchmark/benchmark_serialize.cpp new file mode 100644 index 000000000..97cc7d59d --- /dev/null +++ b/src/benchmark/benchmark_serialize.cpp @@ -0,0 +1,71 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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 "benchmark_setup.h" +#include "util/serialize.h" +#include <sstream> +#include <ios> + +// Builds a string of exactly `length` characters by repeating `s` (rest cut off) +static std::string makeRepeatTo(const std::string &s, size_t length) +{ + std::string v; + v.reserve(length + s.size()); + for (size_t i = 0; i < length; i += s.size()) { + v += s; + } + v.resize(length); + return v; +} + +#define BENCH3(_label, _chars, _length, _lengthlabel) \ + BENCHMARK_ADVANCED("serializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \ + std::string s = makeRepeatTo(_chars, _length); \ + meter.measure([&] { return serializeJsonStringIfNeeded(s); }); \ + }; \ + BENCHMARK_ADVANCED("deSerializeJsonStringIfNeeded_" _lengthlabel "_" _label)(Catch::Benchmark::Chronometer meter) { \ + std::string s = makeRepeatTo(_chars, _length); \ + std::string serialized = serializeJsonStringIfNeeded(s); \ + std::istringstream is(serialized, std::ios::binary); \ + meter.measure([&] { \ + is.clear(); \ + is.seekg(0, std::ios::beg); \ + return deSerializeJsonStringIfNeeded(is); \ + }); \ + }; + +/* Both with and without a space character (' ') */ +#define BENCH2(_label, _chars, _length, _lengthlabel) \ + BENCH3(_label, _chars, _length, _lengthlabel) \ + BENCH3(_label "_with_space", " " _chars, _length, _lengthlabel) \ + +/* Iterate over input lengths */ +#define BENCH1(_label, _chars) \ + BENCH2(_label, _chars, 10, "small") \ + BENCH2(_label, _chars, 10000, "large") + +/* Iterate over character sets */ +#define BENCH_ALL() \ + BENCH1("alpha", "abcdefghijklmnopqrstuvwxyz") \ + BENCH1("escaped", "\"\\/\b\f\n\r\t") \ + BENCH1("nonascii", "\xf0\xff") + +TEST_CASE("benchmark_serialize") { + BENCH_ALL() +} diff --git a/src/benchmark/benchmark_setup.h b/src/benchmark/benchmark_setup.h new file mode 100644 index 000000000..34a4eca4c --- /dev/null +++ b/src/benchmark/benchmark_setup.h @@ -0,0 +1,22 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +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. +*/ + +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#define CATCH_CONFIG_CONSOLE_WIDTH 160 +#include <catch.hpp> diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 8d058852a..656ad45ce 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -60,7 +60,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsshadercallbacks.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/shadows/shadowsScreenQuad.cpp PARENT_SCOPE ) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index d1f19adb3..df75c52d6 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -47,7 +47,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *rendering_engine): m_draw_control(draw_control), - m_client(client) + m_client(client), + m_player_light_color(0xFFFFFFFF) { auto smgr = rendering_engine->get_scene_manager(); // note: making the camera node a child of the player node @@ -74,11 +75,11 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re * (as opposed to the this local caching). This can be addressed in * a later release. */ - m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount"); - m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); + m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount", 0.0f, 100.0f); + m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount", 0.0f, 7.9f); // 45 degrees is the lowest FOV that doesn't cause the server to treat this // as a zoom FOV and load world beyond the set server limits. - m_cache_fov = std::fmax(g_settings->getFloat("fov"), 45.0f); + m_cache_fov = g_settings->getFloat("fov", 45.0f, 160.0f); m_arm_inertia = g_settings->getBool("arm_inertia"); m_nametags.clear(); m_show_nametag_backgrounds = g_settings->getBool("show_nametag_backgrounds"); @@ -153,8 +154,10 @@ void Camera::step(f32 dtime) bool was_under_zero = m_wield_change_timer < 0; m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125); - if (m_wield_change_timer >= 0 && was_under_zero) + if (m_wield_change_timer >= 0 && was_under_zero) { m_wieldnode->setItem(m_wield_item_next, m_client); + m_wieldnode->setNodeLightColor(m_player_light_color); + } if (m_view_bobbing_state != 0) { @@ -555,7 +558,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) m_wieldnode->setPosition(wield_position); m_wieldnode->setRotation(wield_rotation); - m_wieldnode->setNodeLightColor(player->light_color); + m_player_light_color = player->light_color; + m_wieldnode->setNodeLightColor(m_player_light_color); // Set render distance updateViewingRange(); diff --git a/src/client/camera.h b/src/client/camera.h index 403d6024c..cbf248d97 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -264,4 +264,7 @@ private: std::list<Nametag *> m_nametags; bool m_show_nametag_backgrounds; + + // Last known light color of the player + video::SColor m_player_light_color; }; diff --git a/src/client/client.cpp b/src/client/client.cpp index 0dd8a61d2..31bbf2463 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -58,6 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "game.h" #include "chatmessage.h" #include "translation.h" +#include "content/mod_configuration.h" extern gui::IGUIEnvironment* guienv; @@ -100,7 +101,8 @@ Client::Client( MtEventManager *event, RenderingEngine *rendering_engine, bool ipv6, - GameUI *game_ui + GameUI *game_ui, + ELoginRegister allow_login_or_register ): m_tsrc(tsrc), m_shsrc(shsrc), @@ -117,6 +119,7 @@ Client::Client( m_particle_manager(&m_env), m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)), m_address_name(address_name), + m_allow_login_or_register(allow_login_or_register), m_server_ser_ver(SER_FMT_VER_INVALID), m_last_chat_message_sent(time(NULL)), m_password(password), @@ -194,7 +197,21 @@ void Client::loadMods() scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath()); m_script->loadModFromMemory(BUILTIN_MOD_NAME); - ClientModConfiguration modconf(getClientModsLuaPath()); + ModConfiguration modconf; + { + std::unordered_map<std::string, std::string> paths; + std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; + const auto modsPath = getClientModsLuaPath(); + if (modsPath != path_user) { + paths["share"] = modsPath; + } + paths["mods"] = path_user; + + std::string settings_path = path_user + DIR_DELIM + "mods.conf"; + modconf.addModsFromConfig(settings_path, paths); + modconf.checkConflictsAndDeps(); + } + m_mods = modconf.getMods(); // complain about mods with unsatisfied dependencies if (!modconf.isConsistent()) { @@ -396,10 +413,6 @@ void Client::step(float dtime) initial_step = false; } else if(m_state == LC_Created) { - if (m_is_registration_confirmation_state) { - // Waiting confirmation - return; - } float &counter = m_connection_reinit_timer; counter -= dtime; if(counter <= 0.0) { @@ -426,7 +439,7 @@ void Client::step(float dtime) if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { std::vector<v3s16> deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), + std::max(g_settings->getFloat("client_unload_unused_data_timeout"), 0.0f), g_settings->getS32("client_mapblock_limit"), &deleted_blocks); @@ -495,6 +508,7 @@ void Client::step(float dtime) ClientEvent *event = new ClientEvent(); event->type = CE_PLAYER_DAMAGE; event->player_damage.amount = damage; + event->player_damage.effect = true; m_client_event_queue.push(event); } } @@ -530,6 +544,7 @@ void Client::step(float dtime) { int num_processed_meshes = 0; std::vector<v3s16> blocks_to_ack; + bool force_update_shadows = false; while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; @@ -556,9 +571,11 @@ void Client::step(float dtime) if (is_empty) delete r.mesh; - else + else { // Replace with the new mesh block->mesh = r.mesh; + force_update_shadows = true; + } } } else { delete r.mesh; @@ -583,6 +600,10 @@ void Client::step(float dtime) if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); + + auto shadow_renderer = RenderingEngine::get_shadow_renderer(); + if (shadow_renderer && force_update_shadows) + shadow_renderer->setForceUpdateShadowMap(); } /* @@ -786,16 +807,18 @@ void Client::peerAdded(con::Peer *peer) infostream << "Client::peerAdded(): peer->id=" << peer->id << std::endl; } + void Client::deletingPeer(con::Peer *peer, bool timeout) { infostream << "Client::deletingPeer(): " "Server Peer is getting deleted " << "(timeout=" << timeout << ")" << std::endl; - if (timeout) { - m_access_denied = true; + m_access_denied = true; + if (timeout) m_access_denied_reason = gettext("Connection timed out."); - } + else if (m_access_denied_reason.empty()) + m_access_denied_reason = gettext("Connection aborted (protocol error?)."); } /* @@ -1069,18 +1092,6 @@ void Client::sendInit(const std::string &playerName) Send(&pkt); } -void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism) -{ - m_chosen_auth_mech = chosen_auth_mechanism; - m_is_registration_confirmation_state = true; -} - -void Client::confirmRegistration() -{ - m_is_registration_confirmation_state = false; - startAuth(m_chosen_auth_mech); -} - void Client::startAuth(AuthMechanism chosen_auth_mechanism) { m_chosen_auth_mech = chosen_auth_mechanism; @@ -1258,7 +1269,7 @@ void Client::sendChatMessage(const std::wstring &message) pkt << message; Send(&pkt); - } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) { + } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size < 0) { m_out_chat_queue.push(message); } else { infostream << "Could not queue chat message because maximum out chat queue size (" @@ -1812,11 +1823,10 @@ void Client::makeScreenshot() if (!raw_image) return; - time_t t = time(NULL); - struct tm *tm = localtime(&t); + const struct tm tm = mt_localtime(); char timetstamp_c[64]; - strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); + strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", &tm); std::string screenshot_dir; diff --git a/src/client/client.h b/src/client/client.h index 84c85471d..bdcc2a3dd 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mesh_generator_thread.h" #include "network/address.h" #include "network/peerhandler.h" +#include "gameparams.h" #include <fstream> #define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f @@ -127,7 +128,8 @@ public: MtEventManager *event, RenderingEngine *rendering_engine, bool ipv6, - GameUI *game_ui + GameUI *game_ui, + ELoginRegister allow_login_or_register ); ~Client(); @@ -227,6 +229,7 @@ public: void handleCommand_PlayerSpeed(NetworkPacket *pkt); void handleCommand_MediaPush(NetworkPacket *pkt); void handleCommand_MinimapModes(NetworkPacket *pkt); + void handleCommand_SetLighting(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -346,8 +349,6 @@ public: u16 getProtoVersion() { return m_proto_ver; } - void confirmRegistration(); - bool m_is_registration_confirmation_state = false; bool m_simple_singleplayer_mode; float mediaReceiveProgress(); @@ -406,7 +407,7 @@ public: } ClientScripting *getScript() { return m_script; } - const bool modsLoaded() const { return m_mods_loaded; } + bool modsLoaded() const { return m_mods_loaded; } void pushToEventQueue(ClientEvent *event); @@ -459,7 +460,6 @@ private: static AuthMechanism choseAuthMech(const u32 mechs); void sendInit(const std::string &playerName); - void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism); void startAuth(AuthMechanism chosen_auth_mechanism); void sendDeletedBlocks(std::vector<v3s16> &blocks); void sendGotBlocks(const std::vector<v3s16> &blocks); @@ -491,6 +491,7 @@ private: ParticleManager m_particle_manager; std::unique_ptr<con::Connection> m_con; std::string m_address_name; + ELoginRegister m_allow_login_or_register = ELoginRegister::Any; Camera *m_camera = nullptr; Minimap *m_minimap = nullptr; bool m_minimap_disabled_by_server = false; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 448af36c6..183a95007 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -305,6 +305,7 @@ void ClientEnvironment::step(float dtime) node_at_lplayer = m_map->getNode(p); u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef()); + lplayer->light_color = encode_light(light, 0); // this transfers light.alpha final_color_blend(&lplayer->light_color, light, day_night_ratio); } diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 17d3aedd6..243a94596 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -87,6 +87,7 @@ struct ClientEvent struct { u16 amount; + bool effect; } player_damage; struct { diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 063154316..60c9525f3 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -451,6 +451,7 @@ bool ClientLauncher::launch_game(std::string &error_message, start_data.name = menudata.name; start_data.password = menudata.password; start_data.address = std::move(menudata.address); + start_data.allow_login_or_register = menudata.allow_login_or_register; server_name = menudata.servername; server_description = menudata.serverdescription; @@ -564,6 +565,8 @@ void ClientLauncher::speed_tests() // volatile to avoid some potential compiler optimisations volatile static s16 temp16; volatile static f32 tempf; + // Silence compiler warning + (void)temp16; static v3f tempv3f1; static v3f tempv3f2; static std::string tempstring; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 1a024e464..c5ba98ff6 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -97,9 +97,32 @@ ClientMap::ClientMap( m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); + m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance"); } +void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset) +{ + v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset; + v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE); + + m_camera_position = pos; + m_camera_direction = dir; + m_camera_fov = fov; + m_camera_offset = offset; + + v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset; + v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE); + + // reorder the blocks when camera crosses block boundary + if (previous_block != current_block) + m_needs_update_drawlist = true; + + // reorder transparent meshes when camera crosses node boundary + if (previous_node != current_node) + m_needs_update_transparent_meshes = true; +} + MapSector * ClientMap::emergeSector(v2s16 p2d) { // Check that it doesn't exist already @@ -196,13 +219,11 @@ void ClientMap::updateDrawList() // Number of blocks occlusion culled u32 blocks_occlusion_culled = 0; - // No occlusion culling when free_move is on and camera is - // inside ground + // No occlusion culling when free_move is on and camera is inside ground bool occlusion_culling_enabled = true; - if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) { + if (m_control.allow_noclip) { MapNode n = getNode(cam_pos_nodes); - if (n.getContent() == CONTENT_IGNORE || - m_nodedef->get(n).solidness == 2) + if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2) occlusion_culling_enabled = false; } @@ -324,21 +345,16 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) //u32 mesh_animate_count_far = 0; /* + Update transparent meshes + */ + if (is_transparent_pass) + updateTransparentMeshBuffers(); + + /* Draw the selected MapBlocks */ MeshBufListList grouped_buffers; - - struct DrawDescriptor { - v3s16 m_pos; - scene::IMeshBuffer *m_buffer; - bool m_reuse_material; - - DrawDescriptor(const v3s16 &pos, scene::IMeshBuffer *buffer, bool reuse_material) : - m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material) - {} - }; - std::vector<DrawDescriptor> draw_order; video::SMaterial previous_material; @@ -375,7 +391,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) /* Get the meshbuffers of the block */ - { + if (is_transparent_pass) { + // In transparent pass, the mesh will give us + // the partial buffers in the correct order + for (auto &buffer : block->mesh->getTransparentBuffers()) + draw_order.emplace_back(block_pos, &buffer); + } + else { + // otherwise, group buffers across meshes + // using MeshBufListList MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -389,35 +413,14 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) video::SMaterial& material = buf->getMaterial(); video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); + driver->getMaterialRenderer(material.MaterialType); bool transparent = (rnd && rnd->isTransparent()); - if (transparent == is_transparent_pass) { + if (!transparent) { if (buf->getVertexCount() == 0) errorstream << "Block [" << analyze_block(block) - << "] contains an empty meshbuf" << std::endl; - - material.setFlag(video::EMF_TRILINEAR_FILTER, - m_cache_trilinear_filter); - material.setFlag(video::EMF_BILINEAR_FILTER, - m_cache_bilinear_filter); - material.setFlag(video::EMF_ANISOTROPIC_FILTER, - m_cache_anistropic_filter); - material.setFlag(video::EMF_WIREFRAME, - m_control.show_wireframe); - - if (is_transparent_pass) { - // Same comparison as in MeshBufListList - bool new_material = material.getTexture(0) != previous_material.getTexture(0) || - material != previous_material; - - draw_order.emplace_back(block_pos, buf, !new_material); - - if (new_material) - previous_material = material; - } - else { - grouped_buffers.add(buf, block_pos, layer); - } + << "] contains an empty meshbuf" << std::endl; + + grouped_buffers.add(buf, block_pos, layer); } } } @@ -442,8 +445,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) // Render all mesh buffers in order drawcall_count += draw_order.size(); + for (auto &descriptor : draw_order) { - scene::IMeshBuffer *buf = descriptor.m_buffer; + scene::IMeshBuffer *buf = descriptor.getBuffer(); // Check and abort if the machine is swapping a lot if (draw.getTimerTime() > 2000) { @@ -454,28 +458,42 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) if (!descriptor.m_reuse_material) { auto &material = buf->getMaterial(); + + // Apply filter settings + material.setFlag(video::EMF_TRILINEAR_FILTER, + m_cache_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, + m_cache_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, + m_cache_anistropic_filter); + material.setFlag(video::EMF_WIREFRAME, + m_control.show_wireframe); + // pass the shadow map texture to the buffer texture ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer(); if (shadow && shadow->is_active()) { - auto &layer = material.TextureLayer[3]; + auto &layer = material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW]; layer.Texture = shadow->get_texture(); layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE; layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE; // Do not enable filter on shadow texture to avoid visual artifacts // with colored shadows. // Filtering is done in shader code anyway + layer.BilinearFilter = false; + layer.AnisotropicFilter = false; layer.TrilinearFilter = false; } driver->setMaterial(material); ++material_swaps; + material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr; } v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS); m.setTranslation(block_wpos - offset); driver->setTransform(video::ETS_WORLD, m); - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); + descriptor.draw(driver); + vertex_count += buf->getIndexCount(); } g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); @@ -659,19 +677,17 @@ void ClientMap::renderPostFx(CameraMode cam_mode) MapNode n = getNode(floatToInt(m_camera_position, BS)); - // - If the player is in a solid node, make everything black. - // - If the player is in liquid, draw a semi-transparent overlay. - // - Do not if player is in third person mode const ContentFeatures& features = m_nodedef->get(n); video::SColor post_effect_color = features.post_effect_color; - if(features.solidness == 2 && !(g_settings->getBool("noclip") && - m_client->checkLocalPrivilege("noclip")) && - cam_mode == CAMERA_MODE_FIRST) - { + + // If the camera is in a solid node, make everything black. + // (first person mode only) + if (features.solidness == 2 && cam_mode == CAMERA_MODE_FIRST && + !m_control.allow_noclip) { post_effect_color = video::SColor(255, 0, 0, 0); } - if (post_effect_color.getAlpha() != 0) - { + + if (post_effect_color.getAlpha() != 0) { // Draw a full-screen rectangle video::IVideoDriver* driver = SceneManager->getVideoDriver(); v2u32 ss = driver->getScreenSize(); @@ -698,7 +714,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, u32 drawcall_count = 0; u32 vertex_count = 0; - MeshBufListList drawbufs; + MeshBufListList grouped_buffers; + std::vector<DrawDescriptor> draw_order; + int count = 0; int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame; @@ -727,7 +745,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, /* Get the meshbuffers of the block */ - { + if (is_transparent_pass) { + // In transparent pass, the mesh will give us + // the partial buffers in the correct order + for (auto &buffer : block->mesh->getTransparentBuffers()) + draw_order.emplace_back(block_pos, &buffer); + } + else { + // otherwise, group buffers across meshes + // using MeshBufListList MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -742,79 +768,91 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, video::SMaterial &mat = buf->getMaterial(); auto rnd = driver->getMaterialRenderer(mat.MaterialType); bool transparent = rnd && rnd->isTransparent(); - if (transparent == is_transparent_pass) - drawbufs.add(buf, block_pos, layer); + if (!transparent) + grouped_buffers.add(buf, block_pos, layer); } } } } + u32 buffer_count = 0; + for (auto &lists : grouped_buffers.lists) + for (MeshBufList &list : lists) + buffer_count += list.bufs.size(); + + draw_order.reserve(draw_order.size() + buffer_count); + + // Capture draw order for all solid meshes + for (auto &lists : grouped_buffers.lists) { + for (MeshBufList &list : lists) { + // iterate in reverse to draw closest blocks first + for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) + draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin()); + } + } + TimeTaker draw("Drawing shadow mesh buffers"); core::matrix4 m; // Model matrix v3f offset = intToFloat(m_camera_offset, BS); + u32 material_swaps = 0; - // Render all layers in order - for (auto &lists : drawbufs.lists) { - for (MeshBufList &list : lists) { - // Check and abort if the machine is swapping a lot - if (draw.getTimerTime() > 1000) { - infostream << "ClientMap::renderMapShadows(): Rendering " - "took >1s, returning." << std::endl; - break; - } - for (auto &pair : list.bufs) { - scene::IMeshBuffer *buf = pair.second; - - // override some material properties - video::SMaterial local_material = buf->getMaterial(); - local_material.MaterialType = material.MaterialType; - local_material.BackfaceCulling = material.BackfaceCulling; - local_material.FrontfaceCulling = material.FrontfaceCulling; - local_material.BlendOperation = material.BlendOperation; - local_material.Lighting = false; - driver->setMaterial(local_material); - - v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS); - m.setTranslation(block_wpos - offset); - - driver->setTransform(video::ETS_WORLD, m); - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - } + // Render all mesh buffers in order + drawcall_count += draw_order.size(); + + for (auto &descriptor : draw_order) { + scene::IMeshBuffer *buf = descriptor.getBuffer(); - drawcall_count += list.bufs.size(); + // Check and abort if the machine is swapping a lot + if (draw.getTimerTime() > 1000) { + infostream << "ClientMap::renderMapShadows(): Rendering " + "took >1s, returning." << std::endl; + break; + } + + if (!descriptor.m_reuse_material) { + // override some material properties + video::SMaterial local_material = buf->getMaterial(); + local_material.MaterialType = material.MaterialType; + local_material.BackfaceCulling = material.BackfaceCulling; + local_material.FrontfaceCulling = material.FrontfaceCulling; + local_material.BlendOperation = material.BlendOperation; + local_material.Lighting = false; + driver->setMaterial(local_material); + ++material_swaps; } + + v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS); + m.setTranslation(block_wpos - offset); + + driver->setTransform(video::ETS_WORLD, m); + descriptor.draw(driver); + vertex_count += buf->getIndexCount(); } - // restore the driver material state + // restore the driver material state video::SMaterial clean; clean.BlendOperation = video::EBO_ADD; driver->setMaterial(clean); // reset material to defaults driver->draw3DLine(v3f(), v3f(), video::SColor(0)); - + g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); + g_profiler->avg(prefix + "material swaps [#]", material_swaps); } /* Custom update draw list for the pov of shadow light. */ -void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range) +void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length) { ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG); - const v3f camera_position = shadow_light_pos; - const v3f camera_direction = shadow_light_dir; - // I "fake" fov just to avoid creating a new function to handle orthographic - // projection. - const f32 camera_fov = m_camera_fov * 1.9f; - - v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS); v3s16 p_blocks_min; v3s16 p_blocks_max; - getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range); + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length); std::vector<v2s16> blocks_in_range; @@ -824,15 +862,6 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha } m_drawlist_shadow.clear(); - // We need to append the blocks from the camera POV because sometimes - // they are not inside the light frustum and it creates glitches. - // FIXME: This could be removed if we figure out why they are missing - // from the light frustum. - for (auto &i : m_drawlist) { - i.second->refGrab(); - m_drawlist_shadow[i.first] = i.second; - } - // Number of blocks currently loaded by the client u32 blocks_loaded = 0; // Number of blocks with mesh in rendering range @@ -858,23 +887,13 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha continue; } - float range = shadow_range; - - float d = 0.0; - if (!isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, range, &d)) + v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS); + v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos); + if (projection.getDistanceFrom(block_pos) > radius) continue; blocks_in_range_with_mesh++; - /* - Occlusion culling - */ - if (isBlockOccluded(block, cam_pos_nodes)) { - blocks_occlusion_culled++; - continue; - } - // This block is in range. Reset usage timer. block->resetUsageTimer(); @@ -891,3 +910,55 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size()); g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded); } + +void ClientMap::updateTransparentMeshBuffers() +{ + ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG); + u32 sorted_blocks = 0; + u32 unsorted_blocks = 0; + f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f); + + + // Update the order of transparent mesh buffers in each mesh + for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) { + MapBlock* block = it->second; + if (!block->mesh) + continue; + + if (m_needs_update_transparent_meshes || + block->mesh->getTransparentBuffers().size() == 0) { + + v3s16 block_pos = block->getPos(); + v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS); + f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f); + if (distance <= sorting_distance_sq) { + block->mesh->updateTransparentBuffers(m_camera_position, block_pos); + ++sorted_blocks; + } + else { + block->mesh->consolidateTransparentBuffers(); + ++unsorted_blocks; + } + } + } + + g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks); + g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks); + m_needs_update_transparent_meshes = false; +} + +scene::IMeshBuffer* ClientMap::DrawDescriptor::getBuffer() +{ + return m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer; +} + +void ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver) +{ + if (m_use_partial_buffer) { + m_partial_buffer->beforeDraw(); + driver->drawMeshBuffer(m_partial_buffer->getBuffer()); + m_partial_buffer->afterDraw(); + } else { + driver->drawMeshBuffer(m_buffer); + } +} diff --git a/src/client/clientmap.h b/src/client/clientmap.h index b4dc42395..8c45b5382 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -27,10 +27,12 @@ with this program; if not, write to the Free Software Foundation, Inc., struct MapDrawControl { - // Overrides limits by drawing everything - bool range_all = false; // Wanted drawing range float wanted_range = 0.0f; + // Overrides limits by drawing everything + bool range_all = false; + // Allow rendering out of bounds + bool allow_noclip = false; // show a wire frame for debugging bool show_wireframe = false; }; @@ -56,6 +58,7 @@ struct MeshBufListList class Client; class ITextureSource; +class PartialMeshBuffer; /* ClientMap @@ -75,53 +78,37 @@ public: virtual ~ClientMap() = default; - s32 mapType() const + bool maySaveBlocks() override { - return MAPTYPE_CLIENT; + return false; } - void drop() + void drop() override { - ISceneNode::drop(); + ISceneNode::drop(); // calls destructor } - void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) - { - v3s16 previous_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE); - - m_camera_position = pos; - m_camera_direction = dir; - m_camera_fov = fov; - m_camera_offset = offset; - - v3s16 current_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE); - - // reorder the blocks when camera crosses block boundary - if (previous_block != current_block) - m_needs_update_drawlist = true; - } + void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset); /* Forcefully get a sector from somewhere */ - MapSector * emergeSector(v2s16 p); - - //void deSerializeSector(v2s16 p2d, std::istream &is); + MapSector * emergeSector(v2s16 p) override; /* ISceneNode methods */ - virtual void OnRegisterSceneNode(); + virtual void OnRegisterSceneNode() override; - virtual void render() + virtual void render() override { video::IVideoDriver* driver = SceneManager->getVideoDriver(); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); renderMap(driver, SceneManager->getSceneNodeRenderPass()); } - virtual const aabb3f &getBoundingBox() const + virtual const aabb3f &getBoundingBox() const override { return m_box; } @@ -129,7 +116,7 @@ public: void getBlocksInViewRange(v3s16 cam_pos_nodes, v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range=-1.0f); void updateDrawList(); - void updateDrawListShadow(const v3f &shadow_light_pos, const v3f &shadow_light_dir, float shadow_range); + void updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length); // Returns true if draw list needs updating before drawing the next frame. bool needsUpdateDrawList() { return m_needs_update_drawlist; } void renderMap(video::IVideoDriver* driver, s32 pass); @@ -143,13 +130,17 @@ public: void renderPostFx(CameraMode cam_mode); // For debug printing - virtual void PrintInfo(std::ostream &out); + void PrintInfo(std::ostream &out) override; const MapDrawControl & getControl() const { return m_control; } f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } private: + + // update the vertex order in transparent mesh buffers + void updateTransparentMeshBuffers(); + // Orders blocks by distance to the camera class MapBlockComparer { @@ -167,6 +158,29 @@ private: v3s16 m_camera_block; }; + + // reference to a mesh buffer used when rendering the map. + struct DrawDescriptor { + v3s16 m_pos; + union { + scene::IMeshBuffer *m_buffer; + const PartialMeshBuffer *m_partial_buffer; + }; + bool m_reuse_material:1; + bool m_use_partial_buffer:1; + + DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) : + m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false) + {} + + DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) : + m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true) + {} + + scene::IMeshBuffer* getBuffer(); + void draw(video::IVideoDriver* driver); + }; + Client *m_client; RenderingEngine *m_rendering_engine; @@ -179,6 +193,7 @@ private: v3f m_camera_direction = v3f(0,0,1); f32 m_camera_fov = M_PI; v3s16 m_camera_offset; + bool m_needs_update_transparent_meshes = true; std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist; std::map<v3s16, MapBlock*> m_drawlist_shadow; @@ -190,4 +205,5 @@ private: bool m_cache_bilinear_filter; bool m_cache_anistropic_filter; bool m_added_to_shadow_renderer{false}; + u16 m_cache_transparency_sorting_distance; }; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 383a1d799..c84c03034 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -366,7 +366,8 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) void Clouds::readSettings() { - m_cloud_radius_i = g_settings->getU16("cloud_radius"); + // Upper limit was chosen due to posible render bugs + m_cloud_radius_i = rangelim(g_settings->getU16("cloud_radius"), 1, 62); m_enable_3d = g_settings->getBool("enable_3d_clouds"); } diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 1d4636a08..568d25fb7 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -38,9 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "mesh.h" #include "nodedef.h" -#include "serialization.h" // For decompressZlib #include "settings.h" -#include "sound.h" #include "tool.h" #include "wieldmesh.h" #include <algorithm> @@ -435,7 +433,7 @@ const v3f GenericCAO::getPosition() const return m_position; } -const bool GenericCAO::isImmortal() +bool GenericCAO::isImmortal() const { return itemgroup_get(getGroups(), "immortal"); } @@ -745,9 +743,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); - // Set it to use the materials of the meshbuffers directly. - // This is needed for changing the texture in the future - m_meshnode->setReadOnlyMaterials(true); } else if (m_prop.visual == "cube") { grabMatrixNode(); scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); @@ -861,10 +856,11 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) void GenericCAO::updateLight(u32 day_night_ratio) { - if (m_glow < 0) + if (m_prop.glow < 0) return; - u8 light_at_pos = 0; + u16 light_at_pos = 0; + u8 light_at_pos_intensity = 0; bool pos_ok = false; v3s16 pos[3]; @@ -873,28 +869,33 @@ void GenericCAO::updateLight(u32 day_night_ratio) bool this_ok; MapNode n = m_env->getMap().getNode(pos[i], &this_ok); if (this_ok) { - u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef()); - light_at_pos = MYMAX(light_at_pos, this_light); + u16 this_light = getInteriorLight(n, 0, m_client->ndef()); + u8 this_light_intensity = MYMAX(this_light & 0xFF, this_light >> 8); + if (this_light_intensity > light_at_pos_intensity) { + light_at_pos = this_light; + light_at_pos_intensity = this_light_intensity; + } pos_ok = true; } } if (!pos_ok) - light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0); + light_at_pos = LIGHT_SUN; + + video::SColor light = encode_light(light_at_pos, m_prop.glow); + if (!m_enable_shaders) + final_color_blend(&light, light_at_pos, day_night_ratio); - u8 light = decode_light(light_at_pos + m_glow); if (light != m_last_light) { m_last_light = light; setNodeLight(light); } } -void GenericCAO::setNodeLight(u8 light) +void GenericCAO::setNodeLight(const video::SColor &light_color) { - video::SColor color(255, light, light, light); - if (m_prop.visual == "wielditem" || m_prop.visual == "item") { if (m_wield_meshnode) - m_wield_meshnode->setNodeLightColor(color); + m_wield_meshnode->setNodeLightColor(light_color); return; } @@ -902,12 +903,8 @@ void GenericCAO::setNodeLight(u8 light) if (m_prop.visual == "upright_sprite") { if (!m_meshnode) return; - - scene::IMesh *mesh = m_meshnode->getMesh(); - for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - buf->getMaterial().EmissiveColor = color; - } + for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) + m_meshnode->getMaterial(i).EmissiveColor = light_color; } else { scene::ISceneNode *node = getSceneNode(); if (!node) @@ -915,16 +912,16 @@ void GenericCAO::setNodeLight(u8 light) for (u32 i = 0; i < node->getMaterialCount(); ++i) { video::SMaterial &material = node->getMaterial(i); - material.EmissiveColor = color; + material.EmissiveColor = light_color; } } } else { if (m_meshnode) { - setMeshColor(m_meshnode->getMesh(), color); + setMeshColor(m_meshnode->getMesh(), light_color); } else if (m_animated_meshnode) { - setAnimatedMeshColor(m_animated_meshnode, color); + setAnimatedMeshColor(m_animated_meshnode, light_color); } else if (m_spritenode) { - m_spritenode->setColor(color); + m_spritenode->setColor(light_color); } } } @@ -1175,7 +1172,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // Reduce footstep gain, as non-local-player footsteps are // somehow louder. spec.gain *= 0.6f; - m_client->sound()->playSoundAt(spec, false, getPosition()); + m_client->sound()->playSoundAt(spec, getPosition()); } } } @@ -1200,7 +1197,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) } } - if (!getParent() && node && fabs(m_prop.automatic_rotate) > 0.001f) { + if (node && fabs(m_prop.automatic_rotate) > 0.001f) { // This is the child node's rotation. It is only used for automatic_rotate. v3f local_rot = node->getRotation(); local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG * @@ -1320,7 +1317,6 @@ void GenericCAO::updateTextures(std::string mod) m_previous_texture_modifier = m_current_texture_modifier; m_current_texture_modifier = mod; - m_glow = m_prop.glow; if (m_spritenode) { if (m_prop.visual == "sprite") { @@ -1440,22 +1436,22 @@ void GenericCAO::updateTextures(std::string mod) if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); - buf->getMaterial().setTexture(0, + auto& material = m_meshnode->getMaterial(0); + material.setTexture(0, tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if(!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + material.AmbientColor = m_prop.colors[0]; + material.DiffuseColor = m_prop.colors[0]; + material.SpecularColor = m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } { std::string tname = "no_texture.png"; @@ -1464,29 +1460,29 @@ void GenericCAO::updateTextures(std::string mod) else if (!m_prop.textures.empty()) tname = m_prop.textures[0]; tname += mod; - scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); - buf->getMaterial().setTexture(0, + auto& material = m_meshnode->getMaterial(1); + material.setTexture(0, tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest // has directional lighting, it should work automatically. if (m_prop.colors.size() >= 2) { - buf->getMaterial().AmbientColor = m_prop.colors[1]; - buf->getMaterial().DiffuseColor = m_prop.colors[1]; - buf->getMaterial().SpecularColor = m_prop.colors[1]; + material.AmbientColor = m_prop.colors[1]; + material.DiffuseColor = m_prop.colors[1]; + material.SpecularColor = m_prop.colors[1]; } else if (!m_prop.colors.empty()) { - buf->getMaterial().AmbientColor = m_prop.colors[0]; - buf->getMaterial().DiffuseColor = m_prop.colors[0]; - buf->getMaterial().SpecularColor = m_prop.colors[0]; + material.AmbientColor = m_prop.colors[0]; + material.DiffuseColor = m_prop.colors[0]; + material.SpecularColor = m_prop.colors[0]; } - buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); - buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + material.setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + material.setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + material.setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); } // Set mesh color (only if lighting is disabled) - if (!m_prop.colors.empty() && m_glow < 0) + if (!m_prop.colors.empty() && m_prop.glow < 0) setMeshColor(mesh, m_prop.colors[0]); } } @@ -1960,20 +1956,17 @@ void GenericCAO::updateMeshCulling() const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST; - if (m_meshnode && m_prop.visual == "upright_sprite") { - u32 buffers = m_meshnode->getMesh()->getMeshBufferCount(); - for (u32 i = 0; i < buffers; i++) { - video::SMaterial &mat = m_meshnode->getMesh()->getMeshBuffer(i)->getMaterial(); - // upright sprite has no backface culling - mat.setFlag(video::EMF_FRONT_FACE_CULLING, hidden); - } - return; - } - scene::ISceneNode *node = getSceneNode(); + if (!node) return; + if (m_prop.visual == "upright_sprite") { + // upright sprite has no backface culling + node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, hidden); + return; + } + if (hidden) { // Hide the mesh by culling both front and // back faces. Serious hackyness but it works for our diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 4bbba9134..5a8116c71 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -125,9 +125,8 @@ private: std::string m_current_texture_modifier = ""; bool m_visuals_expired = false; float m_step_distance_counter = 0.0f; - u8 m_last_light = 255; + video::SColor m_last_light = video::SColor(0xFFFFFFFF); bool m_is_visible = false; - s8 m_glow = 0; // Material video::E_MATERIAL_TYPE m_material_type; // Settings @@ -165,14 +164,9 @@ public: const v3f getPosition() const; - void setPosition(const v3f &pos) - { - pos_translator.val_current = pos; - } - inline const v3f &getRotation() const { return m_rotation; } - const bool isImmortal(); + bool isImmortal() const; inline const ObjectProperties &getProperties() const { return m_prop; } @@ -245,7 +239,7 @@ public: void updateLight(u32 day_night_ratio); - void setNodeLight(u8 light); + void setNodeLight(const video::SColor &light); /* Get light position(s). * returns number of positions written into pos[], which must have space diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index bb2d6398f..0bac5e827 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -150,8 +150,10 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, // should be (2+2)*6=24 values in the list. The order of // the faces in the list is up-down-right-left-back-front // (compatible with ContentFeatures). +// mask - a bit mask that suppresses drawing of tiles. +// tile i will not be drawn if mask & (1 << i) is 1 void MapblockMeshGenerator::drawCuboid(const aabb3f &box, - TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) + TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask) { assert(tilecount >= 1 && tilecount <= 6); // pre-condition @@ -274,6 +276,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, // Add to mesh collector for (int k = 0; k < 6; ++k) { + if (mask & (1 << k)) + continue; int tileindex = MYMIN(k, tilecount - 1); collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); } @@ -363,7 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * } void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, - TileSpec *tiles, int tile_count) + TileSpec *tiles, int tile_count, u8 mask) { bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f; f32 texture_coord_buf[24]; @@ -400,12 +404,49 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, d.Z = (j & 1) ? dz2 : dz1; lights[j] = blendLight(d); } - drawCuboid(box, tiles, tile_count, lights, txc); + drawCuboid(box, tiles, tile_count, lights, txc, mask); } else { - drawCuboid(box, tiles, tile_count, nullptr, txc); + drawCuboid(box, tiles, tile_count, nullptr, txc, mask); } } +u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const +{ + const f32 NODE_BOUNDARY = 0.5 * BS; + + // For an oversized nodebox, return immediately + if (box.MaxEdge.X > NODE_BOUNDARY || + box.MinEdge.X < -NODE_BOUNDARY || + box.MaxEdge.Y > NODE_BOUNDARY || + box.MinEdge.Y < -NODE_BOUNDARY || + box.MaxEdge.Z > NODE_BOUNDARY || + box.MinEdge.Z < -NODE_BOUNDARY) + return 0; + + // We can skip faces at node boundary if the matching neighbor is solid + u8 solid_mask = + (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) | + (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) | + (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) | + (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) | + (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) | + (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0); + + u8 sametype_mask = 0; + if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) { + // In opaque nodeboxes, faces on opposite sides can cancel + // each other out if there is a matching neighbor of the same type + sametype_mask = + ((solid_mask & 3) == 3 ? 3 : 0) | + ((solid_mask & 12) == 12 ? 12 : 0) | + ((solid_mask & 48) == 48 ? 48 : 0); + } + + // Combine masks with actual neighbors to get the faces to be skipped + return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors); +} + + void MapblockMeshGenerator::prepareLiquidNodeDrawing() { getSpecialTile(0, &tile_liquid_top); @@ -1363,13 +1404,38 @@ void MapblockMeshGenerator::drawNodeboxNode() getTile(nodebox_tile_dirs[face], &tiles[face]); } + bool param2_is_rotation = + f->param_type_2 == CPT2_COLORED_FACEDIR || + f->param_type_2 == CPT2_COLORED_WALLMOUNTED || + f->param_type_2 == CPT2_FACEDIR || + f->param_type_2 == CPT2_WALLMOUNTED; + + bool param2_is_level = + f->param_type_2 == CPT2_LEVELED; + // locate possible neighboring nodes to connect to u8 neighbors_set = 0; - if (f->node_box.type == NODEBOX_CONNECTED) { - for (int dir = 0; dir != 6; dir++) { - u8 flag = 1 << dir; - v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; - MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + u8 solid_neighbors = 0; + u8 sametype_neighbors = 0; + for (int dir = 0; dir != 6; dir++) { + u8 flag = 1 << dir; + v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir]; + MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + + // mark neighbors that are the same node type + // and have the same rotation or higher level stored as param2 + if (n2.param0 == n.param0 && + (!param2_is_rotation || n.param2 == n2.param2) && + (!param2_is_level || n.param2 <= n2.param2)) + sametype_neighbors |= flag; + + // mark neighbors that are simple solid blocks + if (nodedef->get(n2).drawtype == NDT_NORMAL) + solid_neighbors |= flag; + + if (f->node_box.type == NODEBOX_CONNECTED) { + p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; + n2 = data->m_vmanip.getNodeNoEx(p2); if (nodedef->nodeboxConnects(n, n2, flag)) neighbors_set |= flag; } @@ -1377,8 +1443,63 @@ void MapblockMeshGenerator::drawNodeboxNode() std::vector<aabb3f> boxes; n.getNodeBoxes(nodedef, &boxes, neighbors_set); - for (auto &box : boxes) - drawAutoLightedCuboid(box, nullptr, tiles, 6); + + bool isTransparent = false; + + for (const TileSpec &tile : tiles) { + if (tile.layers[0].isTransparent()) { + isTransparent = true; + break; + } + } + + if (isTransparent) { + std::vector<float> sections; + // Preallocate 8 default splits + Min&Max for each nodebox + sections.reserve(8 + 2 * boxes.size()); + + for (int axis = 0; axis < 3; axis++) { + // identify sections + + if (axis == 0) { + // Default split at node bounds, up to 3 nodes in each direction + for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS) + sections.push_back(s); + } + else { + // Avoid readding the same 8 default splits for Y and Z + sections.resize(8); + } + + // Add edges of existing node boxes, rounded to 1E-3 + for (size_t i = 0; i < boxes.size(); i++) { + sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3); + sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3); + } + + // split the boxes at recorded sections + // limit splits to avoid runaway crash if inner loop adds infinite splits + // due to e.g. precision problems. + // 100 is just an arbitrary, reasonably high number. + for (size_t i = 0; i < boxes.size() && i < 100; i++) { + aabb3f *box = &boxes[i]; + for (float section : sections) { + if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) { + aabb3f copy(*box); + copy.MinEdge[axis] = section; + box->MaxEdge[axis] = section; + boxes.push_back(copy); + box = &boxes[i]; // find new address of the box in case of reallocation + } + } + } + } + } + + for (auto &box : boxes) { + u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); + drawAutoLightedCuboid(box, nullptr, tiles, 6, mask); + } } void MapblockMeshGenerator::drawMeshNode() diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 7344f05ee..b13748cbc 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -100,10 +100,11 @@ public: // cuboid drawing! void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, - const LightInfo *lights , const f32 *txc); + const LightInfo *lights , const f32 *txc, u8 mask = 0); void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, - TileSpec *tiles = NULL, int tile_count = 0); + TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0); + u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; // liquid-specific bool top_is_same_liquid; diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index ad8305b45..0ae50dfe2 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "gettext.h" #include "irrlicht_changes/CGUITTFont.h" +#include "util/numeric.h" // rangelim /** maximum size distance for getting a "similar" font size */ #define MAX_FONT_SIZE_OFFSET 10 @@ -172,9 +173,9 @@ unsigned int FontEngine::getFontSize(FontMode mode) /******************************************************************************/ void FontEngine::readSettings() { - m_default_size[FM_Standard] = g_settings->getU16("font_size"); - m_default_size[_FM_Fallback] = g_settings->getU16("font_size"); - m_default_size[FM_Mono] = g_settings->getU16("mono_font_size"); + m_default_size[FM_Standard] = rangelim(g_settings->getU16("font_size"), 5, 72); + m_default_size[_FM_Fallback] = m_default_size[FM_Standard]; + m_default_size[FM_Mono] = rangelim(g_settings->getU16("mono_font_size"), 5, 72); m_default_bold = g_settings->getBool("font_bold"); m_default_italic = g_settings->getBool("font_italic"); @@ -217,8 +218,9 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) if (spec.italic) setting_suffix.append("_italic"); - u32 size = std::max<u32>(spec.size * RenderingEngine::getDisplayDensity() * - g_settings->getFloat("gui_scaling"), 1); + // Font size in pixels for FreeType + u32 size = rangelim(spec.size * RenderingEngine::getDisplayDensity() * + g_settings->getFloat("gui_scaling"), 1U, 500U); // Constrain the font size to a certain multiple, if necessary u16 divisible_by = g_settings->getU16(setting_prefix + "font_size_divisible_by"); diff --git a/src/client/game.cpp b/src/client/game.cpp index 768b1abad..c34e3a415 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -43,7 +43,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "gettext.h" #include "gui/guiChatConsole.h" -#include "gui/guiConfirmRegistration.h" #include "gui/guiFormSpecMenu.h" #include "gui/guiKeyChangeMenu.h" #include "gui/guiPasswordChange.h" @@ -283,7 +282,7 @@ public: if (m_player_step_timer <= 0 && m_player_step_sound.exists()) { m_player_step_timer = 0.03; if (makes_footstep_sound) - m_sound->playSound(m_player_step_sound, false); + m_sound->playSound(m_player_step_sound); } } @@ -291,7 +290,7 @@ public: { if (m_player_jump_timer <= 0.0f) { m_player_jump_timer = 0.2f; - m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false); + m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f)); } } @@ -316,32 +315,32 @@ public: static void cameraPunchLeft(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); + sm->m_sound->playSound(sm->m_player_leftpunch_sound); } static void cameraPunchRight(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); + sm->m_sound->playSound(sm->m_player_rightpunch_sound); } static void nodeDug(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; NodeDugEvent *nde = (NodeDugEvent *)e; - sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false); + sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug); } static void playerDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5)); } static void playerFallingDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5)); } void registerReceiver(MtEventManager *mgr) @@ -422,6 +421,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting<float, 3> m_camera_offset_vertex; CachedPixelShaderSetting<SamplerLayer_t> m_base_texture; CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture; + CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags; Client *m_client; public: @@ -456,6 +456,7 @@ public: m_camera_offset_vertex("cameraOffset"), m_base_texture("baseTexture"), m_normal_texture("normalTexture"), + m_texture_flags("textureFlags"), m_client(client) { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); @@ -525,9 +526,12 @@ public: m_camera_offset_pixel.set(camera_offset_array, services); m_camera_offset_vertex.set(camera_offset_array, services); - SamplerLayer_t base_tex = 0, normal_tex = 1; + 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); } }; @@ -723,7 +727,7 @@ protected: void processClientEvents(CameraOrientation *cam); void updateCamera(f32 dtime); void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); + void processPlayerInteraction(f32 dtime, bool show_hud); /*! * Returns the object or node the player is pointing at. * Also updates the selected thing in the Hud. @@ -842,7 +846,6 @@ private: EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; - bool registration_confirmation_shown = false; std::unique_ptr<GameUI> m_game_ui; GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() @@ -1063,9 +1066,9 @@ bool Game::startup(bool *kill, void Game::run() { ProfilerGraph graph; - RunStats stats = { 0 }; - CameraOrientation cam_view_target = { 0 }; - CameraOrientation cam_view = { 0 }; + RunStats stats = {}; + CameraOrientation cam_view_target = {}; + CameraOrientation cam_view = {}; FpsControl draw_times; f32 dtime; // in seconds @@ -1137,8 +1140,7 @@ void Game::run() updateDebugState(); updateCamera(dtime); updateSound(dtime); - processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_basic_debug); + processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -1481,7 +1483,8 @@ bool Game::connectToServer(const GameStartData &start_data, start_data.password, start_data.address, *draw_control, texture_src, shader_src, itemdef_manager, nodedef_manager, sound, eventmgr, - m_rendering_engine, connect_address.isIPv6(), m_game_ui.get()); + m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(), + start_data.allow_login_or_register); client->migrateModStorage(); } catch (const BaseException &e) { *error_message = fmtgettext("Error creating client: %s", e.what()); @@ -1492,7 +1495,7 @@ bool Game::connectToServer(const GameStartData &start_data, client->m_simple_singleplayer_mode = simple_singleplayer_mode; infostream << "Connecting to server at "; - connect_address.print(&infostream); + connect_address.print(infostream); infostream << std::endl; client->connect(connect_address, @@ -1544,28 +1547,16 @@ bool Game::connectToServer(const GameStartData &start_data, break; } - if (client->m_is_registration_confirmation_state) { - if (registration_confirmation_shown) { - // Keep drawing the GUI - m_rendering_engine->draw_menu_scene(guienv, dtime, true); - } else { - registration_confirmation_shown = true; - (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, - &g_menumgr, client, start_data.name, start_data.password, - connection_aborted, texture_src))->drop(); - } - } else { - wait_time += dtime; - // Only time out if we aren't waiting for the server we started - if (!start_data.address.empty() && wait_time > 10) { - *error_message = gettext("Connection timed out."); - errorstream << *error_message << std::endl; - break; - } - - // Update status - showOverlayMessage(N_("Connecting to server..."), dtime, 20); + wait_time += dtime; + // Only time out if we aren't waiting for the server we started + if (!start_data.address.empty() && wait_time > 10) { + *error_message = gettext("Connection timed out."); + errorstream << *error_message << std::endl; + break; } + + // Update status + showOverlayMessage(N_("Connecting to server..."), dtime, 20); } } catch (con::PeerNotFoundException &e) { // TODO: Should something be done here? At least an info/error @@ -1743,22 +1734,26 @@ void Game::processQueues() void Game::updateDebugState() { - const bool has_basic_debug = true; + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // debug UI and wireframe bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); if (m_game_ui->m_flags.show_basic_debug) { - if (!has_basic_debug) { + if (!has_basic_debug) m_game_ui->m_flags.show_basic_debug = false; - } } else if (m_game_ui->m_flags.show_minimal_debug) { - if (has_basic_debug) { + if (has_basic_debug) m_game_ui->m_flags.show_basic_debug = true; - } } if (!has_basic_debug) hud->disableBlockBounds(); if (!has_debug) draw_control->show_wireframe = false; + + // noclip + draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip"); } void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, @@ -1914,7 +1909,7 @@ void Game::processKeyInput() if (client->modsLoaded()) openConsole(0.2, L"."); else - m_game_ui->showStatusText(wgettext("Client side scripting is disabled")); + m_game_ui->showTranslatedStatusText("Client side scripting is disabled"); } else if (wasKeyDown(KeyType::CONSOLE)) { openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f)); } else if (wasKeyDown(KeyType::FREEMOVE)) { @@ -1941,7 +1936,7 @@ void Game::processKeyInput() } } else if (wasKeyDown(KeyType::INC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); + float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f; g_settings->setFloat("sound_volume", new_volume); std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); m_game_ui->showStatusText(msg); @@ -1950,7 +1945,7 @@ void Game::processKeyInput() } } else if (wasKeyDown(KeyType::DEC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); + float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f; g_settings->setFloat("sound_volume", new_volume); std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); m_game_ui->showStatusText(msg); @@ -2214,27 +2209,27 @@ void Game::toggleCinematic() void Game::toggleBlockBounds() { - if (true /* basic_debug */) { - enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); - switch (newmode) { - case Hud::BLOCK_BOUNDS_OFF: - m_game_ui->showTranslatedStatusText("Block bounds hidden"); - break; - case Hud::BLOCK_BOUNDS_CURRENT: - m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); - break; - case Hud::BLOCK_BOUNDS_NEAR: - m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); - break; - case Hud::BLOCK_BOUNDS_MAX: - m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); - break; - default: - break; - } - - } else { - m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)"); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) { + m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)"); + return; + } + enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); + switch (newmode) { + case Hud::BLOCK_BOUNDS_OFF: + m_game_ui->showTranslatedStatusText("Block bounds hidden"); + break; + case Hud::BLOCK_BOUNDS_CURRENT: + m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); + break; + case Hud::BLOCK_BOUNDS_NEAR: + m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); + break; + case Hud::BLOCK_BOUNDS_MAX: + m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); + break; + default: + break; } } @@ -2301,6 +2296,9 @@ void Game::toggleFog() void Game::toggleDebug() { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); // Initial: No debug info // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph @@ -2310,9 +2308,8 @@ void Game::toggleDebug() // The debug text can be in 2 modes: minimal and basic. // * Minimal: Only technical client info that not gameplay-relevant // * Basic: Info that might give gameplay advantage, e.g. pos, angle - // Basic mode is always used. - - const bool has_basic_debug = true; + // Basic mode is used when player has the debug HUD flag set, + // otherwise the Minimal mode is used. if (!m_game_ui->m_flags.show_minimal_debug) { m_game_ui->m_flags.show_minimal_debug = true; if (has_basic_debug) @@ -2336,7 +2333,7 @@ void Game::toggleDebug() m_game_ui->m_flags.show_basic_debug = false; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; - if (client->checkPrivilege("debug")) { + if (has_debug) { m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); } else { m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); @@ -2512,11 +2509,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam) input->getMovementDirection() ); - // autoforward if set: move towards pointed position at maximum speed + // autoforward if set: move at maximum speed if (player->getPlayerSettings().continuous_forward && client->activeObjectsReceived() && !player->isDead()) { control.movement_speed = 1.0f; - control.movement_direction = 0.0f; + // sideways movement only + float dx = sin(control.movement_direction); + control.movement_direction = atan2(dx, 1.0f); } #ifdef HAVE_TOUCHSCREENGUI @@ -2612,6 +2611,9 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation if (client->modsLoaded()) client->getScript()->on_damage_taken(event->player_damage.amount); + if (!event->player_damage.effect) + return; + // Damage flash and hurt tilt are not used at death if (client->getHP() > 0) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2880,6 +2882,7 @@ void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam sky->setStarCount(event->star_params->count); sky->setStarColor(event->star_params->starcolor); sky->setStarScale(event->star_params->scale); + sky->setStarDayOpacity(event->star_params->day_opacity); delete event->star_params; } @@ -3042,7 +3045,7 @@ void Game::updateSound(f32 dtime) } -void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) +void Game::processPlayerInteraction(f32 dtime, bool show_hud) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -3162,7 +3165,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { v3f player_position = player->getPosition(); - handlePointingAtObject(pointed, tool_item, player_position, show_debug); + bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); + handlePointingAtObject(pointed, tool_item, player_position, + m_game_ui->m_flags.show_basic_debug && basic_debug_allowed); } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation runData.punching = true; @@ -3760,7 +3765,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, float direct_brightness; bool sunlight_seen; - if (m_cache_enable_noclip && m_cache_enable_free_move) { + // When in noclip mode force same sky brightness as above ground so you + // can see properly + if (draw_control->allow_noclip && m_cache_enable_free_move && + client->checkPrivilege("fly")) { direct_brightness = time_brightness; sunlight_seen = true; } else { @@ -4043,7 +4051,12 @@ void Game::updateShadows() float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); - float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f; + float timeoftheday = getWickedTimeOfDay(in_timeofday); + bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; + bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); + shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); + + timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; const float offset_constant = 10000.0f; v3f light(0.0f, 0.0f, -1.0f); @@ -4075,10 +4088,10 @@ void FpsControl::reset() */ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) { - const u64 frametime_min = 1000000.0f / ( - device->isWindowFocused() && !g_menumgr.pausesGame() + const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame()) ? g_settings->getFloat("fps_max") - : g_settings->getFloat("fps_max_unfocused")); + : g_settings->getFloat("fps_max_unfocused"); + const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f); u64 time = porting::getTimeUs(); @@ -4127,9 +4140,9 @@ void Game::readSettings() 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_place_time = g_settings->getFloat("repeat_place_time"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f); + m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f); + m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0); m_cache_enable_noclip = g_settings->getBool("noclip"); m_cache_enable_free_move = g_settings->getBool("free_move"); diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 8505ea3ae..909719bbe 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -68,7 +68,7 @@ void GameUI::init() u16 chat_font_size = g_settings->getU16("chat_font_size"); if (chat_font_size != 0) { m_guitext_chat->setOverrideFont(g_fontengine->getFont( - chat_font_size, FM_Unspecified)); + rangelim(chat_font_size, 5, 72), FM_Unspecified)); } @@ -151,9 +151,13 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ const NodeDefManager *nodedef = client->getNodeDefManager(); MapNode n = map.getNode(pointed_old.node_undersurface); - if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") { - os << ", pointed: " << nodedef->get(n).name - << ", param2: " << (u64) n.getParam2(); + if (n.getContent() != CONTENT_IGNORE) { + if (nodedef->get(n).name == "unknown") { + os << ", pointed: <unknown node>"; + } else { + os << ", pointed: " << nodedef->get(n).name; + } + os << ", param2: " << (u64) n.getParam2(); } } diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index de122becf..42508259f 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -176,52 +176,61 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, } void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect<s32> &rect, const core::rect<s32> &middle, - const core::rect<s32> *cliprect, const video::SColor *const colors) + const core::rect<s32> &destrect, const core::rect<s32> &srcrect, + const core::rect<s32> &middlerect, const core::rect<s32> *cliprect, + const video::SColor *const colors) { - auto originalSize = texture->getOriginalSize(); - core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner; + // `-x` is interpreted as `w - x` + core::rect<s32> middle = middlerect; + + if (middlerect.LowerRightCorner.X < 0) + middle.LowerRightCorner.X += srcrect.getWidth(); + if (middlerect.LowerRightCorner.Y < 0) + middle.LowerRightCorner.Y += srcrect.getHeight(); + + core::vector2di lower_right_offset = core::vector2di(srcrect.getWidth(), + srcrect.getHeight()) - middle.LowerRightCorner; for (int y = 0; y < 3; ++y) { for (int x = 0; x < 3; ++x) { - core::rect<s32> src({0, 0}, originalSize); - core::rect<s32> dest = rect; + core::rect<s32> src = srcrect; + core::rect<s32> dest = destrect; switch (x) { case 0: - dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X; - src.LowerRightCorner.X = middle.UpperLeftCorner.X; + dest.LowerRightCorner.X = destrect.UpperLeftCorner.X + middle.UpperLeftCorner.X; + src.LowerRightCorner.X = srcrect.UpperLeftCorner.X + middle.UpperLeftCorner.X; break; case 1: dest.UpperLeftCorner.X += middle.UpperLeftCorner.X; - dest.LowerRightCorner.X -= lowerRightOffset.X; - src.UpperLeftCorner.X = middle.UpperLeftCorner.X; - src.LowerRightCorner.X = middle.LowerRightCorner.X; + dest.LowerRightCorner.X -= lower_right_offset.X; + src.UpperLeftCorner.X += middle.UpperLeftCorner.X; + src.LowerRightCorner.X -= lower_right_offset.X; break; case 2: - dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X; - src.UpperLeftCorner.X = middle.LowerRightCorner.X; + dest.UpperLeftCorner.X = destrect.LowerRightCorner.X - lower_right_offset.X; + src.UpperLeftCorner.X = srcrect.LowerRightCorner.X - lower_right_offset.X; break; } switch (y) { case 0: - dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; - src.LowerRightCorner.Y = middle.UpperLeftCorner.Y; + dest.LowerRightCorner.Y = destrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; + src.LowerRightCorner.Y = srcrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; break; case 1: dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y; - dest.LowerRightCorner.Y -= lowerRightOffset.Y; - src.UpperLeftCorner.Y = middle.UpperLeftCorner.Y; - src.LowerRightCorner.Y = middle.LowerRightCorner.Y; + dest.LowerRightCorner.Y -= lower_right_offset.Y; + src.UpperLeftCorner.Y += middle.UpperLeftCorner.Y; + src.LowerRightCorner.Y -= lower_right_offset.Y; break; case 2: - dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y; - src.UpperLeftCorner.Y = middle.LowerRightCorner.Y; + dest.UpperLeftCorner.Y = destrect.LowerRightCorner.Y - lower_right_offset.Y; + src.UpperLeftCorner.Y = srcrect.LowerRightCorner.Y - lower_right_offset.Y; break; } diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index 379a4bdb0..f2d2fce10 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -46,13 +46,13 @@ video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::IText */ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, const core::rect<s32> &destrect, const core::rect<s32> &srcrect, - const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0, - bool usealpha = false); + const core::rect<s32> *cliprect = nullptr, + const video::SColor *const colors = nullptr, bool usealpha = false); /* * 9-slice / segment drawing */ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect<s32> &rect, const core::rect<s32> &middle, - const core::rect<s32> *cliprect = nullptr, + const core::rect<s32> &destrect, const core::rect<s32> &srcrect, + const core::rect<s32> &middlerect, const core::rect<s32> *cliprect = nullptr, const video::SColor *const colors = nullptr); diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 259a18ab9..c0c289608 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -55,7 +55,7 @@ Hud::Hud(Client *client, LocalPlayer *player, this->player = player; this->inventory = inventory; - m_hud_scaling = g_settings->getFloat("hud_scaling"); + m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f); m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity(); m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE * RenderingEngine::getDisplayDensity() + 0.5f); @@ -1013,6 +1013,10 @@ void drawItemStack( bool has_mesh = false; ItemMesh *imesh; + core::rect<s32> viewrect = rect; + if (clip != nullptr) + viewrect.clipAgainst(*clip); + // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || def.inventory_image.empty()) { imesh = client->idef()->getWieldMesh(def.name, client); @@ -1034,9 +1038,6 @@ void drawItemStack( core::rect<s32> oldViewPort = driver->getViewPort(); core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); - core::rect<s32> viewrect = rect; - if (clip) - viewrect.clipAgainst(*clip); core::matrix4 ProjMatrix; ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f); @@ -1180,24 +1181,68 @@ void drawItemStack( driver->draw2DRectangle(color, progressrect2, clip); } - if (font != NULL && item.count >= 2) { + const std::string &count_text = item.metadata.getString("count_meta"); + if (font != nullptr && (item.count >= 2 || !count_text.empty())) { // Get the item count as a string - std::string text = itos(item.count); - v2u32 dim = font->getDimension(utf8_to_wide(text).c_str()); + std::string text = count_text.empty() ? itos(item.count) : count_text; + v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str()); v2s32 sdim(dim.X, dim.Y); core::rect<s32> rect2( - /*rect.UpperLeftCorner, - core::dimension2d<u32>(rect.getWidth(), 15)*/ rect.LowerRightCorner - sdim, - sdim + rect.LowerRightCorner ); - video::SColor bgcolor(128, 0, 0, 0); - driver->draw2DRectangle(bgcolor, rect2, clip); + // get the count alignment + s32 count_alignment = stoi(item.metadata.getString("count_alignment")); + if (count_alignment != 0) { + s32 a_x = count_alignment & 3; + s32 a_y = (count_alignment >> 2) & 3; + + s32 x1, x2, y1, y2; + switch (a_x) { + case 1: // left + x1 = rect.UpperLeftCorner.X; + x2 = x1 + sdim.X; + break; + case 2: // middle + x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2; + x2 = x1 + sdim.X; + break; + case 3: // right + x2 = rect.LowerRightCorner.X; + x1 = x2 - sdim.X; + break; + default: // 0 = default + x1 = rect2.UpperLeftCorner.X; + x2 = rect2.LowerRightCorner.X; + break; + } + + switch (a_y) { + case 1: // up + y1 = rect.UpperLeftCorner.Y; + y2 = y1 + sdim.Y; + break; + case 2: // middle + y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2; + y2 = y1 + sdim.Y; + break; + case 3: // down + y2 = rect.LowerRightCorner.Y; + y1 = y2 - sdim.Y; + break; + default: // 0 = default + y1 = rect2.UpperLeftCorner.Y; + y2 = rect2.LowerRightCorner.Y; + break; + } + + rect2 = core::rect<s32>(x1, y1, x2, y2); + } video::SColor color(255, 255, 255, 255); - font->draw(text.c_str(), rect2, color, false, false, clip); + font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect); } } diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index b62e336f7..c9d1504ad 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -124,7 +124,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) // Ignore pixels we haven't processed if (!bitmap.get(sx, sy)) continue; - + // Add RGB values weighted by alpha IF the pixel is opaque, otherwise // use full weight since we want to propagate colors. video::SColor d = src->getPixel(sx, sy); diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index aae73c62d..9e58b9f62 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettime.h" #include "porting.h" #include "util/string.h" +#include "util/numeric.h" bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const { @@ -202,9 +203,9 @@ JoystickLayout create_dragonrise_gamecube_layout() } -JoystickController::JoystickController() : - doubling_dtime(g_settings->getFloat("repeat_joystick_button_time")) +JoystickController::JoystickController() { + doubling_dtime = std::max(g_settings->getFloat("repeat_joystick_button_time"), 0.001f); for (float &i : m_past_pressed_time) { i = 0; } @@ -217,19 +218,20 @@ void JoystickController::onJoystickConnect(const std::vector<irr::SJoystickInfo> s32 id = g_settings->getS32("joystick_id"); std::string layout = g_settings->get("joystick_type"); - if (id < 0 || (u16)id >= joystick_infos.size()) { + if (id < 0 || id >= (s32)joystick_infos.size()) { // TODO: auto detection id = 0; } - if (id >= 0 && (u16)id < joystick_infos.size()) { + if (id >= 0 && id < (s32)joystick_infos.size()) { if (layout.empty() || layout == "auto") setLayoutFromControllerName(joystick_infos[id].Name.c_str()); else setLayoutFromControllerName(layout); } - m_joystick_id = id; + // Irrlicht restriction. + m_joystick_id = rangelim(id, 0, UINT8_MAX); } void JoystickController::setLayoutFromControllerName(const std::string &name) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 279efafe9..79fe2cb11 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -426,16 +426,6 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, camera_impact = getSpeed().Y * -1; } - { - camera_barely_in_ceiling = false; - v3s16 camera_np = floatToInt(getEyePosition(), BS); - MapNode n = map->getNode(camera_np); - if (n.getContent() != CONTENT_IGNORE) { - if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2) - camera_barely_in_ceiling = true; - } - } - /* Check properties of the node on which the player is standing */ @@ -696,8 +686,7 @@ v3s16 LocalPlayer::getLightPosition() const v3f LocalPlayer::getEyeOffset() const { - float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height; - return v3f(0.0f, BS * eye_height, 0.0f); + return v3f(0.0f, BS * m_eye_height, 0.0f); } ClientActiveObject *LocalPlayer::getParent() const @@ -1025,16 +1014,6 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, camera_impact = getSpeed().Y * -1.0f; } - { - camera_barely_in_ceiling = false; - v3s16 camera_np = floatToInt(getEyePosition(), BS); - MapNode n = map->getNode(camera_np); - if (n.getContent() != CONTENT_IGNORE) { - if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2) - camera_barely_in_ceiling = true; - } - } - /* Update the node last under the player */ diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 577be2803..650a01574 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "constants.h" #include "settings.h" +#include "lighting.h" #include <list> class Client; @@ -158,6 +159,8 @@ public: added_velocity += vel; } + inline Lighting& getLighting() { return m_lighting; } + private: void accelerate(const v3f &target_speed, const f32 max_increase_H, const f32 max_increase_V, const bool use_pitch); @@ -196,7 +199,6 @@ private: u16 m_breath = PLAYER_MAX_BREATH_DEFAULT; f32 m_yaw = 0.0f; f32 m_pitch = 0.0f; - bool camera_barely_in_ceiling = false; aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f, BS * 1.75f, BS * 0.30f); float m_eye_height = 1.625f; @@ -209,4 +211,5 @@ private: GenericCAO *m_cao = nullptr; Client *m_client; + Lighting m_lighting; }; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index e077011cc..c730b9bf9 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/meshgen/collector.h" #include "client/renderingengine.h" #include <array> +#include <algorithm> /* MeshMakeData @@ -861,7 +862,7 @@ static void updateFastFaceRow( g_settings->getBool("enable_waving_water"); static thread_local const bool force_not_tiling = - false && g_settings->getBool("enable_dynamic_shadows"); + g_settings->getBool("enable_dynamic_shadows"); v3s16 p = startpos; @@ -1004,6 +1005,179 @@ static void applyTileColor(PreMeshBuffer &pmb) } /* + MapBlockBspTree +*/ + +void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles) +{ + this->triangles = triangles; + + nodes.clear(); + + // assert that triangle index can fit into s32 + assert(triangles->size() <= 0x7FFFFFFFL); + std::vector<s32> indexes; + indexes.reserve(triangles->size()); + for (u32 i = 0; i < triangles->size(); i++) + indexes.push_back(i); + + if (!indexes.empty()) { + // Start in the center of the block with increment of one quarter in each direction + root = buildTree(v3f(1, 0, 0), v3f((MAP_BLOCKSIZE + 1) * 0.5f * BS), MAP_BLOCKSIZE * 0.25f * BS, indexes, 0); + } else { + root = -1; + } +} + +/** + * @brief Find a candidate plane to split a set of triangles in two + * + * The candidate plane is represented by one of the triangles from the set. + * + * @param list Vector of indexes of the triangles in the set + * @param triangles Vector of all triangles in the BSP tree + * @return Address of the triangle that represents the proposed split plane + */ +static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles) +{ + // find the center of the cluster. + v3f center(0, 0, 0); + size_t n = list.size(); + for (s32 i : list) { + center += triangles[i].centroid / n; + } + + // find the triangle with the largest area and closest to the center + const MeshTriangle *candidate_triangle = &triangles[list[0]]; + const MeshTriangle *ith_triangle; + for (s32 i : list) { + ith_triangle = &triangles[i]; + if (ith_triangle->areaSQ > candidate_triangle->areaSQ || + (ith_triangle->areaSQ == candidate_triangle->areaSQ && + ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) { + candidate_triangle = ith_triangle; + } + } + return candidate_triangle; +} + +s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth) +{ + // if the list is empty, don't bother + if (list.empty()) + return -1; + + // if there is only one triangle, or the delta is insanely small, this is a leaf node + if (list.size() == 1 || delta < 0.01) { + nodes.emplace_back(normal, origin, list, -1, -1); + return nodes.size() - 1; + } + + std::vector<s32> front_list; + std::vector<s32> back_list; + std::vector<s32> node_list; + + // split the list + for (s32 i : list) { + const MeshTriangle &triangle = (*triangles)[i]; + float factor = normal.dotProduct(triangle.centroid - origin); + if (factor == 0) + node_list.push_back(i); + else if (factor > 0) + front_list.push_back(i); + else + back_list.push_back(i); + } + + // define the new split-plane + v3f candidate_normal(normal.Z, normal.X, normal.Y); + float candidate_delta = delta; + if (depth % 3 == 2) + candidate_delta /= 2; + + s32 front_index = -1; + s32 back_index = -1; + + if (!front_list.empty()) { + v3f next_normal = candidate_normal; + v3f next_origin = origin + delta * normal; + float next_delta = candidate_delta; + if (next_delta < 5) { + const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles); + next_normal = candidate->getNormal(); + next_origin = candidate->centroid; + } + front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1); + + // if there are no other triangles, don't create a new node + if (back_list.empty() && node_list.empty()) + return front_index; + } + + if (!back_list.empty()) { + v3f next_normal = candidate_normal; + v3f next_origin = origin - delta * normal; + float next_delta = candidate_delta; + if (next_delta < 5) { + const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles); + next_normal = candidate->getNormal(); + next_origin = candidate->centroid; + } + + back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1); + + // if there are no other triangles, don't create a new node + if (front_list.empty() && node_list.empty()) + return back_index; + } + + nodes.emplace_back(normal, origin, node_list, front_index, back_index); + + return nodes.size() - 1; +} + +void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const +{ + if (node < 0) return; // recursion break; + + const TreeNode &n = nodes[node]; + float factor = n.normal.dotProduct(viewpoint - n.origin); + + if (factor > 0) + traverse(n.back_ref, viewpoint, output); + else + traverse(n.front_ref, viewpoint, output); + + if (factor != 0) + for (s32 i : n.triangle_refs) + output.push_back(i); + + if (factor > 0) + traverse(n.front_ref, viewpoint, output); + else + traverse(n.back_ref, viewpoint, output); +} + + + +/* + PartialMeshBuffer +*/ + +void PartialMeshBuffer::beforeDraw() const +{ + // Patch the indexes in the mesh buffer before draw + m_buffer->Indices = std::move(m_vertex_indexes); + m_buffer->setDirty(scene::EBT_INDEX); +} + +void PartialMeshBuffer::afterDraw() const +{ + // Take the data back + m_vertex_indexes = m_buffer->Indices.steal(); +} + +/* MapBlockMesh */ @@ -1084,6 +1258,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): Convert MeshCollector to SMesh */ + const bool desync_animations = g_settings->getBool( + "desynchronize_mapblock_texture_animation"); + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) { @@ -1113,18 +1290,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): // - Texture animation if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { // Add to MapBlockMesh in order to animate these tiles - m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer; - m_animation_frames[std::pair<u8, u32>(layer, i)] = 0; - if (g_settings->getBool( - "desynchronize_mapblock_texture_animation")) { + auto &info = m_animation_info[{layer, i}]; + info.tile = p.layer; + info.frame = 0; + if (desync_animations) { // Get starting position from noise - m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = + info.frame_offset = 100000 * (2.0 + noise3d( data->m_blockpos.X, data->m_blockpos.Y, data->m_blockpos.Z, 0)); } else { // Play all synchronized - m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0; + info.frame_offset = 0; } // Replace tile texture with the first animation frame p.layer.texture = (*p.layer.frames)[0].texture; @@ -1135,19 +1312,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): // Dummy sunlight to handle non-sunlit areas video::SColorf sunlight; get_sunlight_color(&sunlight, 0); - u32 vertex_count = p.vertices.size(); + + std::map<u32, video::SColor> colors; + const u32 vertex_count = p.vertices.size(); for (u32 j = 0; j < vertex_count; j++) { video::SColor *vc = &p.vertices[j].Color; video::SColor copy = *vc; if (vc->getAlpha() == 0) // No sunlight - no need to animate final_color_blend(vc, copy, sunlight); // Finalize color else // Record color to animate - m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy; + colors[j] = copy; // The sunlight ratio has been stored, // delete alpha (for the final rendering). vc->setAlpha(255); } + if (!colors.empty()) + m_daynight_diffs[{layer, i}] = std::move(colors); } // Create material @@ -1173,8 +1354,23 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Material = material; - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); + if (p.layer.isTransparent()) { + buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0); + + MeshTriangle t; + t.buffer = buf; + m_transparent_triangles.reserve(p.indices.size() / 3); + for (u32 i = 0; i < p.indices.size(); i += 3) { + t.p1 = p.indices[i]; + t.p2 = p.indices[i + 1]; + t.p3 = p.indices[i + 2]; + t.updateAttributes(); + m_transparent_triangles.push_back(t); + } + } else { + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + } mesh->addMeshBuffer(buf); buf->drop(); } @@ -1187,12 +1383,13 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): } //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl; + m_bsp_tree.buildTree(&m_transparent_triangles); // Check if animation is required for this mesh m_has_animation = !m_crack_materials.empty() || !m_daynight_diffs.empty() || - !m_animation_tiles.empty(); + !m_animation_info.empty(); } MapBlockMesh::~MapBlockMesh() @@ -1226,25 +1423,22 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, for (auto &crack_material : m_crack_materials) { scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> getMeshBuffer(crack_material.first.second); - std::string basename = crack_material.second; // Create new texture name from original - std::ostringstream os; - os << basename << crack; + std::string s = crack_material.second + itos(crack); u32 new_texture_id = 0; video::ITexture *new_texture = - m_tsrc->getTextureForMesh(os.str(), &new_texture_id); + m_tsrc->getTextureForMesh(s, &new_texture_id); buf->getMaterial().setTexture(0, new_texture); - // If the current material is also animated, - // update animation info - auto anim_iter = m_animation_tiles.find(crack_material.first); - if (anim_iter != m_animation_tiles.end()) { - TileLayer &tile = anim_iter->second; + // If the current material is also animated, update animation info + auto anim_it = m_animation_info.find(crack_material.first); + if (anim_it != m_animation_info.end()) { + TileLayer &tile = anim_it->second.tile; tile.texture = new_texture; tile.texture_id = new_texture_id; // force animation update - m_animation_frames[crack_material.first] = -1; + anim_it->second.frame = -1; } } @@ -1252,28 +1446,25 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, } // Texture animation - for (auto &animation_tile : m_animation_tiles) { - const TileLayer &tile = animation_tile.second; + for (auto &it : m_animation_info) { + const TileLayer &tile = it.second.tile; // Figure out current frame - int frameoffset = m_animation_frame_offsets[animation_tile.first]; - int frame = (int)(time * 1000 / tile.animation_frame_length_ms - + frameoffset) % tile.animation_frame_count; + int frameno = (int)(time * 1000 / tile.animation_frame_length_ms + + it.second.frame_offset) % tile.animation_frame_count; // If frame doesn't change, skip - if (frame == m_animation_frames[animation_tile.first]) + if (frameno == it.second.frame) continue; - m_animation_frames[animation_tile.first] = frame; + it.second.frame = frameno; - scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]-> - getMeshBuffer(animation_tile.first.second); + scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second); - const FrameSpec &animation_frame = (*tile.frames)[frame]; - buf->getMaterial().setTexture(0, animation_frame.texture); + const FrameSpec &frame = (*tile.frames)[frameno]; + buf->getMaterial().setTexture(0, frame.texture); if (m_enable_shaders) { - if (animation_frame.normal_texture) - buf->getMaterial().setTexture(1, - animation_frame.normal_texture); - buf->getMaterial().setTexture(2, animation_frame.flags_texture); + if (frame.normal_texture) + buf->getMaterial().setTexture(1, frame.normal_texture); + buf->getMaterial().setTexture(2, frame.flags_texture); } } @@ -1300,6 +1491,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, return true; } +void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) +{ + // nothing to do if the entire block is opaque + if (m_transparent_triangles.empty()) + return; + + v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS); + v3f rel_camera_pos = camera_pos - block_posf; + + std::vector<s32> triangle_refs; + m_bsp_tree.traverse(rel_camera_pos, triangle_refs); + + // arrange index sequences into partial buffers + m_transparent_buffers.clear(); + + scene::SMeshBuffer *current_buffer = nullptr; + std::vector<u16> current_strain; + for (auto i : triangle_refs) { + const auto &t = m_transparent_triangles[i]; + if (current_buffer != t.buffer) { + if (current_buffer) { + m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); + current_strain.clear(); + } + current_buffer = t.buffer; + } + current_strain.push_back(t.p1); + current_strain.push_back(t.p2); + current_strain.push_back(t.p3); + } + + if (!current_strain.empty()) + m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); +} + +void MapBlockMesh::consolidateTransparentBuffers() +{ + m_transparent_buffers.clear(); + + scene::SMeshBuffer *current_buffer = nullptr; + std::vector<u16> current_strain; + + // use the fact that m_transparent_triangles is already arranged by buffer + for (const auto &t : m_transparent_triangles) { + if (current_buffer != t.buffer) { + if (current_buffer != nullptr) { + this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); + current_strain.clear(); + } + current_buffer = t.buffer; + } + current_strain.push_back(t.p1); + current_strain.push_back(t.p2); + current_strain.push_back(t.p3); + } + + if (!current_strain.empty()) { + this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); + } +} + video::SColor encode_light(u16 light, u8 emissive_light) { // Get components diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 3b17c4af9..169b3a8c1 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -71,6 +71,102 @@ struct MeshMakeData void setSmoothLighting(bool smooth_lighting); }; +// represents a triangle as indexes into the vertex buffer in SMeshBuffer +class MeshTriangle +{ +public: + scene::SMeshBuffer *buffer; + u16 p1, p2, p3; + v3f centroid; + float areaSQ; + + void updateAttributes() + { + v3f v1 = buffer->getPosition(p1); + v3f v2 = buffer->getPosition(p2); + v3f v3 = buffer->getPosition(p3); + + centroid = (v1 + v2 + v3) / 3; + areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4; + } + + v3f getNormal() const { + v3f v1 = buffer->getPosition(p1); + v3f v2 = buffer->getPosition(p2); + v3f v3 = buffer->getPosition(p3); + + return (v2-v1).crossProduct(v3-v1); + } +}; + +/** + * Implements a binary space partitioning tree + * See also: https://en.wikipedia.org/wiki/Binary_space_partitioning + */ +class MapBlockBspTree +{ +public: + MapBlockBspTree() {} + + void buildTree(const std::vector<MeshTriangle> *triangles); + + void traverse(v3f viewpoint, std::vector<s32> &output) const + { + traverse(root, viewpoint, output); + } + +private: + // Tree node definition; + struct TreeNode + { + v3f normal; + v3f origin; + std::vector<s32> triangle_refs; + s32 front_ref; + s32 back_ref; + + TreeNode() = default; + TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) : + normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref) + {} + }; + + + s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth); + void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const; + + const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally + std::vector<TreeNode> nodes; // list of nodes + s32 root = -1; // index of the root node +}; + +/* + * PartialMeshBuffer + * + * Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different + * indices with the same vertex buffer. + * + * Irrlicht does not currently support this: `CMeshBuffer` ties together a single vertex buffer + * and a single index buffer. There's no way to share these between mesh buffers. + * + */ +class PartialMeshBuffer +{ +public: + PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector<u16> &&vertex_indexes) : + m_buffer(buffer), m_vertex_indexes(std::move(vertex_indexes)) + {} + + scene::IMeshBuffer *getBuffer() const { return m_buffer; } + const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; } + + void beforeDraw() const; + void afterDraw() const; +private: + scene::SMeshBuffer *m_buffer; + mutable std::vector<u16> m_vertex_indexes; +}; + /* Holds a mesh for a mapblock. @@ -125,7 +221,23 @@ public: m_animation_force_timer--; } + /// update transparent buffers to render towards the camera + void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos); + void consolidateTransparentBuffers(); + + /// get the list of transparent buffers + const std::vector<PartialMeshBuffer> &getTransparentBuffers() const + { + return this->m_transparent_buffers; + } + private: + struct AnimationInfo { + int frame; // last animation frame + int frame_offset; + TileLayer tile; + }; + scene::IMesh *m_mesh[MAX_TILE_LAYERS]; MinimapMapblock *m_minimap_mapblock; ITextureSource *m_tsrc; @@ -144,12 +256,10 @@ private: // Maps mesh and mesh buffer (i.e. material) indices to base texture names std::map<std::pair<u8, u32>, std::string> m_crack_materials; - // Animation info: texture animationi + // Animation info: texture animation // Maps mesh and mesh buffer indices to TileSpecs // Keys are pairs of (mesh index, buffer index in the mesh) - std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles; - std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame - std::map<std::pair<u8, u32>, int> m_animation_frame_offsets; + std::map<std::pair<u8, u32>, AnimationInfo> m_animation_info; // Animation info: day/night transitions // Last daynight_ratio value passed to animate() @@ -158,6 +268,13 @@ private: // of sunlit vertices // Keys are pairs of (mesh index, buffer index in the mesh) std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs; + + // list of all semitransparent triangles in the mapblock + std::vector<MeshTriangle> m_transparent_triangles; + // Binary Space Partitioning tree for the block + MapBlockBspTree m_bsp_tree; + // Ordered list of references to parts of transparent buffers to draw + std::vector<PartialMeshBuffer> m_transparent_buffers; }; /*! diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index c56eba2e2..4bf07effa 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mesh.h" #include "debug.h" #include "log.h" -#include "irrMap.h" #include <cmath> #include <iostream> #include <IAnimatedMesh.h> diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index 5c3f4180b..c1bd7388e 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -50,7 +50,8 @@ QueuedMeshUpdate::~QueuedMeshUpdate() */ MeshUpdateQueue::MeshUpdateQueue(Client *client): - m_client(client) + m_client(client), + m_next_cache_cleanup(0) { m_cache_enable_shaders = g_settings->getBool("enable_shaders"); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); @@ -157,8 +158,7 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo size_t *cache_hit_counter) { CachedMapBlockData *cached_block = nullptr; - std::map<v3s16, CachedMapBlockData*>::iterator it = - m_cache.find(p); + auto it = m_cache.find(p); if (it != m_cache.end()) { cached_block = it->second; @@ -192,7 +192,7 @@ CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mo CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p) { - std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.find(p); + auto it = m_cache.find(p); if (it != m_cache.end()) { return it->second; } @@ -231,6 +231,15 @@ void MeshUpdateQueue::cleanupCache() g_profiler->avg("MeshUpdateQueue MapBlock cache size kB", mapblock_kB * m_cache.size()); + // Iterating the entire cache can get pretty expensive so don't do it too often + { + constexpr int cleanup_interval = 250; + const u64 now = porting::getTimeMs(); + if (m_next_cache_cleanup > now) + return; + m_next_cache_cleanup = now + cleanup_interval; + } + // The cache size is kept roughly below cache_soft_max_size, not letting // anything get older than cache_seconds_max or deleted before 2 seconds. const int cache_seconds_max = 10; @@ -240,12 +249,11 @@ void MeshUpdateQueue::cleanupCache() int t_now = time(0); - for (std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.begin(); - it != m_cache.end(); ) { + for (auto it = m_cache.begin(); it != m_cache.end(); ) { CachedMapBlockData *cached_block = it->second; if (cached_block->refcount_from_queue == 0 && cached_block->last_used_timestamp < t_now - cache_seconds) { - m_cache.erase(it++); + it = m_cache.erase(it); delete cached_block; } else { ++it; diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index 1b734bc06..552b2a9f0 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <ctime> #include <mutex> +#include <unordered_map> +#include <unordered_set> #include "mapblock_mesh.h" #include "threading/mutex_auto_lock.h" #include "util/thread.h" @@ -81,8 +83,9 @@ public: private: Client *m_client; std::vector<QueuedMeshUpdate *> m_queue; - std::set<v3s16> m_urgents; - std::map<v3s16, CachedMapBlockData *> m_cache; + std::unordered_set<v3s16> m_urgents; + std::unordered_map<v3s16, CachedMapBlockData *> m_cache; + u64 m_next_cache_cleanup; // milliseconds std::mutex m_mutex; // TODO: Add callback to update these when g_settings changes diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index f26aa1c70..320621d91 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -304,7 +304,7 @@ void Minimap::setModeIndex(size_t index) data->mode = m_modes[index]; m_current_mode_index = index; } else { - data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, ""}; + data->mode = {MINIMAP_TYPE_OFF, gettext("Minimap hidden"), 0, 0, "", 0}; m_current_mode_index = 0; } diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 288826a5f..818cdc8cc 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -34,23 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" /* - Utility -*/ - -static f32 random_f32(f32 min, f32 max) -{ - return rand() / (float)RAND_MAX * (max - min) + min; -} - -static v3f random_v3f(v3f min, v3f max) -{ - return v3f( - random_f32(min.X, max.X), - random_f32(min.Y, max.Y), - random_f32(min.Z, max.Z)); -} - -/* Particle */ @@ -59,25 +42,69 @@ Particle::Particle( LocalPlayer *player, ClientEnvironment *env, const ParticleParameters &p, - video::ITexture *texture, + const ClientTexRef& texture, v2f texpos, v2f texsize, video::SColor color ): scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(), - ((Client *)gamedef)->getSceneManager()) + ((Client *)gamedef)->getSceneManager()), + m_texture(texture) { // Misc m_gamedef = gamedef; m_env = env; + // translate blend modes to GL blend functions + video::E_BLEND_FACTOR bfsrc, bfdst; + video::E_BLEND_OPERATION blendop; + const auto blendmode = texture.tex != nullptr + ? texture.tex -> blendmode + : ParticleParamTypes::BlendMode::alpha; + + switch (blendmode) { + case ParticleParamTypes::BlendMode::add: + bfsrc = video::EBF_SRC_ALPHA; + bfdst = video::EBF_DST_ALPHA; + blendop = video::EBO_ADD; + break; + + case ParticleParamTypes::BlendMode::sub: + bfsrc = video::EBF_SRC_ALPHA; + bfdst = video::EBF_DST_ALPHA; + blendop = video::EBO_REVSUBTRACT; + break; + + case ParticleParamTypes::BlendMode::screen: + bfsrc = video::EBF_ONE; + bfdst = video::EBF_ONE_MINUS_SRC_COLOR; + blendop = video::EBO_ADD; + break; + + default: // includes ParticleParamTypes::BlendMode::alpha + bfsrc = video::EBF_SRC_ALPHA; + bfdst = video::EBF_ONE_MINUS_SRC_ALPHA; + blendop = video::EBO_ADD; + break; + } + // Texture m_material.setFlag(video::EMF_LIGHTING, false); m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); m_material.setFlag(video::EMF_BILINEAR_FILTER, false); m_material.setFlag(video::EMF_FOG_ENABLE, true); - m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - m_material.setTexture(0, texture); + + // correctly render layered transparent particles -- see #10398 + m_material.setFlag(video::EMF_ZWRITE_ENABLE, true); + + // enable alpha blending and set blend mode + m_material.MaterialType = video::EMT_ONETEXTURE_BLEND; + m_material.MaterialTypeParam = video::pack_textureBlendFunc( + bfsrc, bfdst, + video::EMFN_MODULATE_1X, + video::EAS_TEXTURE | video::EAS_VERTEX_COLOR); + m_material.BlendOperation = blendop; + m_material.setTexture(0, m_texture.ref); m_texpos = texpos; m_texsize = texsize; m_animation = p.animation; @@ -90,6 +117,9 @@ Particle::Particle( m_pos = p.pos; m_velocity = p.vel; m_acceleration = p.acc; + m_drag = p.drag; + m_jitter = p.jitter; + m_bounce = p.bounce; m_expiration = p.expirationtime; m_player = player; m_size = p.size; @@ -98,6 +128,8 @@ Particle::Particle( m_object_collision = p.object_collision; m_vertical = p.vertical; m_glow = p.glow; + m_alpha = 0; + m_parent = nullptr; // Irrlicht stuff const float c = p.size / 2; @@ -111,6 +143,14 @@ Particle::Particle( updateVertices(); } +Particle::~Particle() +{ + /* if our textures aren't owned by a particlespawner, we need to clean + * them up ourselves when the particle dies */ + if (m_parent == nullptr) + delete m_texture.tex; +} + void Particle::OnRegisterSceneNode() { if (IsVisible) @@ -134,6 +174,12 @@ void Particle::render() void Particle::step(float dtime) { m_time += dtime; + + // apply drag (not handled by collisionMoveSimple) and brownian motion + v3f av = vecAbsolute(m_velocity); + av -= av * (m_drag * dtime); + m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime; + if (m_collisiondetection) { aabb3f box = m_collisionbox; v3f p_pos = m_pos * BS; @@ -141,17 +187,41 @@ void Particle::step(float dtime) collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f, box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr, m_object_collision); - if (m_collision_removal && r.collides) { - // force expiration of the particle - m_expiration = -1.0; + + f32 bounciness = m_bounce.pickWithin(); + if (r.collides && (m_collision_removal || bounciness > 0)) { + if (m_collision_removal) { + // force expiration of the particle + m_expiration = -1.0f; + } else if (bounciness > 0) { + /* cheap way to get a decent bounce effect is to only invert the + * largest component of the velocity vector, so e.g. you don't + * have a rock immediately bounce back in your face when you try + * to skip it across the water (as would happen if we simply + * downscaled and negated the velocity vector). this means + * bounciness will work properly for cubic objects, but meshes + * with diagonal angles and entities will not yield the correct + * visual. this is probably unavoidable */ + if (av.Y > av.X && av.Y > av.Z) { + m_velocity.Y = -(m_velocity.Y * bounciness); + } else if (av.X > av.Y && av.X > av.Z) { + m_velocity.X = -(m_velocity.X * bounciness); + } else if (av.Z > av.Y && av.Z > av.X) { + m_velocity.Z = -(m_velocity.Z * bounciness); + } else { // well now we're in a bit of a pickle + m_velocity = -(m_velocity * bounciness); + } + } } else { - m_pos = p_pos / BS; m_velocity = p_velocity / BS; } + m_pos = p_pos / BS; } else { + // apply acceleration m_velocity += m_acceleration * dtime; m_pos += m_velocity * dtime; } + if (m_animation.type != TAT_NONE) { m_animation_time += dtime; int frame_length_i, frame_count; @@ -165,11 +235,21 @@ void Particle::step(float dtime) } } + // animate particle alpha in accordance with settings + if (m_texture.tex != nullptr) + m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f)); + else + m_alpha = 1.f; + // Update lighting updateLight(); // Update model updateVertices(); + + // Update position -- see #10398 + v3s16 camera_offset = m_env->getCameraOffset(); + setPosition(m_pos*BS - intToFloat(camera_offset, BS)); } void Particle::updateLight() @@ -189,7 +269,7 @@ void Particle::updateLight() light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); u8 m_light = decode_light(light + m_glow); - m_color.set(255, + m_color.set(m_alpha*255, m_light * m_base_color.getRed() / 255, m_light * m_base_color.getGreen() / 255, m_light * m_base_color.getBlue() / 255); @@ -198,6 +278,12 @@ void Particle::updateLight() void Particle::updateVertices() { f32 tx0, tx1, ty0, ty1; + v2f scale; + + if (m_texture.tex != nullptr) + scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1)); + else + scale = v2f(1.f, 1.f); if (m_animation.type != TAT_NONE) { const v2u32 texsize = m_material.getTexture(0)->getSize(); @@ -218,16 +304,24 @@ void Particle::updateVertices() ty1 = m_texpos.Y + m_texsize.Y; } - m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2, + auto half = m_size * .5f, + hx = half * scale.X, + hy = half * scale.Y; + m_vertices[0] = video::S3DVertex(-hx, -hy, 0, 0, 0, 0, m_color, tx0, ty1); - m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2, + m_vertices[1] = video::S3DVertex(hx, -hy, 0, 0, 0, 0, m_color, tx1, ty1); - m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2, + m_vertices[2] = video::S3DVertex(hx, hy, 0, 0, 0, 0, m_color, tx1, ty0); - m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2, + m_vertices[3] = video::S3DVertex(-hx, hy, 0, 0, 0, 0, m_color, tx0, ty0); - v3s16 camera_offset = m_env->getCameraOffset(); + + // see #10398 + // v3s16 camera_offset = m_env->getCameraOffset(); + // particle position is now handled by step() + m_box.reset(v3f()); + for (video::S3DVertex &vertex : m_vertices) { if (m_vertical) { v3f ppos = m_player->getPosition()/BS; @@ -238,7 +332,6 @@ void Particle::updateVertices() vertex.Pos.rotateXZBy(m_player->getYaw()); } m_box.addInternalPoint(vertex.Pos); - vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS); } } @@ -251,7 +344,8 @@ ParticleSpawner::ParticleSpawner( LocalPlayer *player, const ParticleSpawnerParameters &p, u16 attached_id, - video::ITexture *texture, + std::unique_ptr<ClientTexture[]>& texpool, + size_t texcount, ParticleManager *p_manager ): m_particlemanager(p_manager), p(p) @@ -259,21 +353,66 @@ ParticleSpawner::ParticleSpawner( m_gamedef = gamedef; m_player = player; m_attached_id = attached_id; - m_texture = texture; + m_texpool = std::move(texpool); + m_texcount = texcount; m_time = 0; + m_active = 0; + m_dying = false; m_spawntimes.reserve(p.amount + 1); for (u16 i = 0; i <= p.amount; i++) { - float spawntime = rand() / (float)RAND_MAX * p.time; + float spawntime = myrand_float() * p.time; m_spawntimes.push_back(spawntime); } + + size_t max_particles = 0; // maximum number of particles likely to be visible at any given time + if (p.time != 0) { + auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min); + max_particles = p.amount / maxGenerations; + } else { + auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max); + max_particles = p.amount * longestLife; + } + + p_manager->reserveParticleSpace(max_particles * 1.2); +} + +namespace { + GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) { + if (id == 0) + return nullptr; + return env->getGenericCAO(id); + } } void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, const core::matrix4 *attached_absolute_pos_rot_matrix) { + float fac = 0; + if (p.time != 0) { // ensure safety from divide-by-zeroes + fac = m_time / (p.time+0.1f); + } + + auto r_pos = p.pos.blend(fac); + auto r_vel = p.vel.blend(fac); + auto r_acc = p.acc.blend(fac); + auto r_drag = p.drag.blend(fac); + auto r_radius = p.radius.blend(fac); + auto r_jitter = p.jitter.blend(fac); + auto r_bounce = p.bounce.blend(fac); + v3f attractor_origin = p.attractor_origin.blend(fac); + v3f attractor_direction = p.attractor_direction.blend(fac); + auto attractor_obj = findObjectByID(env, p.attractor_attachment); + auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment); + + auto r_exp = p.exptime.blend(fac); + auto r_size = p.size.blend(fac); + auto r_attract = p.attract.blend(fac); + auto attract = r_attract.pickWithin(); + v3f ppos = m_player->getPosition() / BS; - v3f pos = random_v3f(p.minpos, p.maxpos); + v3f pos = r_pos.pickWithin(); + v3f sphere_radius = r_radius.pickWithin(); // Need to apply this first or the following check // will be wrong for attached spawners @@ -287,15 +426,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, pos.Z += camera_offset.Z; } - if (pos.getDistanceFrom(ppos) > radius) + if (pos.getDistanceFromSQ(ppos) > radius*radius) return; // Parameters for the single particle we're about to spawn ParticleParameters pp; pp.pos = pos; - pp.vel = random_v3f(p.minvel, p.maxvel); - pp.acc = random_v3f(p.minacc, p.maxacc); + pp.vel = r_vel.pickWithin(); + pp.acc = r_acc.pickWithin(); + pp.drag = r_drag.pickWithin(); + pp.jitter = r_jitter; + pp.bounce = r_bounce; if (attached_absolute_pos_rot_matrix) { // Apply attachment rotation @@ -303,30 +445,137 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, attached_absolute_pos_rot_matrix->rotateVect(pp.acc); } - pp.expirationtime = random_f32(p.minexptime, p.maxexptime); + if (attractor_obj) + attractor_origin += attractor_obj->getPosition() / BS; + if (attractor_direction_obj) { + auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix(); + if (attractor_absolute_pos_rot_matrix) + attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction); + } + + pp.expirationtime = r_exp.pickWithin(); + + if (sphere_radius != v3f()) { + f32 l = sphere_radius.getLength(); + v3f mag = sphere_radius; + mag.normalize(); + + v3f ofs = v3f(l,0,0); + ofs.rotateXZBy(myrand_range(0.f,360.f)); + ofs.rotateYZBy(myrand_range(0.f,360.f)); + ofs.rotateXYBy(myrand_range(0.f,360.f)); + + pp.pos += ofs * mag; + } + + if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) { + v3f dir; + f32 dist = 0; /* =0 necessary to silence warning */ + switch (p.attractor_kind) { + case ParticleParamTypes::AttractorKind::none: + break; + + case ParticleParamTypes::AttractorKind::point: { + dist = pp.pos.getDistanceFrom(attractor_origin); + dir = pp.pos - attractor_origin; + dir.normalize(); + break; + } + + case ParticleParamTypes::AttractorKind::line: { + // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700 + const auto& lorigin = attractor_origin; + v3f ldir = attractor_direction; + ldir.normalize(); + auto origin_to_point = pp.pos - lorigin; + auto scalar_projection = origin_to_point.dotProduct(ldir); + auto point_on_line = lorigin + (ldir * scalar_projection); + + dist = pp.pos.getDistanceFrom(point_on_line); + dir = (point_on_line - pp.pos); + dir.normalize(); + dir *= -1; // flip it around so strength=1 attracts, not repulses + break; + } + + case ParticleParamTypes::AttractorKind::plane: { + // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700 + const v3f& porigin = attractor_origin; + v3f normal = attractor_direction; + normal.normalize(); + v3f point_to_origin = porigin - pp.pos; + f32 factor = normal.dotProduct(point_to_origin); + if (numericAbsolute(factor) == 0.0f) { + dir = normal; + } else { + factor = numericSign(factor); + dir = normal * factor; + } + dist = numericAbsolute(normal.dotProduct(pp.pos - porigin)); + dir *= -1; // flip it around so strength=1 attracts, not repulses + break; + } + } + + f32 speedTowards = numericAbsolute(attract) * dist; + v3f avel = dir * speedTowards; + if (attract > 0 && speedTowards > 0) { + avel *= -1; + if (p.attractor_kill) { + // make sure the particle dies after crossing the attractor threshold + f32 timeToCenter = dist / speedTowards; + if (timeToCenter < pp.expirationtime) + pp.expirationtime = timeToCenter; + } + } + pp.vel += avel; + } + p.copyCommon(pp); - video::ITexture *texture; + ClientTexRef texture; v2f texpos, texsize; video::SColor color(0xFFFFFFFF); if (p.node.getContent() != CONTENT_IGNORE) { const ContentFeatures &f = m_particlemanager->m_env->getGameDef()->ndef()->get(p.node); - if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture, + if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref, texpos, texsize, &color, p.node_tile)) return; } else { - texture = m_texture; + if (m_texcount == 0) + return; + texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]); texpos = v2f(0.0f, 0.0f); texsize = v2f(1.0f, 1.0f); + if (texture.tex->animated) + pp.animation = texture.tex->animation; + } + + // synchronize animation length with particle life if desired + if (pp.animation.type != TAT_NONE) { + if (pp.animation.type == TAT_VERTICAL_FRAMES && + pp.animation.vertical_frames.length < 0) { + auto& a = pp.animation.vertical_frames; + // we add a tiny extra value to prevent the first frame + // from flickering back on just before the particle dies + a.length = (pp.expirationtime / -a.length) + 0.1; + } else if (pp.animation.type == TAT_SHEET_2D && + pp.animation.sheet_2d.frame_length < 0) { + auto& a = pp.animation.sheet_2d; + auto frames = a.frames_w * a.frames_h; + auto runtime = (pp.expirationtime / -a.frame_length) + 0.1; + pp.animation.sheet_2d.frame_length = frames / runtime; + } } // Allow keeping default random size - if (p.maxsize > 0.0f) - pp.size = random_f32(p.minsize, p.maxsize); + if (p.size.start.max > 0.0f || p.size.end.max > 0.0f) + pp.size = r_size.pickWithin(); - m_particlemanager->addParticle(new Particle( + ++m_active; + auto pa = new Particle( m_gamedef, m_player, env, @@ -335,7 +584,9 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, texpos, texsize, color - )); + ); + pa->m_parent = this; + m_particlemanager->addParticle(pa); } void ParticleSpawner::step(float dtime, ClientEnvironment *env) @@ -348,7 +599,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) bool unloaded = false; const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr; if (m_attached_id) { - if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) { + if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) { attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix(); } else { unloaded = true; @@ -379,7 +630,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env) return; for (int i = 0; i <= p.amount; i++) { - if (rand() / (float)RAND_MAX < dtime) + if (myrand_float() < dtime) spawnParticle(env, radius, attached_absolute_pos_rot_matrix); } } @@ -408,9 +659,15 @@ void ParticleManager::stepSpawners(float dtime) { MutexAutoLock lock(m_spawner_list_lock); for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) { - if (i->second->get_expired()) { - delete i->second; - m_particle_spawners.erase(i++); + if (i->second->getExpired()) { + // the particlespawner owns the textures, so we need to make + // sure there are no active particles before we free it + if (i->second->m_active == 0) { + delete i->second; + m_particle_spawners.erase(i++); + } else { + ++i; + } } else { i->second->step(dtime, m_env); ++i; @@ -423,6 +680,10 @@ void ParticleManager::stepParticles(float dtime) MutexAutoLock lock(m_particle_list_lock); for (auto i = m_particles.begin(); i != m_particles.end();) { if ((*i)->get_expired()) { + if ((*i)->m_parent) { + assert((*i)->m_parent->m_active != 0); + --(*i)->m_parent->m_active; + } (*i)->remove(); delete *i; i = m_particles.erase(i); @@ -464,13 +725,29 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, const ParticleSpawnerParameters &p = *event->add_particlespawner.p; - video::ITexture *texture = - client->tsrc()->getTextureForMesh(p.texture); + // texture pool + std::unique_ptr<ClientTexture[]> texpool = nullptr; + size_t txpsz = 0; + if (!p.texpool.empty()) { + txpsz = p.texpool.size(); + texpool = decltype(texpool)(new ClientTexture [txpsz]); + + for (size_t i = 0; i < txpsz; ++i) { + texpool[i] = ClientTexture(p.texpool[i], client->tsrc()); + } + } else { + // no texpool in use, use fallback texture + txpsz = 1; + texpool = decltype(texpool)(new ClientTexture[1] { + ClientTexture(p.texture, client->tsrc()) + }); + } auto toadd = new ParticleSpawner(client, player, p, event->add_particlespawner.attached_id, - texture, + texpool, + txpsz, this); addParticleSpawner(event->add_particlespawner.id, toadd); @@ -481,7 +758,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, case CE_SPAWN_PARTICLE: { ParticleParameters &p = *event->spawn_particle; - video::ITexture *texture; + ClientTexRef texture; v2f texpos, texsize; video::SColor color(0xFFFFFFFF); @@ -489,11 +766,15 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, if (p.node.getContent() != CONTENT_IGNORE) { const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node); - if (!getNodeParticleParams(p.node, f, p, &texture, texpos, - texsize, &color, p.node_tile)) - texture = nullptr; + getNodeParticleParams(p.node, f, p, &texture.ref, texpos, + texsize, &color, p.node_tile); } else { - texture = client->tsrc()->getTextureForMesh(p.texture); + /* with no particlespawner to own the texture, we need + * to save it on the heap. it will be freed when the + * particle is destroyed */ + auto texstore = new ClientTexture(p.texture, client->tsrc()); + + texture = ClientTexRef(*texstore); texpos = v2f(0.0f, 0.0f); texsize = v2f(1.0f, 1.0f); } @@ -502,7 +783,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, if (oldsize > 0.0f) p.size = oldsize; - if (texture) { + if (texture.ref) { Particle *toadd = new Particle(client, player, m_env, p, texture, texpos, texsize, color); @@ -529,7 +810,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n, if (tilenum > 0 && tilenum <= 6) texid = tilenum - 1; else - texid = rand() % 6; + texid = myrand_range(0,5); const TileLayer &tile = f.tiles[texid].layers[0]; p.animation.type = TAT_NONE; @@ -539,13 +820,13 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n, else *texture = tile.texture; - float size = (rand() % 8) / 64.0f; + float size = (myrand_range(0,8)) / 64.0f; p.size = BS * size; if (tile.scale) size /= tile.scale; texsize = v2f(size * 2.0f, size * 2.0f); - texpos.X = (rand() % 64) / 64.0f - texsize.X; - texpos.Y = (rand() % 64) / 64.0f - texsize.Y; + texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X; + texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y; if (tile.has_color) *color = tile.color; @@ -577,20 +858,20 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f) { ParticleParameters p; - video::ITexture *texture; + video::ITexture *ref = nullptr; v2f texpos, texsize; video::SColor color; - if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color)) + if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color)) return; - p.expirationtime = (rand() % 100) / 100.0f; + p.expirationtime = myrand_range(0, 100) / 100.0f; // Physics p.vel = v3f( - (rand() % 150) / 50.0f - 1.5f, - (rand() % 150) / 50.0f, - (rand() % 150) / 50.0f - 1.5f + myrand_range(-1.5f,1.5f), + myrand_range(0.f,3.f), + myrand_range(-1.5f,1.5f) ); p.acc = v3f( 0.0f, @@ -598,9 +879,9 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, 0.0f ); p.pos = v3f( - (f32)pos.X + (rand() % 100) / 200.0f - 0.25f, - (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f, - (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f + (f32)pos.X + myrand_range(0.f, .5f) - .25f, + (f32)pos.Y + myrand_range(0.f, .5f) - .25f, + (f32)pos.Z + myrand_range(0.f, .5f) - .25f ); Particle *toadd = new Particle( @@ -608,7 +889,7 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, player, m_env, p, - texture, + ClientTexRef(ref), texpos, texsize, color); @@ -616,6 +897,12 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, addParticle(toadd); } +void ParticleManager::reserveParticleSpace(size_t max_estimate) +{ + MutexAutoLock lock(m_particle_list_lock); + m_particles.reserve(m_particles.size() + max_estimate); +} + void ParticleManager::addParticle(Particle *toadd) { MutexAutoLock lock(m_particle_list_lock); @@ -634,7 +921,6 @@ void ParticleManager::deleteParticleSpawner(u64 id) MutexAutoLock lock(m_spawner_list_lock); auto it = m_particle_spawners.find(id); if (it != m_particle_spawners.end()) { - delete it->second; - m_particle_spawners.erase(it); + it->second->setDying(); } } diff --git a/src/client/particles.h b/src/client/particles.h index 2011f0262..0818b796b 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -31,20 +31,51 @@ class ClientEnvironment; struct MapNode; struct ContentFeatures; +struct ClientTexture +{ + /* per-spawner structure used to store the ParticleTexture structs + * that spawned particles will refer to through ClientTexRef */ + ParticleTexture tex; + video::ITexture *ref = nullptr; + + ClientTexture() = default; + ClientTexture(const ServerParticleTexture& p, ITextureSource *t): + tex(p), + ref(t->getTextureForMesh(p.string)) {}; +}; + +struct ClientTexRef +{ + /* per-particle structure used to avoid massively duplicating the + * fairly large ParticleTexture struct */ + ParticleTexture* tex = nullptr; + video::ITexture* ref = nullptr; + ClientTexRef() = default; + + /* constructor used by particles spawned from a spawner */ + ClientTexRef(ClientTexture& t): + tex(&t.tex), ref(t.ref) {}; + + /* constructor used for node particles */ + ClientTexRef(decltype(ref) tp): ref(tp) {}; +}; + +class ParticleSpawner; + class Particle : public scene::ISceneNode { - public: +public: Particle( - IGameDef* gamedef, + IGameDef *gamedef, LocalPlayer *player, ClientEnvironment *env, const ParticleParameters &p, - video::ITexture *texture, + const ClientTexRef &texture, v2f texpos, v2f texsize, video::SColor color ); - ~Particle() = default; + ~Particle(); virtual const aabb3f &getBoundingBox() const { @@ -69,9 +100,12 @@ class Particle : public scene::ISceneNode bool get_expired () { return m_expiration < m_time; } + ParticleSpawner *m_parent; + private: void updateLight(); void updateVertices(); + void setVertexAlpha(float a); video::S3DVertex m_vertices[4]; float m_time = 0.0f; @@ -81,14 +115,19 @@ private: IGameDef *m_gamedef; aabb3f m_box; aabb3f m_collisionbox; + ClientTexRef m_texture; video::SMaterial m_material; v2f m_texpos; v2f m_texsize; v3f m_pos; v3f m_velocity; v3f m_acceleration; + v3f m_drag; + ParticleParamTypes::v3fRange m_jitter; + ParticleParamTypes::f32Range m_bounce; LocalPlayer *m_player; float m_size; + //! Color without lighting video::SColor m_base_color; //! Final rendered color @@ -102,24 +141,27 @@ private: float m_animation_time = 0.0f; int m_animation_frame = 0; u8 m_glow; + float m_alpha = 0.0f; }; class ParticleSpawner { public: - ParticleSpawner(IGameDef* gamedef, + ParticleSpawner(IGameDef *gamedef, LocalPlayer *player, const ParticleSpawnerParameters &p, u16 attached_id, - video::ITexture *texture, + std::unique_ptr<ClientTexture[]> &texpool, + size_t texcount, ParticleManager* p_manager); - ~ParticleSpawner() = default; - void step(float dtime, ClientEnvironment *env); - bool get_expired () - { return p.amount <= 0 && p.time != 0; } + size_t m_active; + + bool getExpired() const + { return m_dying || (p.amount <= 0 && p.time != 0); } + void setDying() { m_dying = true; } private: void spawnParticle(ClientEnvironment *env, float radius, @@ -127,10 +169,12 @@ private: ParticleManager *m_particlemanager; float m_time; + bool m_dying; IGameDef *m_gamedef; LocalPlayer *m_player; ParticleSpawnerParameters p; - video::ITexture *m_texture; + std::unique_ptr<ClientTexture[]> m_texpool; + size_t m_texcount; std::vector<float> m_spawntimes; u16 m_attached_id; }; @@ -156,6 +200,8 @@ public: void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f); + void reserveParticleSpace(size_t max_estimate); + /** * This function is only used by client particle spawners * diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 44ef1c98c..ca5d3c614 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -35,8 +35,15 @@ RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud screensize = driver->getScreenSize(); virtual_size = screensize; + // disable if unsupported + if (g_settings->getBool("enable_dynamic_shadows") && ( + g_settings->get("video_driver") != "opengl" || + !g_settings->getBool("enable_shaders"))) { + g_settings->setBool("enable_dynamic_shadows", false); + } + if (g_settings->getBool("enable_shaders") && - false && g_settings->getBool("enable_dynamic_shadows")) { + g_settings->getBool("enable_dynamic_shadows")) { shadow_renderer = new ShadowRenderer(device, client); } } @@ -76,8 +83,11 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_wield_tool = _draw_wield_tool; draw_crosshair = _draw_crosshair; - if (shadow_renderer) + if (shadow_renderer) { + // This is necessary to render shadows for animations correctly + smgr->getRootSceneNode()->OnAnimate(device->getTimer()->getTime()); shadow_renderer->update(); + } beforeDraw(); drawAll(); @@ -103,7 +113,7 @@ void RenderingCore::drawHUD() if (show_hud) { if (draw_crosshair) hud->drawCrosshair(); - + hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex()); hud->drawLuaElements(camera->getOffset()); camera->drawNametags(); diff --git a/src/client/render/stereo.cpp b/src/client/render/stereo.cpp index 967b5a78f..0f54e166e 100644 --- a/src/client/render/stereo.cpp +++ b/src/client/render/stereo.cpp @@ -27,7 +27,7 @@ RenderingCoreStereo::RenderingCoreStereo( IrrlichtDevice *_device, Client *_client, Hud *_hud) : RenderingCore(_device, _client, _hud) { - eye_offset = BS * g_settings->getFloat("3d_paralax_strength"); + eye_offset = BS * g_settings->getFloat("3d_paralax_strength", -0.087f, 0.087f); } void RenderingCoreStereo::beforeDraw() diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 723865db4..9698b63bb 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -86,8 +86,12 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // Resolution selection bool fullscreen = g_settings->getBool("fullscreen"); - u16 screen_w = g_settings->getU16("screen_w"); - u16 screen_h = g_settings->getU16("screen_h"); +#ifdef __ANDROID__ + u16 screen_w = 0, screen_h = 0; +#else + u16 screen_w = std::max<u16>(g_settings->getU16("screen_w"), 1); + u16 screen_h = std::max<u16>(g_settings->getU16("screen_h"), 1); +#endif // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); @@ -116,7 +120,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) } SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - if (g_logger.getTraceEnabled()) + if (tracestream) params.LoggingLevel = irr::ELL_DEBUG; params.DriverType = driverType; params.WindowSize = core::dimension2d<u32>(screen_w, screen_h); @@ -598,7 +602,7 @@ static float calcDisplayDensity() float RenderingEngine::getDisplayDensity() { static float cached_display_density = calcDisplayDensity(); - return cached_display_density * g_settings->getFloat("display_density_factor"); + return std::max(cached_display_density * g_settings->getFloat("display_density_factor"), 0.5f); } #elif defined(_WIN32) @@ -626,37 +630,23 @@ float RenderingEngine::getDisplayDensity() display_density = calcDisplayDensity(get_video_driver()); cached = true; } - return display_density * g_settings->getFloat("display_density_factor"); + return std::max(display_density * g_settings->getFloat("display_density_factor"), 0.5f); } #else float RenderingEngine::getDisplayDensity() { - return (g_settings->getFloat("screen_dpi") / 96.0) * g_settings->getFloat("display_density_factor"); + return std::max(g_settings->getFloat("screen_dpi") / 96.0f * + g_settings->getFloat("display_density_factor"), 0.5f); } #endif -v2u32 RenderingEngine::getDisplaySize() -{ - IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL); - - core::dimension2d<u32> deskres = - nulldevice->getVideoModeList()->getDesktopResolution(); - nulldevice->drop(); - - return deskres; -} - #else // __ANDROID__ float RenderingEngine::getDisplayDensity() { return porting::getDisplayDensity(); } -v2u32 RenderingEngine::getDisplaySize() -{ - return porting::getDisplaySize(); -} #endif // __ANDROID__ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index a0ddb0d9a..38420010f 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -55,7 +55,6 @@ public: static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type); static float getDisplayDensity(); - static v2u32 getDisplaySize(); bool setupTopLevelWindow(const std::string &name); void setupTopLevelXorgWindow(const std::string &name); @@ -123,8 +122,8 @@ public: // FIXME: this is still global when it shouldn't be static ShadowRenderer *get_shadow_renderer() { - //if (s_singleton && s_singleton->core) - // return s_singleton->core->get_shadow_renderer(); + if (s_singleton && s_singleton->core) + return s_singleton->core->get_shadow_renderer(); return nullptr; } static std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers(); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index c04a25862..009a4b3d7 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -40,20 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #include "config.h" -#if ENABLE_GLES -#ifdef _IRR_COMPILE_WITH_OGLES1_ -#include <GLES/gl.h> -#else -#include <GLES2/gl2.h> -#endif -#else -#ifndef __APPLE__ -#include <GL/gl.h> -#else -#define GL_SILENCE_DEPRECATION -#include <OpenGL/gl.h> -#endif -#endif +#include <mt_opengl.h> /* A cache from shader name to shader path @@ -223,17 +210,24 @@ public: class MainShaderConstantSetter : public IShaderConstantSetter { - CachedVertexShaderSetting<float, 16> m_world_view_proj; - CachedVertexShaderSetting<float, 16> m_world; + CachedVertexShaderSetting<f32, 16> m_world_view_proj; + CachedVertexShaderSetting<f32, 16> m_world; // Shadow-related - CachedPixelShaderSetting<float, 16> m_shadow_view_proj; - CachedPixelShaderSetting<float, 3> m_light_direction; - CachedPixelShaderSetting<float> m_texture_res; - CachedPixelShaderSetting<float> m_shadow_strength; - CachedPixelShaderSetting<float> m_time_of_day; - CachedPixelShaderSetting<float> m_shadowfar; + CachedPixelShaderSetting<f32, 16> m_shadow_view_proj; + CachedPixelShaderSetting<f32, 3> m_light_direction; + CachedPixelShaderSetting<f32> m_texture_res; + CachedPixelShaderSetting<f32> m_shadow_strength; + CachedPixelShaderSetting<f32> m_time_of_day; + CachedPixelShaderSetting<f32> m_shadowfar; + CachedPixelShaderSetting<f32, 4> m_camera_pos; CachedPixelShaderSetting<s32> m_shadow_texture; + CachedVertexShaderSetting<f32> m_perspective_bias0_vertex; + CachedPixelShaderSetting<f32> m_perspective_bias0_pixel; + CachedVertexShaderSetting<f32> m_perspective_bias1_vertex; + CachedPixelShaderSetting<f32> m_perspective_bias1_pixel; + CachedVertexShaderSetting<f32> m_perspective_zbias_vertex; + CachedPixelShaderSetting<f32> m_perspective_zbias_pixel; #if ENABLE_GLES // Modelview matrix @@ -248,18 +242,25 @@ public: MainShaderConstantSetter() : m_world_view_proj("mWorldViewProj") , m_world("mWorld") -#if ENABLE_GLES - , m_world_view("mWorldView") - , m_texture("mTexture") - , m_normal("mNormal") -#endif , m_shadow_view_proj("m_ShadowViewProj") , m_light_direction("v_LightDirection") , m_texture_res("f_textureresolution") , m_shadow_strength("f_shadow_strength") , m_time_of_day("f_timeofday") , m_shadowfar("f_shadowfar") + , m_camera_pos("CameraPos") , m_shadow_texture("ShadowMapSampler") + , m_perspective_bias0_vertex("xyPerspectiveBias0") + , m_perspective_bias0_pixel("xyPerspectiveBias0") + , m_perspective_bias1_vertex("xyPerspectiveBias1") + , m_perspective_bias1_pixel("xyPerspectiveBias1") + , m_perspective_zbias_vertex("zPerspectiveBias") + , m_perspective_zbias_pixel("zPerspectiveBias") +#if ENABLE_GLES + , m_world_view("mWorldView") + , m_texture("mTexture") + , m_normal("mNormal") +#endif {} ~MainShaderConstantSetter() = default; @@ -306,26 +307,40 @@ public: shadowViewProj *= light.getViewMatrix(); m_shadow_view_proj.set(shadowViewProj.pointer(), services); - float v_LightDirection[3]; + f32 v_LightDirection[3]; light.getDirection().getAs3Values(v_LightDirection); m_light_direction.set(v_LightDirection, services); - float TextureResolution = light.getMapResolution(); + f32 TextureResolution = light.getMapResolution(); m_texture_res.set(&TextureResolution, services); - float ShadowStrength = shadow->getShadowStrength(); + f32 ShadowStrength = shadow->getShadowStrength(); m_shadow_strength.set(&ShadowStrength, services); - float timeOfDay = shadow->getTimeOfDay(); + f32 timeOfDay = shadow->getTimeOfDay(); m_time_of_day.set(&timeOfDay, services); - float shadowFar = shadow->getMaxShadowFar(); + f32 shadowFar = shadow->getMaxShadowFar(); m_shadowfar.set(&shadowFar, services); + f32 cam_pos[4]; + shadowViewProj.transformVect(cam_pos, light.getPlayerPos()); + m_camera_pos.set(cam_pos, services); + // I dont like using this hardcoded value. maybe something like // MAX_TEXTURE - 1 or somthing like that?? s32 TextureLayerID = 3; m_shadow_texture.set(&TextureLayerID, services); + + f32 bias0 = shadow->getPerspectiveBiasXY(); + m_perspective_bias0_vertex.set(&bias0, services); + m_perspective_bias0_pixel.set(&bias0, services); + f32 bias1 = 1.0f - bias0 + 1e-5f; + m_perspective_bias1_vertex.set(&bias1, services); + m_perspective_bias1_pixel.set(&bias1, services); + f32 zbias = shadow->getPerspectiveBiasZ(); + m_perspective_zbias_vertex.set(&zbias, services); + m_perspective_zbias_pixel.set(&zbias, services); } } }; @@ -667,13 +682,19 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, )"; } + // Since this is the first time we're using the GL bindings be extra careful. + // This should be removed before 5.6.0 or similar. + if (!GL.GetString) { + errorstream << "OpenGL procedures were not loaded correctly, " + "please open a bug report with details about your platform/OS." << std::endl; + abort(); + } + bool use_discard = use_gles; -#ifdef __unix__ // For renderers that should use discard instead of GL_ALPHA_TEST - const char* gl_renderer = (const char*)glGetString(GL_RENDERER); - if (strstr(gl_renderer, "GC7000")) + const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER)); + if (strstr(renderer, "GC7000")) use_discard = true; -#endif if (use_discard) { if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) shaders_header << "#define USE_DISCARD 1\n"; @@ -733,7 +754,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n"; - if (false && g_settings->getBool("enable_dynamic_shadows")) { + if (g_settings->getBool("enable_dynamic_shadows")) { shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n"; if (g_settings->getBool("shadow_map_color")) shaders_header << "#define COLORED_SHADOWS 1\n"; @@ -750,6 +771,8 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; } + shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics + std::string common_header = shaders_header.str(); std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl"); diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 6ef5a4f1d..9f26ba94a 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -27,11 +27,24 @@ with this program; if not, write to the Free Software Foundation, Inc., using m4f = core::matrix4; +static v3f quantizeDirection(v3f direction, float step) +{ + + float yaw = std::atan2(direction.Z, direction.X); + float pitch = std::asin(direction.Y); // assume look is normalized + + yaw = std::floor(yaw / step) * step; + pitch = std::floor(pitch / step) * step; + + return v3f(std::cos(yaw)*std::cos(pitch), std::sin(pitch), std::sin(yaw)*std::cos(pitch)); +} + void DirectionalLight::createSplitMatrices(const Camera *cam) { - float radius; + const float DISTANCE_STEP = BS * 2.0; // 2 meters v3f newCenter; v3f look = cam->getDirection(); + look = quantizeDirection(look, M_PI / 12.0); // 15 degrees // camera view tangents float tanFovY = tanf(cam->getFovY() * 0.5f); @@ -42,42 +55,42 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) float sfFar = adjustDist(future_frustum.zFar, cam->getFovY()); // adjusted camera positions - v3f camPos2 = cam->getPosition(); - v3f camPos = v3f(camPos2.X - cam->getOffset().X * BS, - camPos2.Y - cam->getOffset().Y * BS, - camPos2.Z - cam->getOffset().Z * BS); - camPos += look * sfNear; - camPos2 += look * sfNear; + v3f cam_pos_world = cam->getPosition(); + cam_pos_world = v3f( + floor(cam_pos_world.X / DISTANCE_STEP) * DISTANCE_STEP, + floor(cam_pos_world.Y / DISTANCE_STEP) * DISTANCE_STEP, + floor(cam_pos_world.Z / DISTANCE_STEP) * DISTANCE_STEP); + v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS, + cam_pos_world.Y - cam->getOffset().Y * BS, + cam_pos_world.Z - cam->getOffset().Z * BS); + cam_pos_scene += look * sfNear; + cam_pos_world += look * sfNear; // center point of light frustum - float end = sfNear + sfFar; - newCenter = camPos + look * (sfNear + 0.05f * end); - v3f world_center = camPos2 + look * (sfNear + 0.05f * end); + v3f center_scene = cam_pos_scene + look * 0.35 * (sfFar - sfNear); + v3f center_world = cam_pos_world + look * 0.35 * (sfFar - sfNear); // Create a vector to the frustum far corner const v3f &viewUp = cam->getCameraNode()->getUpVector(); v3f viewRight = look.crossProduct(viewUp); - v3f farCorner = look + viewRight * tanFovX + viewUp * tanFovY; + v3f farCorner = (look + viewRight * tanFovX + viewUp * tanFovY).normalize(); // Compute the frustumBoundingSphere radius - v3f boundVec = (camPos + farCorner * sfFar) - newCenter; - radius = boundVec.getLength() * 2.0f; - // boundVec.getLength(); - float vvolume = radius * 2.0f; - - v3f frustumCenter = newCenter; - // probar radius multipliacdor en funcion del I, a menor I mas multiplicador - v3f eye_displacement = direction * vvolume; + v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene; + float radius = boundVec.getLength(); + float length = radius * 3.0f; + v3f eye_displacement = quantizeDirection(direction, M_PI / 2880 /*15 seconds*/) * length; // we must compute the viewmat with the position - the camera offset // but the future_frustum position must be the actual world position - v3f eye = frustumCenter - eye_displacement; - future_frustum.position = world_center - eye_displacement; - future_frustum.length = vvolume; - future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, frustumCenter, v3f(0.0f, 1.0f, 0.0f)); - future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(future_frustum.length, - future_frustum.length, -future_frustum.length, - future_frustum.length,false); + v3f eye = center_scene - eye_displacement; + future_frustum.player = cam_pos_scene; + future_frustum.position = center_world - eye_displacement; + future_frustum.length = length; + future_frustum.radius = radius; + future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f)); + future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius, + 0.0f, length, false); future_frustum.camera_offset = cam->getOffset(); } @@ -95,6 +108,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo float zNear = cam->getCameraNode()->getNearValue(); float zFar = getMaxFarValue(); + if (!client->getEnv().getClientMap().getControl().range_all) + zFar = MYMIN(zFar, client->getEnv().getClientMap().getControl().wanted_range * BS); /////////////////////////////////// // update splits near and fars @@ -105,7 +120,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo createSplitMatrices(cam); // get the draw list for shadows client->getEnv().getClientMap().updateDrawListShadow( - getPosition(), getDirection(), future_frustum.length); + getPosition(), getDirection(), future_frustum.radius, future_frustum.length); should_update_map_shadow = true; dirty = true; @@ -115,6 +130,7 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo v3f rotated_offset; shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS)); shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset); + shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS); shadow_frustum.camera_offset = cam_offset; } } @@ -139,6 +155,16 @@ v3f DirectionalLight::getPosition() const return shadow_frustum.position; } +v3f DirectionalLight::getPlayerPos() const +{ + return shadow_frustum.player; +} + +v3f DirectionalLight::getFuturePlayerPos() const +{ + return future_frustum.player; +} + const m4f &DirectionalLight::getViewMatrix() const { return shadow_frustum.ViewMat; diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h index d8be66be8..6e9d96b15 100644 --- a/src/client/shadows/dynamicshadows.h +++ b/src/client/shadows/dynamicshadows.h @@ -22,18 +22,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include <matrix4.h> #include "util/basic_macros.h" +#include "constants.h" class Camera; class Client; struct shadowFrustum { - float zNear{0.0f}; - float zFar{0.0f}; - float length{0.0f}; + f32 zNear{0.0f}; + f32 zFar{0.0f}; + f32 length{0.0f}; + f32 radius{0.0f}; core::matrix4 ProjOrthMat; core::matrix4 ViewMat; v3f position; + v3f player; v3s16 camera_offset; }; @@ -56,6 +59,8 @@ public: return direction; }; v3f getPosition() const; + v3f getPlayerPos() const; + v3f getFuturePlayerPos() const; /// Gets the light's matrices. const core::matrix4 &getViewMatrix() const; @@ -64,10 +69,16 @@ public: const core::matrix4 &getFutureProjectionMatrix() const; core::matrix4 getViewProjMatrix(); - /// Gets the light's far value. + /// Gets the light's maximum far value, i.e. the shadow boundary f32 getMaxFarValue() const { - return farPlane; + return farPlane * BS; + } + + /// Gets the current far value of the light + f32 getFarValue() const + { + return shadow_frustum.zFar; } diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index a913a9290..944deb801 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include <cstring> +#include <cmath> #include "client/shadows/dynamicshadowsrender.h" #include "client/shadows/shadowsScreenQuad.h" #include "client/shadows/shadowsshadercallbacks.h" @@ -30,12 +31,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : - m_device(device), m_smgr(device->getSceneManager()), - m_driver(device->getVideoDriver()), m_client(client), m_current_frame(0) + m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()), + m_client(client), m_current_frame(0), + m_perspective_bias_xy(0.8), m_perspective_bias_z(0.5) { + (void) m_client; + + m_shadows_supported = true; // assume shadows supported. We will check actual support in initialize m_shadows_enabled = true; - m_shadow_strength = g_settings->getFloat("shadow_strength"); + m_shadow_strength_gamma = g_settings->getFloat("shadow_strength_gamma"); + if (std::isnan(m_shadow_strength_gamma)) + m_shadow_strength_gamma = 1.0f; + m_shadow_strength_gamma = core::clamp(m_shadow_strength_gamma, 0.1f, 10.0f); m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); @@ -49,27 +57,58 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : ShadowRenderer::~ShadowRenderer() { + // call to disable releases dynamically allocated resources + disable(); + if (m_shadow_depth_cb) delete m_shadow_depth_cb; + if (m_shadow_depth_entity_cb) + delete m_shadow_depth_entity_cb; + if (m_shadow_depth_trans_cb) + delete m_shadow_depth_trans_cb; if (m_shadow_mix_cb) delete m_shadow_mix_cb; m_shadow_node_array.clear(); m_light_list.clear(); +} - if (shadowMapTextureDynamicObjects) +void ShadowRenderer::disable() +{ + m_shadows_enabled = false; + if (shadowMapTextureFinal) { + m_driver->setRenderTarget(shadowMapTextureFinal, true, true, + video::SColor(255, 255, 255, 255)); + m_driver->setRenderTarget(0, false, false); + } + + if (shadowMapTextureDynamicObjects) { m_driver->removeTexture(shadowMapTextureDynamicObjects); + shadowMapTextureDynamicObjects = nullptr; + } - if (shadowMapTextureFinal) + if (shadowMapTextureFinal) { m_driver->removeTexture(shadowMapTextureFinal); + shadowMapTextureFinal = nullptr; + } - if (shadowMapTextureColors) + if (shadowMapTextureColors) { m_driver->removeTexture(shadowMapTextureColors); + shadowMapTextureColors = nullptr; + } - if (shadowMapClientMap) + if (shadowMapClientMap) { m_driver->removeTexture(shadowMapClientMap); + shadowMapClientMap = nullptr; + } - if (shadowMapClientMapFuture) + if (shadowMapClientMapFuture) { m_driver->removeTexture(shadowMapClientMapFuture); + shadowMapClientMapFuture = nullptr; + } + + for (auto node : m_shadow_node_array) + if (node.shadowMode & E_SHADOW_MODE::ESM_RECEIVE) + node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr); } void ShadowRenderer::initialize() @@ -77,10 +116,10 @@ void ShadowRenderer::initialize() auto *gpu = m_driver->getGPUProgrammingServices(); // we need glsl - if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { + if (m_shadows_supported && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { createShaders(); } else { - m_shadows_enabled = false; + m_shadows_supported = false; warningstream << "Shadows: GLSL Shader not supported on this system." << std::endl; @@ -94,6 +133,8 @@ void ShadowRenderer::initialize() m_texture_format_color = m_shadow_map_texture_32bit ? video::ECOLOR_FORMAT::ECF_G32R32F : video::ECOLOR_FORMAT::ECF_G16R16F; + + m_shadows_enabled &= m_shadows_supported; } @@ -118,24 +159,36 @@ size_t ShadowRenderer::getDirectionalLightCount() const f32 ShadowRenderer::getMaxShadowFar() const { if (!m_light_list.empty()) { - float wanted_range = m_client->getEnv().getClientMap().getWantedRange(); - - float zMax = m_light_list[0].getMaxFarValue() > wanted_range - ? wanted_range - : m_light_list[0].getMaxFarValue(); - return zMax * MAP_BLOCKSIZE; + float zMax = m_light_list[0].getFarValue(); + return zMax; } return 0.0f; } +void ShadowRenderer::setShadowIntensity(float shadow_intensity) +{ + m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); + if (m_shadow_strength > 1E-2) + enable(); + else + disable(); +} + void ShadowRenderer::addNodeToShadowList( scene::ISceneNode *node, E_SHADOW_MODE shadowMode) { - m_shadow_node_array.emplace_back(NodeToApply(node, shadowMode)); + if (!node) + return; + m_shadow_node_array.emplace_back(node, shadowMode); + if (shadowMode == ESM_RECEIVE || shadowMode == ESM_BOTH) + node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal); } void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node) { + if (!node) + return; + node->setMaterialTexture(TEXTURE_LAYER_SHADOW, nullptr); for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) { if (it->node == node) { it = m_shadow_node_array.erase(it); @@ -157,6 +210,7 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureDynamicObjects = getSMTexture( std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size), m_texture_format, true); + assert(shadowMapTextureDynamicObjects != nullptr); } if (!shadowMapClientMap) { @@ -165,6 +219,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMap != nullptr); } if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) { @@ -172,6 +227,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapClientMapFuture != nullptr); } if (m_shadow_map_colored && !shadowMapTextureColors) { @@ -179,6 +235,7 @@ void ShadowRenderer::updateSMTextures() std::string("shadow_colored_") + itos(m_shadow_map_texture_size), m_shadow_map_colored ? m_texture_format_color : m_texture_format, true); + assert(shadowMapTextureColors != nullptr); } // The merge all shadowmaps texture @@ -198,6 +255,11 @@ void ShadowRenderer::updateSMTextures() shadowMapTextureFinal = getSMTexture( std::string("shadowmap_final_") + itos(m_shadow_map_texture_size), frt, true); + assert(shadowMapTextureFinal != nullptr); + + for (auto &node : m_shadow_node_array) + if (node.shadowMode == ESM_RECEIVE || node.shadowMode == ESM_BOTH) + node.node->setMaterialTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal); } if (!m_shadow_node_array.empty() && !m_light_list.empty()) { @@ -205,7 +267,7 @@ void ShadowRenderer::updateSMTextures() // detect if SM should be regenerated for (DirectionalLight &light : m_light_list) { - if (light.should_update_map_shadow) { + if (light.should_update_map_shadow || m_force_update_shadow_map) { light.should_update_map_shadow = false; m_current_frame = 0; reset_sm_texture = true; @@ -219,23 +281,29 @@ void ShadowRenderer::updateSMTextures() // Update SM incrementally: for (DirectionalLight &light : m_light_list) { // Static shader values. - m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size; - m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS; - + for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) + if (cb) { + cb->MapRes = (f32)m_shadow_map_texture_size; + cb->MaxFar = (f32)m_shadow_map_max_distance * BS; + cb->PerspectiveBiasXY = getPerspectiveBiasXY(); + cb->PerspectiveBiasZ = getPerspectiveBiasZ(); + cb->CameraPos = light.getFuturePlayerPos(); + } + // set the Render Target // right now we can only render in usual RTT, not // Depth texture is available in irrlicth maybe we // should put some gl* fn here - if (m_current_frame < m_map_shadow_update_frames) { + if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) { m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true, video::SColor(255, 255, 255, 255)); renderShadowMap(shadowMapTargetTexture, light); // Render transparent part in one pass. // This is also handled in ClientMap. - if (m_current_frame == m_map_shadow_update_frames - 1) { + if (m_current_frame == m_map_shadow_update_frames - 1 || m_force_update_shadow_map) { if (m_shadow_map_colored) { m_driver->setRenderTarget(0, false, false); m_driver->setRenderTarget(shadowMapTextureColors, @@ -255,7 +323,7 @@ void ShadowRenderer::updateSMTextures() ++m_current_frame; // pass finished, swap textures and commit light changes - if (m_current_frame == m_map_shadow_update_frames) { + if (m_current_frame == m_map_shadow_update_frames || m_force_update_shadow_map) { if (shadowMapClientMapFuture != nullptr) std::swap(shadowMapClientMapFuture, shadowMapClientMap); @@ -263,6 +331,7 @@ void ShadowRenderer::updateSMTextures() for (DirectionalLight &light : m_light_list) light.commitFrustum(); } + m_force_update_shadow_map = false; } } @@ -274,12 +343,18 @@ void ShadowRenderer::update(video::ITexture *outputTarget) updateSMTextures(); + if (shadowMapTextureFinal == nullptr) { + return; + } + + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { for (DirectionalLight &light : m_light_list) { - // Static shader values. - m_shadow_depth_cb->MapRes = (f32)m_shadow_map_texture_size; - m_shadow_depth_cb->MaxFar = (f32)m_shadow_map_max_distance * BS; + // Static shader values for entities are set in updateSMTextures + // SM texture for entities is not updated incrementally and + // must by updated using current player position. + m_shadow_depth_entity_cb->CameraPos = light.getPlayerPos(); // render shadows for the n0n-map objects. m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true, @@ -311,19 +386,23 @@ void ShadowRenderer::drawDebug() /* this code just shows shadows textures in screen and in ONLY for debugging*/ #if 0 // this is debug, ignore for now. - m_driver->draw2DImage(shadowMapTextureFinal, - core::rect<s32>(0, 50, 128, 128 + 50), - core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); + if (shadowMapTextureFinal) + m_driver->draw2DImage(shadowMapTextureFinal, + core::rect<s32>(0, 50, 128, 128 + 50), + core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapClientMap, - core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128), - core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); - m_driver->draw2DImage(shadowMapTextureDynamicObjects, - core::rect<s32>(0, 128 + 50 + 128, 128, - 128 + 50 + 128 + 128), - core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize())); + if (shadowMapClientMap) + m_driver->draw2DImage(shadowMapClientMap, + core::rect<s32>(0, 50 + 128, 128, 128 + 50 + 128), + core::rect<s32>({0, 0}, shadowMapTextureFinal->getSize())); + + if (shadowMapTextureDynamicObjects) + m_driver->draw2DImage(shadowMapTextureDynamicObjects, + core::rect<s32>(0, 128 + 50 + 128, 128, + 128 + 50 + 128 + 128), + core::rect<s32>({0, 0}, shadowMapTextureDynamicObjects->getSize())); - if (m_shadow_map_colored) { + if (m_shadow_map_colored && shadowMapTextureColors) { m_driver->draw2DImage(shadowMapTextureColors, core::rect<s32>(128,128 + 50 + 128 + 128, @@ -368,10 +447,6 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, material.BackfaceCulling = false; material.FrontfaceCulling = true; - material.PolygonOffsetFactor = 4.0f; - material.PolygonOffsetDirection = video::EPO_BACK; - //material.PolygonOffsetDepthBias = 1.0f/4.0f; - //material.PolygonOffsetSlopeScale = -1.f; if (m_shadow_map_colored && pass != scene::ESNRP_SOLID) { material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader_trans; @@ -381,13 +456,13 @@ void ShadowRenderer::renderShadowMap(video::ITexture *target, material.BlendOperation = video::EBO_MIN; } - // FIXME: I don't think this is needed here - map_node->OnAnimate(m_device->getTimer()->getTime()); - m_driver->setTransform(video::ETS_WORLD, map_node->getAbsoluteTransformation()); - map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames); + int frame = m_force_update_shadow_map ? 0 : m_current_frame; + int total_frames = m_force_update_shadow_map ? 1 : m_map_shadow_update_frames; + + map_node->renderMapShadows(m_driver, material, pass, frame, total_frames); break; } } @@ -429,10 +504,6 @@ void ShadowRenderer::renderShadowObjects( current_mat.BackfaceCulling = true; current_mat.FrontfaceCulling = false; - current_mat.PolygonOffsetFactor = 1.0f/2048.0f; - current_mat.PolygonOffsetDirection = video::EPO_BACK; - //current_mat.PolygonOffsetDepthBias = 1.0 * 2.8e-6; - //current_mat.PolygonOffsetSlopeScale = -1.f; } m_driver->setTransform(video::ETS_WORLD, @@ -473,13 +544,13 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -494,7 +565,9 @@ void ShadowRenderer::createShaders() if (depth_shader == -1) { // upsi, something went wrong loading shader. delete m_shadow_depth_cb; + m_shadow_depth_cb = nullptr; m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader." << std::endl; return; } @@ -510,26 +583,30 @@ void ShadowRenderer::createShaders() if (depth_shader_entities == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } + m_shadow_depth_entity_cb = new ShadowDepthShaderCB(); depth_shader_entities = gpu->addHighLevelShaderMaterial( readShaderFile(depth_shader_vs).c_str(), "vertexMain", video::EVST_VS_1_1, readShaderFile(depth_shader_fs).c_str(), "pixelMain", - video::EPST_PS_1_2, m_shadow_depth_cb); + video::EPST_PS_1_2, m_shadow_depth_entity_cb); if (depth_shader_entities == -1) { // upsi, something went wrong loading shader. + delete m_shadow_depth_entity_cb; + m_shadow_depth_entity_cb = nullptr; m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl; return; } @@ -543,14 +620,14 @@ void ShadowRenderer::createShaders() if (mixcsm_shader == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass2_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; return; } @@ -569,7 +646,7 @@ void ShadowRenderer::createShaders() // upsi, something went wrong loading shader. delete m_shadow_mix_cb; delete m_screen_quad; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling cascade shadow mapping shader." << std::endl; return; } @@ -583,13 +660,13 @@ void ShadowRenderer::createShaders() if (m_shadow_map_colored && depth_shader_trans == -1) { std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_trans_vertex.glsl"); if (depth_shader_vs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping vs shader not found." << std::endl; return; } std::string depth_shader_fs = getShaderPath("shadow_shaders", "pass1_trans_fragment.glsl"); if (depth_shader_fs.empty()) { - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error shadow mapping fs shader not found." << std::endl; return; } @@ -604,8 +681,9 @@ void ShadowRenderer::createShaders() if (depth_shader_trans == -1) { // upsi, something went wrong loading shader. delete m_shadow_depth_trans_cb; + m_shadow_depth_trans_cb = nullptr; m_shadow_map_colored = false; - m_shadows_enabled = false; + m_shadows_supported = false; errorstream << "Error compiling colored shadow mapping shader." << std::endl; return; } @@ -622,6 +700,7 @@ std::string ShadowRenderer::readShaderFile(const std::string &path) std::string prefix; if (m_shadow_map_colored) prefix.append("#define COLORED_SHADOWS 1\n"); + prefix.append("#line 0\n"); std::string content; fs::ReadFile(path, content); diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index e4b3c3e22..bd27f6f20 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -51,6 +51,8 @@ struct NodeToApply class ShadowRenderer { public: + static const int TEXTURE_LAYER_SHADOW = 3; + ShadowRenderer(IrrlichtDevice *device, Client *client); ~ShadowRenderer(); @@ -74,6 +76,7 @@ public: void removeNodeFromShadowList(scene::ISceneNode *node); void update(video::ITexture *outputTarget = nullptr); + void setForceUpdateShadowMap() { m_force_update_shadow_map = true; } void drawDebug(); video::ITexture *get_texture() @@ -82,13 +85,17 @@ public: } - bool is_active() const { return m_shadows_enabled; } + bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; } void setTimeOfDay(float isDay) { m_time_day = isDay; }; + void setShadowIntensity(float shadow_intensity); s32 getShadowSamples() const { return m_shadow_samples; } - float getShadowStrength() const { return m_shadow_strength; } + float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; } float getTimeOfDay() const { return m_time_day; } + f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; } + f32 getPerspectiveBiasZ() { return m_perspective_bias_z; } + private: video::ITexture *getSMTexture(const std::string &shadow_map_name, video::ECOLOR_FORMAT texture_format, @@ -101,8 +108,10 @@ private: void mixShadowsQuad(); void updateSMTextures(); + void disable(); + void enable() { m_shadows_enabled = m_shadows_supported; } + // a bunch of variables - IrrlichtDevice *m_device{nullptr}; scene::ISceneManager *m_smgr{nullptr}; video::IVideoDriver *m_driver{nullptr}; Client *m_client{nullptr}; @@ -116,15 +125,20 @@ private: std::vector<NodeToApply> m_shadow_node_array; float m_shadow_strength; + float m_shadow_strength_gamma; float m_shadow_map_max_distance; float m_shadow_map_texture_size; float m_time_day{0.0f}; int m_shadow_samples; bool m_shadow_map_texture_32bit; bool m_shadows_enabled; + bool m_shadows_supported; bool m_shadow_map_colored; + bool m_force_update_shadow_map; u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */ u8 m_current_frame{0}; /* Current frame */ + f32 m_perspective_bias_xy; + f32 m_perspective_bias_z; video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F}; video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16}; @@ -140,6 +154,7 @@ private: s32 mixcsm_shader{-1}; ShadowDepthShaderCB *m_shadow_depth_cb{nullptr}; + ShadowDepthShaderCB *m_shadow_depth_entity_cb{nullptr}; ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr}; shadowScreenQuad *m_screen_quad{nullptr}; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp index 65a63f49c..b571ea939 100644 --- a/src/client/shadows/shadowsshadercallbacks.cpp +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -26,6 +26,10 @@ void ShadowDepthShaderCB::OnSetConstants( core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION); lightMVP *= driver->getTransform(video::ETS_VIEW); + + f32 cam_pos[4]; + lightMVP.transformVect(cam_pos, CameraPos); + lightMVP *= driver->getTransform(video::ETS_WORLD); m_light_mvp_setting.set(lightMVP.pointer(), services); @@ -33,4 +37,12 @@ void ShadowDepthShaderCB::OnSetConstants( m_max_far_setting.set(&MaxFar, services); s32 TextureId = 0; m_color_map_sampler_setting.set(&TextureId, services); + f32 bias0 = PerspectiveBiasXY; + m_perspective_bias0.set(&bias0, services); + f32 bias1 = 1.0f - bias0 + 1e-5f; + m_perspective_bias1.set(&bias1, services); + f32 zbias = PerspectiveBiasZ; + m_perspective_zbias.set(&zbias, services); + + m_cam_pos_setting.set(cam_pos, services); } diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h index 3549567c3..87833c0ec 100644 --- a/src/client/shadows/shadowsshadercallbacks.h +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -30,7 +30,11 @@ public: m_light_mvp_setting("LightMVP"), m_map_resolution_setting("MapResolution"), m_max_far_setting("MaxFar"), - m_color_map_sampler_setting("ColorMapSampler") + m_color_map_sampler_setting("ColorMapSampler"), + m_perspective_bias0("xyPerspectiveBias0"), + m_perspective_bias1("xyPerspectiveBias1"), + m_perspective_zbias("zPerspectiveBias"), + m_cam_pos_setting("CameraPos") {} void OnSetMaterial(const video::SMaterial &material) override {} @@ -39,10 +43,16 @@ public: s32 userData) override; f32 MaxFar{2048.0f}, MapRes{1024.0f}; + f32 PerspectiveBiasXY {0.9f}, PerspectiveBiasZ {0.5f}; + v3f CameraPos; private: CachedVertexShaderSetting<f32, 16> m_light_mvp_setting; CachedVertexShaderSetting<f32> m_map_resolution_setting; CachedVertexShaderSetting<f32> m_max_far_setting; CachedPixelShaderSetting<s32> m_color_map_sampler_setting; + CachedVertexShaderSetting<f32> m_perspective_bias0; + CachedVertexShaderSetting<f32> m_perspective_bias1; + CachedVertexShaderSetting<f32> m_perspective_zbias; + CachedVertexShaderSetting<f32, 4> m_cam_pos_setting; }; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 7fe90c6cd..ca56889b4 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -103,7 +103,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); - if (false && g_settings->getBool("enable_dynamic_shadows")) { + if (g_settings->getBool("enable_dynamic_shadows")) { float val = g_settings->getFloat("shadow_sky_body_orbit_tilt"); m_sky_body_orbit_tilt = rangelim(val, 0.0f, 60.0f); } @@ -660,9 +660,12 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) // to time 4000. float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); - float starbrightness = (0.25f - fabsf(tod)) * 20.0f; + float day_opacity = clamp(m_star_params.day_opacity, 0.0f, 1.0f); + float starbrightness = (0.25f - fabs(tod)) * 20.0f; + float alpha = clamp(starbrightness, day_opacity, 1.0f); + m_star_color = m_star_params.starcolor; - m_star_color.a *= clamp(starbrightness, 0.0f, 1.0f); + m_star_color.a *= alpha; if (m_star_color.a <= 0.0f) // Stars are only drawn when not fully transparent return; m_materials[0].DiffuseColor = m_materials[0].EmissiveColor = m_star_color.toSColor(); diff --git a/src/client/sky.h b/src/client/sky.h index 3dc057b70..cbb1186aa 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -65,6 +65,7 @@ public: } void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; } + bool getSunVisible() const { return m_sun_params.visible; } void setSunTexture(const std::string &sun_texture, const std::string &sun_tonemap, ITextureSource *tsrc); void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; } @@ -72,6 +73,7 @@ public: void setSunriseTexture(const std::string &sunglow_texture, ITextureSource* tsrc); void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; } + bool getMoonVisible() const { return m_moon_params.visible; } void setMoonTexture(const std::string &moon_texture, const std::string &moon_tonemap, ITextureSource *tsrc); void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; } @@ -80,6 +82,7 @@ public: void setStarCount(u16 star_count); void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; } void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; updateStars(); } + void setStarDayOpacity(f32 day_opacity) { m_star_params.day_opacity = day_opacity; } bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } diff --git a/src/client/sound.h b/src/client/sound.h index c1d3c0cfa..213d20831 100644 --- a/src/client/sound.h +++ b/src/client/sound.h @@ -51,10 +51,8 @@ public: // playSound functions return -1 on failure, otherwise a handle to the // sound. If name=="", call should be ignored without error. - virtual int playSound(const std::string &name, bool loop, float volume, - float fade = 0.0f, float pitch = 1.0f) = 0; - virtual int playSoundAt(const std::string &name, bool loop, float volume, v3f pos, - float pitch = 1.0f) = 0; + virtual int playSound(const SimpleSoundSpec &spec) = 0; + virtual int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) = 0; virtual void stopSound(int sound) = 0; virtual bool soundExists(int sound) = 0; virtual void updateSoundPosition(int sound, v3f pos) = 0; @@ -62,15 +60,6 @@ public: virtual float getSoundGain(int id) = 0; virtual void step(float dtime) = 0; virtual void fadeSound(int sound, float step, float gain) = 0; - - int playSound(const SimpleSoundSpec &spec, bool loop) - { - return playSound(spec.name, loop, spec.gain, spec.fade, spec.pitch); - } - int playSoundAt(const SimpleSoundSpec &spec, bool loop, const v3f &pos) - { - return playSoundAt(spec.name, loop, spec.gain, pos, spec.pitch); - } }; class DummySoundManager : public ISoundManager @@ -88,16 +77,9 @@ public: { } void setListenerGain(float gain) {} - int playSound(const std::string &name, bool loop, float volume, float fade, - float pitch) - { - return 0; - } - int playSoundAt(const std::string &name, bool loop, float volume, v3f pos, - float pitch) - { - return 0; - } + + int playSound(const SimpleSoundSpec &spec) { return -1; } + int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) { return -1; } void stopSound(int sound) {} bool soundExists(int sound) { return false; } void updateSoundPosition(int sound, v3f pos) {} diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 0eda8842b..b015aba25 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -322,7 +322,7 @@ private: OnDemandSoundFetcher *m_fetcher; ALCdevice *m_device; ALCcontext *m_context; - int m_next_id; + u16 m_last_used_id = 0; // only access within getFreeId() ! std::unordered_map<std::string, std::vector<SoundBuffer*>> m_buffers; std::unordered_map<int, PlayingSound*> m_sounds_playing; struct FadeState { @@ -342,8 +342,7 @@ public: OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher): m_fetcher(fetcher), m_device(smg->m_device.get()), - m_context(smg->m_context.get()), - m_next_id(1) + m_context(smg->m_context.get()) { infostream << "Audio: Initialized: OpenAL " << std::endl; } @@ -379,6 +378,22 @@ public: infostream << "Audio: Deinitialized." << std::endl; } + u16 getFreeId() + { + u16 startid = m_last_used_id; + while (!isFreeId(++m_last_used_id)) { + if (m_last_used_id == startid) + return 0; + } + + return m_last_used_id; + } + + inline bool isFreeId(int id) const + { + return id > 0 && m_sounds_playing.find(id) == m_sounds_playing.end(); + } + void step(float dtime) { doFades(dtime); @@ -437,7 +452,7 @@ public: << std::endl; assert(buf); PlayingSound *sound = new PlayingSound; - assert(sound); + warn_if_error(alGetError(), "before createPlayingSoundAt"); alGenSources(1, &sound->source_id); alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id); @@ -463,28 +478,17 @@ public: { assert(buf); PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch); - if(!sound) + if (!sound) return -1; - int id = m_next_id++; - m_sounds_playing[id] = sound; - return id; - } - int playSoundRawAt(SoundBuffer *buf, bool loop, float volume, const v3f &pos, - float pitch) - { - assert(buf); - PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos, pitch); - if(!sound) - return -1; - int id = m_next_id++; - m_sounds_playing[id] = sound; - return id; + int handle = getFreeId(); + m_sounds_playing[handle] = sound; + return handle; } void deleteSound(int id) { - std::unordered_map<int, PlayingSound*>::iterator i = m_sounds_playing.find(id); + auto i = m_sounds_playing.find(id); if(i == m_sounds_playing.end()) return; PlayingSound *sound = i->second; @@ -580,39 +584,46 @@ public: alListenerf(AL_GAIN, gain); } - int playSound(const std::string &name, bool loop, float volume, float fade, float pitch) + int playSound(const SimpleSoundSpec &spec) { maintain(); - if (name.empty()) + if (spec.name.empty()) return 0; - SoundBuffer *buf = getFetchBuffer(name); + SoundBuffer *buf = getFetchBuffer(spec.name); if(!buf){ - infostream << "OpenALSoundManager: \"" << name << "\" not found." + infostream << "OpenALSoundManager: \"" << spec.name << "\" not found." << std::endl; return -1; } + int handle = -1; - if (fade > 0) { - handle = playSoundRaw(buf, loop, 0.0f, pitch); - fadeSound(handle, fade, volume); + if (spec.fade > 0) { + handle = playSoundRaw(buf, spec.loop, 0.0f, spec.pitch); + fadeSound(handle, spec.fade, spec.gain); } else { - handle = playSoundRaw(buf, loop, volume, pitch); + handle = playSoundRaw(buf, spec.loop, spec.gain, spec.pitch); } return handle; } - int playSoundAt(const std::string &name, bool loop, float volume, v3f pos, float pitch) + int playSoundAt(const SimpleSoundSpec &spec, const v3f &pos) { maintain(); - if (name.empty()) + if (spec.name.empty()) return 0; - SoundBuffer *buf = getFetchBuffer(name); - if(!buf){ - infostream << "OpenALSoundManager: \"" << name << "\" not found." + SoundBuffer *buf = getFetchBuffer(spec.name); + if (!buf) { + infostream << "OpenALSoundManager: \"" << spec.name << "\" not found." << std::endl; return -1; } - return playSoundRawAt(buf, loop, volume, pos, pitch); + + PlayingSound *sound = createPlayingSoundAt(buf, spec.loop, spec.gain, pos, spec.pitch); + if (!sound) + return -1; + int handle = getFreeId(); + m_sounds_playing[handle] = sound; + return handle; } void stopSound(int sound) @@ -624,8 +635,9 @@ public: void fadeSound(int soundid, float step, float gain) { // Ignore the command if step isn't valid. - if (step == 0) + if (step == 0 || soundid < 0) return; + float current_gain = getSoundGain(soundid); step = gain - current_gain > 0 ? abs(step) : -abs(step); if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) { diff --git a/src/client/tile.cpp b/src/client/tile.cpp index aa78c50f0..87d2818b4 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1619,7 +1619,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, * textures that don't have the resources to offer high-res alternatives. */ const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; - const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1; + const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1; if (scaleto > 1) { const core::dimension2d<u32> dim = baseimg->getDimension(); diff --git a/src/client/tile.h b/src/client/tile.h index fe96cef58..e55a26e56 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -260,6 +260,17 @@ struct TileLayer && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); } + bool isTransparent() const + { + switch (material_type) { + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + return true; + } + return false; + } + // Ordered for size, please do not reorder video::ITexture *texture = nullptr; @@ -308,7 +319,8 @@ struct TileSpec for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { if (layers[layer] != other.layers[layer]) return false; - if (!layers[layer].isTileable()) + // Only non-transparent tiles can be merged into fast faces + if (layers[layer].isTransparent() || !layers[layer].isTileable()) return false; } return rotation == 0 diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 0a4cb3b86..0a89e2aa2 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -386,6 +386,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -457,6 +460,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); } + + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; } else { if (!def.inventory_image.empty()) { @@ -469,6 +476,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_colors.emplace_back(); // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + + // initialize the color + if (!m_lighting) + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -515,8 +526,9 @@ void WieldMeshSceneNode::setNodeLightColor(video::SColor color) material.EmissiveColor = color; } } - - setColor(color); + else { + setColor(color); + } } void WieldMeshSceneNode::render() @@ -541,9 +553,10 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); m_meshnode->setVisible(true); - // Add mesh to shadow caster - if (m_shadow) + if (m_shadow) { + // Add mesh to shadow caster m_shadow->addNodeToShadowList(m_meshnode); + } } void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index a4bfb8242..5733b05de 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -472,20 +472,14 @@ void RemoteClient::notifyEvent(ClientStateEvent event) { case CSE_AuthAccept: m_state = CS_AwaitingInit2; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; case CSE_Disconnect: m_state = CS_Disconnecting; break; case CSE_SetDenied: m_state = CS_Denied; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; default: myerror << "HelloSent: Invalid client state transition! " << event; @@ -561,9 +555,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; case CSE_SudoSuccess: m_state = CS_SudoMode; - if (chosen_mech == AUTH_MECHANISM_SRP) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ default: @@ -598,7 +590,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) void RemoteClient::resetChosenMech() { - if (chosen_mech == AUTH_MECHANISM_SRP) { + if (auth_data) { srp_verifier_delete((SRPVerifier *) auth_data); auth_data = nullptr; } diff --git a/src/clientiface.h b/src/clientiface.h index 04e8b8033..3e7ba4793 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/networkprotocol.h" #include "network/address.h" #include "porting.h" +#include "threading/mutex_auto_lock.h" #include <list> #include <vector> @@ -342,7 +343,7 @@ public: u8 getMinor() const { return m_version_minor; } u8 getPatch() const { return m_version_patch; } const std::string &getFullVer() const { return m_full_version; } - + void setLangCode(const std::string &code) { m_lang_code = code; } const std::string &getLangCode() const { return m_lang_code; } @@ -505,9 +506,13 @@ public: static std::string state2Name(ClientState state); protected: - //TODO find way to avoid this functions - void lock() { m_clients_mutex.lock(); } - void unlock() { m_clients_mutex.unlock(); } + class AutoLock { + public: + AutoLock(ClientInterface &iface): m_lock(iface.m_clients_mutex) {} + + private: + RecursiveMutexAutoLock m_lock; + }; RemoteClientMap& getClientList() { return m_clients; } diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index cf436d6dc..17b70e268 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -15,6 +15,8 @@ #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" #define ICON_DIR "@ICONDIR@" #cmakedefine01 RUN_IN_PLACE +#cmakedefine01 DEVELOPMENT_BUILD +#cmakedefine01 ENABLE_UPDATE_CHECKER #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL #cmakedefine01 USE_SOUND @@ -35,3 +37,4 @@ #cmakedefine01 CURSES_HAVE_NCURSESW_NCURSES_H #cmakedefine01 CURSES_HAVE_NCURSESW_CURSES_H #cmakedefine01 BUILD_UNITTESTS +#cmakedefine01 BUILD_BENCHMARKS diff --git a/src/collision.cpp b/src/collision.cpp index ccc3a058d..be135a225 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -153,7 +153,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - relbox.MinEdge.Z < 0) - ) + ) return COLLISION_AXIS_X; } } else { @@ -180,7 +180,7 @@ CollisionAxis axisAlignedCollision( (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - relbox.MinEdge.Y < 0) - ) + ) return COLLISION_AXIS_Z; } } diff --git a/src/config.h b/src/config.h index 5e1164642..a4c6c9f10 100644 --- a/src/config.h +++ b/src/config.h @@ -11,17 +11,14 @@ #if defined USE_CMAKE_CONFIG_H #include "cmake_config.h" -#elif defined (__ANDROID__) - #define PROJECT_NAME "minetest" - #define PROJECT_NAME_C "Minetest" - #define STATIC_SHAREDIR "" - #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) -#ifdef NDEBUG - #define BUILD_TYPE "Release" - #else - #define BUILD_TYPE "Debug" - #endif #else + #if defined (__ANDROID__) + #define PROJECT_NAME "minetest" + #define PROJECT_NAME_C "Minetest" + #define STATIC_SHAREDIR "" + #define ENABLE_UPDATE_CHECKER 0 + #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) + #endif #ifdef NDEBUG #define BUILD_TYPE "Release" #else diff --git a/src/content/CMakeLists.txt b/src/content/CMakeLists.txt index 6dd049418..2aefd40a4 100644 --- a/src/content/CMakeLists.txt +++ b/src/content/CMakeLists.txt @@ -1,5 +1,6 @@ set(content_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/content.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mod_configuration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subgames.cpp PARENT_SCOPE diff --git a/src/content/content.cpp b/src/content/content.cpp index 66ef83d42..e576943ff 100644 --- a/src/content/content.cpp +++ b/src/content/content.cpp @@ -96,7 +96,12 @@ void parseContentInfo(ContentSpec &spec) Settings conf; if (!conf_path.empty() && conf.readConfigFile(conf_path.c_str())) { - if (conf.exists("name")) + if (conf.exists("title")) + spec.title = conf.get("title"); + else if (spec.type == "game" && conf.exists("name")) + spec.title = conf.get("name"); + + if (spec.type != "game" && conf.exists("name")) spec.name = conf.get("name"); if (conf.exists("description")) diff --git a/src/content/content.h b/src/content/content.h index e246ed411..ce09a2eb9 100644 --- a/src/content/content.h +++ b/src/content/content.h @@ -27,7 +27,14 @@ struct ContentSpec std::string type; std::string author; u32 release = 0; + + /// Technical name / Id std::string name; + + /// Human-readable title + std::string title; + + /// Short description std::string desc; std::string path; }; diff --git a/src/content/mod_configuration.cpp b/src/content/mod_configuration.cpp new file mode 100644 index 000000000..504cdaf6f --- /dev/null +++ b/src/content/mod_configuration.cpp @@ -0,0 +1,255 @@ +/* +Minetest +Copyright (C) 2013-22 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mod_configuration.h" +#include "log.h" +#include "settings.h" +#include "filesys.h" + +void ModConfiguration::printUnsatisfiedModsError() const +{ + for (const ModSpec &mod : m_unsatisfied_mods) { + errorstream << "mod \"" << mod.name + << "\" has unsatisfied dependencies: "; + for (const std::string &unsatisfied_depend : mod.unsatisfied_depends) + errorstream << " \"" << unsatisfied_depend << "\""; + errorstream << std::endl; + } +} + +void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path) +{ + addMods(flattenMods(getModsInPath(path, virtual_path))); +} + +void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods) +{ + // Maintain a map of all existing m_unsatisfied_mods. + // Keys are mod names and values are indices into m_unsatisfied_mods. + std::map<std::string, u32> existing_mods; + for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) { + existing_mods[m_unsatisfied_mods[i].name] = i; + } + + // Add new mods + for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) { + // First iteration: + // Add all the mods that come from modpacks + // Second iteration: + // Add all the mods that didn't come from modpacks + + std::set<std::string> seen_this_iteration; + + for (const ModSpec &mod : new_mods) { + if (mod.part_of_modpack != (bool)want_from_modpack) + continue; + + if (existing_mods.count(mod.name) == 0) { + // GOOD CASE: completely new mod. + m_unsatisfied_mods.push_back(mod); + existing_mods[mod.name] = m_unsatisfied_mods.size() - 1; + } else if (seen_this_iteration.count(mod.name) == 0) { + // BAD CASE: name conflict in different levels. + u32 oldindex = existing_mods[mod.name]; + const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; + warningstream << "Mod name conflict detected: \"" + << mod.name << "\"" << std::endl + << "Will not load: " << oldmod.path + << std::endl + << "Overridden by: " << mod.path + << std::endl; + m_unsatisfied_mods[oldindex] = mod; + + // If there was a "VERY BAD CASE" name conflict + // in an earlier level, ignore it. + m_name_conflicts.erase(mod.name); + } else { + // VERY BAD CASE: name conflict in the same level. + u32 oldindex = existing_mods[mod.name]; + const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; + warningstream << "Mod name conflict detected: \"" + << mod.name << "\"" << std::endl + << "Will not load: " << oldmod.path + << std::endl + << "Will not load: " << mod.path + << std::endl; + m_unsatisfied_mods[oldindex] = mod; + m_name_conflicts.insert(mod.name); + } + + seen_this_iteration.insert(mod.name); + } + } +} + +void ModConfiguration::addGameMods(const SubgameSpec &gamespec) +{ + std::string game_virtual_path; + game_virtual_path.append("games/").append(gamespec.id).append("/mods"); + addModsInPath(gamespec.gamemods_path, game_virtual_path); +} + +void ModConfiguration::addModsFromConfig( + const std::string &settings_path, + const std::unordered_map<std::string, std::string> &modPaths) +{ + Settings conf; + std::unordered_map<std::string, std::string> load_mod_names; + + conf.readConfigFile(settings_path.c_str()); + std::vector<std::string> names = conf.getNames(); + for (const std::string &name : names) { + const auto &value = conf.get(name); + if (name.compare(0, 9, "load_mod_") == 0 && value != "false" && + value != "nil") + load_mod_names[name.substr(9)] = value; + } + + // List of enabled non-game non-world mods + std::vector<ModSpec> addon_mods; + + // Map of modname to a list candidate mod paths. Used to list + // alternatives if a particular mod cannot be found. + std::unordered_map<std::string, std::vector<std::string>> candidates; + + /* + * Iterate through all installed mods except game mods and world mods + * + * If the mod is enabled, add it to `addon_mods`. * + * + * Alternative candidates for a modname are stored in `candidates`, + * and used in an error message later. + * + * If not enabled, add `load_mod_modname = false` to world.mt + */ + for (const auto &modPath : modPaths) { + std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first)); + for (const auto &mod : addon_mods_in_path) { + const auto &pair = load_mod_names.find(mod.name); + if (pair != load_mod_names.end()) { + if (is_yes(pair->second) || pair->second == mod.virtual_path) { + addon_mods.push_back(mod); + } else { + candidates[pair->first].emplace_back(mod.virtual_path); + } + } else { + conf.setBool("load_mod_" + mod.name, false); + } + } + } + conf.updateConfigFile(settings_path.c_str()); + + addMods(addon_mods); + + // Remove all loaded mods from `load_mod_names` + // NB: as deps have not yet been resolved, `m_unsatisfied_mods` will contain all mods. + for (const ModSpec &mod : m_unsatisfied_mods) + load_mod_names.erase(mod.name); + + // Complain about mods declared to be loaded, but not found + if (!load_mod_names.empty()) { + errorstream << "The following mods could not be found:"; + for (const auto &pair : load_mod_names) + errorstream << " \"" << pair.first << "\""; + errorstream << std::endl; + + for (const auto &pair : load_mod_names) { + const auto &candidate = candidates.find(pair.first); + if (candidate != candidates.end()) { + errorstream << "Unable to load " << pair.first << " as the specified path " + << pair.second << " could not be found. " + << "However, it is available in the following locations:" + << std::endl; + for (const auto &path : candidate->second) { + errorstream << " - " << path << std::endl; + } + } + } + } +} + +void ModConfiguration::checkConflictsAndDeps() +{ + // report on name conflicts + if (!m_name_conflicts.empty()) { + std::string s = "Unresolved name conflicts for mods "; + + bool add_comma = false; + for (const auto& it : m_name_conflicts) { + if (add_comma) + s.append(", "); + s.append("\"").append(it).append("\""); + add_comma = true; + } + s.append("."); + + throw ModError(s); + } + + // get the mods in order + resolveDependencies(); +} + +void ModConfiguration::resolveDependencies() +{ + // Step 1: Compile a list of the mod names we're working with + std::set<std::string> modnames; + for (const ModSpec &mod : m_unsatisfied_mods) { + modnames.insert(mod.name); + } + + // Step 2: get dependencies (including optional dependencies) + // of each mod, split mods into satisfied and unsatisfied + std::list<ModSpec> satisfied; + std::list<ModSpec> unsatisfied; + for (ModSpec mod : m_unsatisfied_mods) { + mod.unsatisfied_depends = mod.depends; + // check which optional dependencies actually exist + for (const std::string &optdep : mod.optdepends) { + if (modnames.count(optdep) != 0) + mod.unsatisfied_depends.insert(optdep); + } + // if a mod has no depends it is initially satisfied + if (mod.unsatisfied_depends.empty()) + satisfied.push_back(mod); + else + unsatisfied.push_back(mod); + } + + // Step 3: mods without unmet dependencies can be appended to + // the sorted list. + while (!satisfied.empty()) { + ModSpec mod = satisfied.back(); + m_sorted_mods.push_back(mod); + satisfied.pop_back(); + for (auto it = unsatisfied.begin(); it != unsatisfied.end();) { + ModSpec &mod2 = *it; + mod2.unsatisfied_depends.erase(mod.name); + if (mod2.unsatisfied_depends.empty()) { + satisfied.push_back(mod2); + it = unsatisfied.erase(it); + } else { + ++it; + } + } + } + + // Step 4: write back list of unsatisfied mods + m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end()); +} diff --git a/src/content/mod_configuration.h b/src/content/mod_configuration.h new file mode 100644 index 000000000..1595d1ca3 --- /dev/null +++ b/src/content/mod_configuration.h @@ -0,0 +1,111 @@ +/* +Minetest +Copyright (C) 2013-22 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "mods.h" + + +/** + * ModConfiguration is a subset of installed mods. This class + * is used to resolve dependencies and return a sorted list of mods. + * + * This class should not be extended from, but instead used as a + * component in other classes. + */ +class ModConfiguration +{ +public: + /** + * @returns true if all dependencies are fullfilled. + */ + inline bool isConsistent() const { return m_unsatisfied_mods.empty(); } + + inline const std::vector<ModSpec> &getUnsatisfiedMods() const + { + return m_unsatisfied_mods; + } + + /** + * List of mods sorted such that they can be loaded in the + * given order with all dependencies being fulfilled. + * + * I.e: every mod in this list has only dependencies on mods which + * appear earlier in the vector. + */ + const std::vector<ModSpec> &getMods() const { return m_sorted_mods; } + + void printUnsatisfiedModsError() const; + + /** + * Adds all mods in the given path. used for games, modpacks + * and world-specific mods (worldmods-folders) + * + * @param path To search, should be absolute + * @param virtual_path Virtual path for this directory, see comment in ModSpec + */ + void addModsInPath(const std::string &path, const std::string &virtual_path); + + /** + * Adds all mods in `new_mods` + */ + void addMods(const std::vector<ModSpec> &new_mods); + + /** + * Adds game mods + */ + void addGameMods(const SubgameSpec &gamespec); + + /** + * Adds mods specifed by a world.mt config + * + * @param settings_path Path to world.mt + * @param modPaths Map from virtual name to mod path + */ + void addModsFromConfig(const std::string &settings_path, + const std::unordered_map<std::string, std::string> &modPaths); + + /** + * Call this function once all mods have been added + */ + void checkConflictsAndDeps(); + +private: + std::vector<ModSpec> m_sorted_mods; + + /** + * move mods from m_unsatisfied_mods to m_sorted_mods + * in an order that satisfies dependencies + */ + void resolveDependencies(); + + // mods with unmet dependencies. Before dependencies are resolved, + // this is where all mods are stored. Afterwards this contains + // only the ones with really unsatisfied dependencies. + std::vector<ModSpec> m_unsatisfied_mods; + + // set of mod names for which an unresolved name conflict + // exists. A name conflict happens when two or more mods + // at the same level have the same name but different paths. + // Levels (mods in higher levels override mods in lower levels): + // 1. game mod in modpack; 2. game mod; + // 3. world mod in modpack; 4. world mod; + // 5. addon mod in modpack; 6. addon mod. + std::unordered_set<std::string> m_name_conflicts; +}; diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 455506967..cec6fc2cd 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -69,7 +69,7 @@ bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols) return !dep.empty(); } -void parseModContents(ModSpec &spec) +bool parseModContents(ModSpec &spec) { // NOTE: this function works in mutual recursion with getModsInPath @@ -79,101 +79,100 @@ void parseModContents(ModSpec &spec) spec.modpack_content.clear(); // Handle modpacks (defined by containing modpack.txt) - std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str()); - std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str()); - if (modpack_is.good() || modpack2_is.good()) { - if (modpack_is.good()) - modpack_is.close(); + if (fs::IsFile(spec.path + DIR_DELIM + "modpack.txt") || + fs::IsFile(spec.path + DIR_DELIM + "modpack.conf")) { + spec.is_modpack = true; + spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true); + return true; + } else if (!fs::IsFile(spec.path + DIR_DELIM + "init.lua")) { + return false; + } - if (modpack2_is.good()) - modpack2_is.close(); - spec.is_modpack = true; - spec.modpack_content = getModsInPath(spec.path, true); + Settings info; + info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); - } else { - Settings info; - info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str()); + if (info.exists("name")) + spec.name = info.get("name"); + else + spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated."); - if (info.exists("name")) - spec.name = info.get("name"); - else - spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated."); - - if (info.exists("author")) - spec.author = info.get("author"); - - if (info.exists("release")) - spec.release = info.getS32("release"); - - // Attempt to load dependencies from mod.conf - bool mod_conf_has_depends = false; - if (info.exists("depends")) { - mod_conf_has_depends = true; - std::string dep = info.get("depends"); - // clang-format off - dep.erase(std::remove_if(dep.begin(), dep.end(), - static_cast<int (*)(int)>(&std::isspace)), dep.end()); - // clang-format on - for (const auto &dependency : str_split(dep, ',')) { - spec.depends.insert(dependency); - } + if (info.exists("author")) + spec.author = info.get("author"); + + if (info.exists("release")) + spec.release = info.getS32("release"); + + // Attempt to load dependencies from mod.conf + bool mod_conf_has_depends = false; + if (info.exists("depends")) { + mod_conf_has_depends = true; + std::string dep = info.get("depends"); + // clang-format off + dep.erase(std::remove_if(dep.begin(), dep.end(), + static_cast<int (*)(int)>(&std::isspace)), dep.end()); + // clang-format on + for (const auto &dependency : str_split(dep, ',')) { + spec.depends.insert(dependency); } + } - if (info.exists("optional_depends")) { - mod_conf_has_depends = true; - std::string dep = info.get("optional_depends"); - // clang-format off - dep.erase(std::remove_if(dep.begin(), dep.end(), - static_cast<int (*)(int)>(&std::isspace)), dep.end()); - // clang-format on - for (const auto &dependency : str_split(dep, ',')) { - spec.optdepends.insert(dependency); - } + if (info.exists("optional_depends")) { + mod_conf_has_depends = true; + std::string dep = info.get("optional_depends"); + // clang-format off + dep.erase(std::remove_if(dep.begin(), dep.end(), + static_cast<int (*)(int)>(&std::isspace)), dep.end()); + // clang-format on + for (const auto &dependency : str_split(dep, ',')) { + spec.optdepends.insert(dependency); } + } - // Fallback to depends.txt - if (!mod_conf_has_depends) { - std::vector<std::string> dependencies; + // Fallback to depends.txt + if (!mod_conf_has_depends) { + std::vector<std::string> dependencies; - std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); + std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); - if (is.good()) - spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); + if (is.good()) + spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); - while (is.good()) { - std::string dep; - std::getline(is, dep); - dependencies.push_back(dep); - } + while (is.good()) { + std::string dep; + std::getline(is, dep); + dependencies.push_back(dep); + } - for (auto &dependency : dependencies) { - std::unordered_set<char> symbols; - if (parseDependsString(dependency, symbols)) { - if (symbols.count('?') != 0) { - spec.optdepends.insert(dependency); - } else { - spec.depends.insert(dependency); - } + for (auto &dependency : dependencies) { + std::unordered_set<char> symbols; + if (parseDependsString(dependency, symbols)) { + if (symbols.count('?') != 0) { + spec.optdepends.insert(dependency); + } else { + spec.depends.insert(dependency); } } } - - if (info.exists("description")) - spec.desc = info.get("description"); - else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc)) - spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead."); } + + if (info.exists("description")) + spec.desc = info.get("description"); + else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc)) + spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead."); + + return true; } std::map<std::string, ModSpec> getModsInPath( - const std::string &path, bool part_of_modpack) + const std::string &path, const std::string &virtual_path, bool part_of_modpack) { // NOTE: this function works in mutual recursion with parseModContents std::map<std::string, ModSpec> result; std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path); - std::string modpath; + std::string mod_path; + std::string mod_virtual_path; for (const fs::DirListNode &dln : dirlist) { if (!dln.dir) @@ -185,10 +184,14 @@ std::map<std::string, ModSpec> getModsInPath( if (modname[0] == '.') continue; - modpath.clear(); - modpath.append(path).append(DIR_DELIM).append(modname); + mod_path.clear(); + mod_path.append(path).append(DIR_DELIM).append(modname); - ModSpec spec(modname, modpath, part_of_modpack); + mod_virtual_path.clear(); + // Intentionally uses / to keep paths same on different platforms + mod_virtual_path.append(virtual_path).append("/").append(modname); + + ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path); parseModContents(spec); result.insert(std::make_pair(modname, spec)); } @@ -213,215 +216,6 @@ std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods) return result; } -ModConfiguration::ModConfiguration(const std::string &worldpath) -{ -} - -void ModConfiguration::printUnsatisfiedModsError() const -{ - for (const ModSpec &mod : m_unsatisfied_mods) { - errorstream << "mod \"" << mod.name - << "\" has unsatisfied dependencies: "; - for (const std::string &unsatisfied_depend : mod.unsatisfied_depends) - errorstream << " \"" << unsatisfied_depend << "\""; - errorstream << std::endl; - } -} - -void ModConfiguration::addModsInPath(const std::string &path) -{ - addMods(flattenMods(getModsInPath(path))); -} - -void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods) -{ - // Maintain a map of all existing m_unsatisfied_mods. - // Keys are mod names and values are indices into m_unsatisfied_mods. - std::map<std::string, u32> existing_mods; - for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) { - existing_mods[m_unsatisfied_mods[i].name] = i; - } - - // Add new mods - for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) { - // First iteration: - // Add all the mods that come from modpacks - // Second iteration: - // Add all the mods that didn't come from modpacks - - std::set<std::string> seen_this_iteration; - - for (const ModSpec &mod : new_mods) { - if (mod.part_of_modpack != (bool)want_from_modpack) - continue; - - if (existing_mods.count(mod.name) == 0) { - // GOOD CASE: completely new mod. - m_unsatisfied_mods.push_back(mod); - existing_mods[mod.name] = m_unsatisfied_mods.size() - 1; - } else if (seen_this_iteration.count(mod.name) == 0) { - // BAD CASE: name conflict in different levels. - u32 oldindex = existing_mods[mod.name]; - const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; - warningstream << "Mod name conflict detected: \"" - << mod.name << "\"" << std::endl - << "Will not load: " << oldmod.path - << std::endl - << "Overridden by: " << mod.path - << std::endl; - m_unsatisfied_mods[oldindex] = mod; - - // If there was a "VERY BAD CASE" name conflict - // in an earlier level, ignore it. - m_name_conflicts.erase(mod.name); - } else { - // VERY BAD CASE: name conflict in the same level. - u32 oldindex = existing_mods[mod.name]; - const ModSpec &oldmod = m_unsatisfied_mods[oldindex]; - warningstream << "Mod name conflict detected: \"" - << mod.name << "\"" << std::endl - << "Will not load: " << oldmod.path - << std::endl - << "Will not load: " << mod.path - << std::endl; - m_unsatisfied_mods[oldindex] = mod; - m_name_conflicts.insert(mod.name); - } - - seen_this_iteration.insert(mod.name); - } - } -} - -void ModConfiguration::addModsFromConfig( - const std::string &settings_path, const std::set<std::string> &mods) -{ - Settings conf; - std::set<std::string> load_mod_names; - - conf.readConfigFile(settings_path.c_str()); - std::vector<std::string> names = conf.getNames(); - for (const std::string &name : names) { - if (name.compare(0, 9, "load_mod_") == 0 && conf.get(name) != "false" && - conf.get(name) != "nil") - load_mod_names.insert(name.substr(9)); - } - - std::vector<ModSpec> addon_mods; - for (const std::string &i : mods) { - std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(i)); - for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin(); - it != addon_mods_in_path.end(); ++it) { - const ModSpec &mod = *it; - if (load_mod_names.count(mod.name) != 0) - addon_mods.push_back(mod); - else - conf.setBool("load_mod_" + mod.name, false); - } - } - conf.updateConfigFile(settings_path.c_str()); - - addMods(addon_mods); - checkConflictsAndDeps(); - - // complain about mods declared to be loaded, but not found - for (const ModSpec &addon_mod : addon_mods) - load_mod_names.erase(addon_mod.name); - - std::vector<ModSpec> unsatisfiedMods = getUnsatisfiedMods(); - - for (const ModSpec &unsatisfiedMod : unsatisfiedMods) - load_mod_names.erase(unsatisfiedMod.name); - - if (!load_mod_names.empty()) { - errorstream << "The following mods could not be found:"; - for (const std::string &mod : load_mod_names) - errorstream << " \"" << mod << "\""; - errorstream << std::endl; - } -} - -void ModConfiguration::checkConflictsAndDeps() -{ - // report on name conflicts - if (!m_name_conflicts.empty()) { - std::string s = "Unresolved name conflicts for mods "; - for (std::unordered_set<std::string>::const_iterator it = - m_name_conflicts.begin(); - it != m_name_conflicts.end(); ++it) { - if (it != m_name_conflicts.begin()) - s += ", "; - s += std::string("\"") + (*it) + "\""; - } - s += "."; - throw ModError(s); - } - - // get the mods in order - resolveDependencies(); -} - -void ModConfiguration::resolveDependencies() -{ - // Step 1: Compile a list of the mod names we're working with - std::set<std::string> modnames; - for (const ModSpec &mod : m_unsatisfied_mods) { - modnames.insert(mod.name); - } - - // Step 2: get dependencies (including optional dependencies) - // of each mod, split mods into satisfied and unsatisfied - std::list<ModSpec> satisfied; - std::list<ModSpec> unsatisfied; - for (ModSpec mod : m_unsatisfied_mods) { - mod.unsatisfied_depends = mod.depends; - // check which optional dependencies actually exist - for (const std::string &optdep : mod.optdepends) { - if (modnames.count(optdep) != 0) - mod.unsatisfied_depends.insert(optdep); - } - // if a mod has no depends it is initially satisfied - if (mod.unsatisfied_depends.empty()) - satisfied.push_back(mod); - else - unsatisfied.push_back(mod); - } - - // Step 3: mods without unmet dependencies can be appended to - // the sorted list. - while (!satisfied.empty()) { - ModSpec mod = satisfied.back(); - m_sorted_mods.push_back(mod); - satisfied.pop_back(); - for (auto it = unsatisfied.begin(); it != unsatisfied.end();) { - ModSpec &mod2 = *it; - mod2.unsatisfied_depends.erase(mod.name); - if (mod2.unsatisfied_depends.empty()) { - satisfied.push_back(mod2); - it = unsatisfied.erase(it); - } else { - ++it; - } - } - } - - // Step 4: write back list of unsatisfied mods - m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end()); -} - -#ifndef SERVER -ClientModConfiguration::ClientModConfiguration(const std::string &path) : - ModConfiguration(path) -{ - std::set<std::string> paths; - std::string path_user = porting::path_user + DIR_DELIM + "clientmods"; - paths.insert(path); - paths.insert(path_user); - - std::string settings_path = path_user + DIR_DELIM + "mods.conf"; - addModsFromConfig(settings_path, paths); -} -#endif ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database): m_mod_name(mod_name), m_database(database) diff --git a/src/content/mods.h b/src/content/mods.h index dd3b6e0e6..5ed5b8171 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/basic_macros.h" #include "config.h" #include "metadata.h" +#include "subgames.h" class ModMetadataDatabase; @@ -51,101 +52,63 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + /** + * A constructed canonical path to represent this mod's location. + * This intended to be used as an identifier for a modpath that tolerates file movement, + * and cannot be used to read the mod files. + * + * Note that `mymod` is the directory name, not the mod name specified in mod.conf. + * + * Ex: + * + * - mods/mymod + * - mods/mymod (1) + * (^ this would have name=mymod in mod.conf) + * - mods/modpack1/mymod + * - games/mygame/mods/mymod + * - worldmods/mymod + */ + std::string virtual_path; + // For logging purposes std::vector<const char *> deprecation_msgs; // if modpack: std::map<std::string, ModSpec> modpack_content; - ModSpec(const std::string &name = "", const std::string &path = "") : - name(name), path(path) + + ModSpec() { } - ModSpec(const std::string &name, const std::string &path, bool part_of_modpack) : - name(name), path(path), part_of_modpack(part_of_modpack) + + ModSpec(const std::string &name, const std::string &path, bool part_of_modpack, const std::string &virtual_path) : + name(name), path(path), part_of_modpack(part_of_modpack), virtual_path(virtual_path) { } void checkAndLog() const; }; -// Retrieves depends, optdepends, is_modpack and modpack_content -void parseModContents(ModSpec &mod); - -std::map<std::string, ModSpec> getModsInPath( - const std::string &path, bool part_of_modpack = false); +/** + * Retrieves depends, optdepends, is_modpack and modpack_content + * + * @returns false if not a mod + */ +bool parseModContents(ModSpec &mod); + +/** + * Gets a list of all mods and modpacks in path + * + * @param Path to search, should be absolute + * @param part_of_modpack Is this searching within a modpack? + * @param virtual_path Virtual path for this directory, see comment in ModSpec + * @returns map of mods + */ +std::map<std::string, ModSpec> getModsInPath(const std::string &path, + const std::string &virtual_path, bool part_of_modpack = false); // replaces modpack Modspecs with their content std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods); -// a ModConfiguration is a subset of installed mods, expected to have -// all dependencies fullfilled, so it can be used as a list of mods to -// load when the game starts. -class ModConfiguration -{ -public: - // checks if all dependencies are fullfilled. - bool isConsistent() const { return m_unsatisfied_mods.empty(); } - - const std::vector<ModSpec> &getMods() const { return m_sorted_mods; } - - const std::vector<ModSpec> &getUnsatisfiedMods() const - { - return m_unsatisfied_mods; - } - - void printUnsatisfiedModsError() const; - -protected: - ModConfiguration(const std::string &worldpath); - // adds all mods in the given path. used for games, modpacks - // and world-specific mods (worldmods-folders) - void addModsInPath(const std::string &path); - - // adds all mods in the set. - void addMods(const std::vector<ModSpec> &new_mods); - - void addModsFromConfig(const std::string &settings_path, - const std::set<std::string> &mods); - - void checkConflictsAndDeps(); - -protected: - // list of mods sorted such that they can be loaded in the - // given order with all dependencies being fullfilled. I.e., - // every mod in this list has only dependencies on mods which - // appear earlier in the vector. - std::vector<ModSpec> m_sorted_mods; - -private: - // move mods from m_unsatisfied_mods to m_sorted_mods - // in an order that satisfies dependencies - void resolveDependencies(); - - // mods with unmet dependencies. Before dependencies are resolved, - // this is where all mods are stored. Afterwards this contains - // only the ones with really unsatisfied dependencies. - std::vector<ModSpec> m_unsatisfied_mods; - - // set of mod names for which an unresolved name conflict - // exists. A name conflict happens when two or more mods - // at the same level have the same name but different paths. - // Levels (mods in higher levels override mods in lower levels): - // 1. game mod in modpack; 2. game mod; - // 3. world mod in modpack; 4. world mod; - // 5. addon mod in modpack; 6. addon mod. - std::unordered_set<std::string> m_name_conflicts; - - // Deleted default constructor - ModConfiguration() = default; -}; - -#ifndef SERVER -class ClientModConfiguration : public ModConfiguration -{ -public: - ClientModConfiguration(const std::string &path); -}; -#endif class ModMetadata : public Metadata { diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 62e82e0e4..d0de926ef 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <common/c_internal.h> #include "content/subgames.h" #include "porting.h" #include "filesys.h" @@ -45,6 +46,25 @@ bool getGameMinetestConfig(const std::string &game_path, Settings &conf) } + +void SubgameSpec::checkAndLog() const +{ + // Log deprecation messages + auto handling_mode = get_deprecated_handling_mode(); + if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) { + std::ostringstream os; + os << "Game " << title << " at " << path << ":" << std::endl; + for (auto msg : deprecation_msgs) + os << "\t" << msg << std::endl; + + if (handling_mode == DeprecatedHandlingMode::Error) + throw ModError(os.str()); + else + warningstream << os.str(); + } +} + + struct GameFindPath { std::string path; @@ -107,14 +127,13 @@ SubgameSpec findSubgame(const std::string &id) std::string gamemod_path = game_path + DIR_DELIM + "mods"; // Find mod directories - std::set<std::string> mods_paths; - if (!user_game) - mods_paths.insert(share + DIR_DELIM + "mods"); - if (user != share || user_game) - mods_paths.insert(user + DIR_DELIM + "mods"); + std::unordered_map<std::string, std::string> mods_paths; + mods_paths["mods"] = user + DIR_DELIM + "mods"; + if (!user_game && user != share) + mods_paths["share"] = share + DIR_DELIM + "mods"; for (const std::string &mod_path : getEnvModPaths()) { - mods_paths.insert(mod_path); + mods_paths[fs::AbsolutePath(mod_path)] = mod_path; } // Get meta @@ -122,11 +141,13 @@ SubgameSpec findSubgame(const std::string &id) Settings conf; conf.readConfigFile(conf_path.c_str()); - std::string game_name; - if (conf.exists("name")) - game_name = conf.get("name"); + std::string game_title; + if (conf.exists("title")) + game_title = conf.get("title"); + else if (conf.exists("name")) + game_title = conf.get("name"); else - game_name = id; + game_title = id; std::string game_author; if (conf.exists("author")) @@ -141,8 +162,14 @@ SubgameSpec findSubgame(const std::string &id) menuicon_path = getImagePath( game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png"); #endif - return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name, + + SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title, menuicon_path, game_author, game_release); + + if (conf.exists("name") && !conf.exists("title")) + spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead"); + + return spec; } SubgameSpec findWorldSubgame(const std::string &world_path) @@ -160,10 +187,12 @@ SubgameSpec findWorldSubgame(const std::string &world_path) std::string conf_path = world_gamepath + DIR_DELIM + "game.conf"; conf.readConfigFile(conf_path.c_str()); - if (conf.exists("name")) - gamespec.name = conf.get("name"); + if (conf.exists("title")) + gamespec.title = conf.get("title"); + else if (conf.exists("name")) + gamespec.title = conf.get("name"); else - gamespec.name = world_gameid; + gamespec.title = world_gameid; return gamespec; } diff --git a/src/content/subgames.h b/src/content/subgames.h index 4a50803e8..d5d168243 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include <set> +#include <unordered_map> #include <vector> class Settings; @@ -28,29 +29,36 @@ class Settings; struct SubgameSpec { std::string id; - std::string name; + std::string title; std::string author; int release; std::string path; std::string gamemods_path; - std::set<std::string> addon_mods_paths; + + /** + * Map from virtual path to mods path + */ + std::unordered_map<std::string, std::string> addon_mods_paths; std::string menuicon_path; + // For logging purposes + std::vector<const char *> deprecation_msgs; + SubgameSpec(const std::string &id = "", const std::string &path = "", const std::string &gamemods_path = "", - const std::set<std::string> &addon_mods_paths = - std::set<std::string>(), - const std::string &name = "", + const std::unordered_map<std::string, std::string> &addon_mods_paths = {}, + const std::string &title = "", const std::string &menuicon_path = "", const std::string &author = "", int release = 0) : id(id), - name(name), author(author), release(release), path(path), + title(title), author(author), release(release), path(path), gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths), menuicon_path(menuicon_path) { } bool isValid() const { return (!id.empty() && !path.empty()); } + void checkAndLog() const; }; SubgameSpec findSubgame(const std::string &id); diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 39f4c8442..6e59daab3 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -74,7 +74,7 @@ void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) i64tos(getBlockAsInteger(pos)), block); if (!status.ok()) - block->clear(); + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 3469f4242..9d6501e68 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -495,7 +495,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventories", 1, rmvalues); execPrepared("remove_player_inventory_items", 1, rmvalues); - std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index 81b4a2b10..0a9ead01e 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -94,7 +94,8 @@ protected: checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL)); } - const int getPGVersion() const { return m_pgversion; } + int getPGVersion() const { return m_pgversion; } + private: // Database connectivity checks void ping(); diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 1e63ae9d8..9521085e9 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -476,10 +476,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); sqlite3_reset(m_stmt_player_remove_inventory_items); - std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + const auto &inventory_lists = sao->getInventory()->getLists(); std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { - const InventoryList* list = inventory_lists[i]; + const InventoryList *list = inventory_lists[i]; str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory, 2, i); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 600fc65f3..c5d92e680 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -64,8 +64,7 @@ void set_default_settings() settings->setDefault("enable_client_modding", "false"); settings->setDefault("max_out_chat_queue_size", "20"); settings->setDefault("pause_on_lost_focus", "false"); - settings->setDefault("enable_register_confirmation", "true"); - settings->setDefault("clickable_chat_weblinks", "false"); + settings->setDefault("enable_split_login_register", "true"); settings->setDefault("chat_weblink_color", "#8888FF"); // Keymap @@ -244,6 +243,7 @@ void set_default_settings() settings->setDefault("enable_particles", "true"); settings->setDefault("arm_inertia", "true"); settings->setDefault("show_nametag_backgrounds", "true"); + settings->setDefault("transparency_sorting_distance", "16"); settings->setDefault("enable_minimap", "true"); settings->setDefault("minimap_shape_round", "true"); @@ -266,7 +266,7 @@ void set_default_settings() // Effects Shadows settings->setDefault("enable_dynamic_shadows", "false"); - settings->setDefault("shadow_strength", "0.2"); + settings->setDefault("shadow_strength_gamma", "1.0"); settings->setDefault("shadow_map_max_distance", "200.0"); settings->setDefault("shadow_map_texture_size", "2048"); settings->setDefault("shadow_map_texture_32bit", "true"); @@ -274,7 +274,7 @@ void set_default_settings() settings->setDefault("shadow_filters", "1"); settings->setDefault("shadow_poisson_filter", "true"); settings->setDefault("shadow_update_frames", "8"); - settings->setDefault("shadow_soft_radius", "1.0"); + settings->setDefault("shadow_soft_radius", "5.0"); settings->setDefault("shadow_sky_body_orbit_tilt", "0.0"); // Input @@ -335,6 +335,12 @@ void set_default_settings() settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default"); #endif + settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json"); +#if ENABLE_UPDATE_CHECKER + settings->setDefault("update_last_checked", ""); +#else + settings->setDefault("update_last_checked", "disabled"); +#endif // Server settings->setDefault("disable_escape_sequences", "false"); @@ -386,7 +392,7 @@ void set_default_settings() settings->setDefault("time_speed", "72"); settings->setDefault("world_start_time", "6125"); settings->setDefault("server_unload_unused_data_timeout", "29"); - settings->setDefault("max_objects_per_block", "64"); + settings->setDefault("max_objects_per_block", "256"); settings->setDefault("server_map_save_interval", "5.3"); settings->setDefault("chat_message_max_size", "500"); settings->setDefault("chat_message_limit_per_10sec", "8.0"); @@ -456,7 +462,6 @@ void set_default_settings() // Altered settings for macOS #if defined(__MACH__) && defined(__APPLE__) settings->setDefault("keymap_sneak", "KEY_SHIFT"); - settings->setDefault("fps_max", "0"); #endif #ifdef HAVE_TOUCHSCREENGUI @@ -464,6 +469,9 @@ void set_default_settings() settings->setDefault("touchscreen_threshold","20"); settings->setDefault("fixed_virtual_joystick", "false"); settings->setDefault("virtual_joystick_triggers_aux1", "false"); + settings->setDefault("clickable_chat_weblinks", "false"); +#else + settings->setDefault("clickable_chat_weblinks", "true"); #endif // Altered settings for Android #ifdef __ANDROID__ @@ -477,9 +485,7 @@ void set_default_settings() settings->setDefault("emergequeue_limit_generate", "16"); settings->setDefault("max_block_generate_distance", "5"); settings->setDefault("enable_3d_clouds", "false"); - settings->setDefault("fps_max", "30"); settings->setDefault("fps_max_unfocused", "10"); - settings->setDefault("max_objects_per_block", "20"); settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("map_compression_level_disk", "-1"); settings->setDefault("map_compression_level_net", "-1"); diff --git a/src/emerge.cpp b/src/emerge.cpp index 3760b24e6..3e42742f6 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -61,7 +61,9 @@ public: void cancelPendingItems(); - static void runCompletionCallbacks( +protected: + + void runCompletionCallbacks( const v3s16 &pos, EmergeAction action, const EmergeCallbackList &callbacks); @@ -138,7 +140,7 @@ EmergeParams::EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, //// EmergeManager //// -EmergeManager::EmergeManager(Server *server) +EmergeManager::EmergeManager(Server *server, MetricsBackend *mb) { this->ndef = server->getNodeDefManager(); this->biomemgr = new BiomeManager(server); @@ -156,11 +158,22 @@ EmergeManager::EmergeManager(Server *server) enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + STATIC_ASSERT(ARRLEN(emergeActionStrs) == ARRLEN(m_completed_emerge_counter), + enum_size_mismatches); + for (u32 i = 0; i < ARRLEN(m_completed_emerge_counter); i++) { + std::string help_str("Number of completed emerges with status "); + help_str.append(emergeActionStrs[i]); + m_completed_emerge_counter[i] = mb->addCounter( + "minetest_emerge_completed", help_str, + {{"status", emergeActionStrs[i]}} + ); + } + s16 nthreads = 1; g_settings->getS16NoEx("num_emerge_threads", nthreads); // If automatic, leave a proc for the main thread and one for // some other misc thread - if (nthreads == 0) + if (nthreads <= 0) nthreads = Thread::getNumberOfProcessors() - 2; if (nthreads < 1) nthreads = 1; @@ -489,6 +502,12 @@ EmergeThread *EmergeManager::getOptimalThread() return m_threads[index]; } +void EmergeManager::reportCompletedEmerge(EmergeAction action) +{ + assert((size_t)action < ARRLEN(m_completed_emerge_counter)); + m_completed_emerge_counter[(int)action]->increment(); +} + //// //// EmergeThread @@ -540,6 +559,8 @@ void EmergeThread::cancelPendingItems() void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action, const EmergeCallbackList &callbacks) { + m_emerge->reportCompletedEmerge(action); + for (size_t i = 0; i != callbacks.size(); i++) { EmergeCompletionCallback callback; void *param; diff --git a/src/emerge.h b/src/emerge.h index 61e7bda63..1bac4b708 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/networkprotocol.h" #include "irr_v3d.h" #include "util/container.h" +#include "util/metricsbackend.h" #include "mapgen/mapgen.h" // for MapgenParams #include "map.h" @@ -69,6 +70,14 @@ enum EmergeAction { EMERGE_GENERATED, }; +const static std::string emergeActionStrs[] = { + "cancelled", + "errored", + "from_memory", + "from_disk", + "generated", +}; + // Callback typedef void (*EmergeCompletionCallback)( v3s16 blockpos, EmergeAction action, void *param); @@ -138,7 +147,7 @@ public: MapSettingsManager *map_settings_mgr; // Methods - EmergeManager(Server *server); + EmergeManager(Server *server, MetricsBackend *mb); ~EmergeManager(); DISABLE_CLASS_COPY(EmergeManager); @@ -197,6 +206,9 @@ private: u32 m_qlimit_diskonly; u32 m_qlimit_generate; + // Emerge metrics + MetricCounterPtr m_completed_emerge_counter[5]; + // Managers of various map generation-related components // Note that each Mapgen gets a copy(!) of these to work with BiomeGen *biomegen; @@ -218,5 +230,7 @@ private: bool popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata); + void reportCompletedEmerge(EmergeAction action); + friend class EmergeThread; }; diff --git a/src/filesys.cpp b/src/filesys.cpp index ea00def6a..28df32054 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -877,4 +877,3 @@ bool Rename(const std::string &from, const std::string &to) } } // namespace fs - diff --git a/src/filesys.h b/src/filesys.h index 3fa2524c3..a26fe28d6 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -60,6 +60,11 @@ bool IsPathAbsolute(const std::string &path); bool IsDir(const std::string &path); +inline bool IsFile(const std::string &path) +{ + return PathExists(path) && !IsDir(path); +} + bool IsDirDelimiter(char c); // Only pass full paths to this one. True on success. diff --git a/src/gamedef.h b/src/gamedef.h index 8a9246da2..45b9c4750 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -63,6 +63,8 @@ public: virtual IRollbackManager* getRollbackManager() { return NULL; } // Shorthands + // TODO: these should be made const-safe so that a const IGameDef* is + // actually usable IItemDefManager *idef() { return getItemDefManager(); } const NodeDefManager *ndef() { return getNodeDefManager(); } ICraftDefManager *cdef() { return getCraftDefManager(); } diff --git a/src/gameparams.h b/src/gameparams.h index 70b0ffcde..b138f8771 100644 --- a/src/gameparams.h +++ b/src/gameparams.h @@ -20,8 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes.h" - -struct SubgameSpec; +#include "content/subgames.h" // Information provided from "main" struct GameParams @@ -34,6 +33,12 @@ struct GameParams bool is_dedicated_server; }; +enum class ELoginRegister { + Any = 0, + Login, + Register +}; + // Information processed by main menu struct GameStartData : GameParams { @@ -46,6 +51,8 @@ struct GameStartData : GameParams std::string address; bool local_server; + ELoginRegister allow_login_or_register = ELoginRegister::Any; + // "world_path" must be kept in sync! WorldSpec world_spec; }; diff --git a/src/gettime.h b/src/gettime.h index 66efef1d7..772ff9b50 100644 --- a/src/gettime.h +++ b/src/gettime.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <ctime> #include <string> +#include <mutex> enum TimePrecision { @@ -30,13 +31,34 @@ enum TimePrecision PRECISION_NANO }; -inline std::string getTimestamp() +inline struct tm mt_localtime() { + // initialize the time zone on first invocation + static std::once_flag tz_init; + std::call_once(tz_init, [] { +#ifdef _WIN32 + _tzset(); +#else + tzset(); +#endif + }); + + struct tm ret; time_t t = time(NULL); - // This is not really thread-safe but it won't break anything - // except its own output, so just go with it. - struct tm *tm = localtime(&t); + // TODO we should check if the function returns NULL, which would mean error +#ifdef _WIN32 + localtime_s(&ret, &t); +#else + localtime_r(&t, &ret); +#endif + return ret; +} + + +inline std::string getTimestamp() +{ + const struct tm tm = mt_localtime(); char cs[20]; // YYYY-MM-DD HH:MM:SS + '\0' - strftime(cs, 20, "%Y-%m-%d %H:%M:%S", tm); + strftime(cs, 20, "%Y-%m-%d %H:%M:%S", &tm); return cs; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 513b13e8e..4434b14a0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -11,7 +11,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index fc92a861b..7a45e07d1 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -45,6 +45,7 @@ public: BGIMG_PRESSED, // Note: Deprecated property FGIMG, FGIMG_HOVERED, // Note: Deprecated property + FGIMG_MIDDLE, FGIMG_PRESSED, // Note: Deprecated property ALPHA, CONTENT_OFFSET, @@ -101,6 +102,8 @@ public: return FGIMG; } else if (name == "fgimg_hovered") { return FGIMG_HOVERED; + } else if (name == "fgimg_middle") { + return FGIMG_MIDDLE; } else if (name == "fgimg_pressed") { return FGIMG_PRESSED; } else if (name == "alpha") { diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp index b1447c45f..890763e71 100644 --- a/src/gui/guiAnimatedImage.cpp +++ b/src/gui/guiAnimatedImage.cpp @@ -9,40 +9,37 @@ #include <vector> GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect<s32> &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), m_tsrc(tsrc) + s32 id, const core::rect<s32> &rectangle) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle) { - m_texture = m_tsrc->getTexture(texture_name); - - m_frame_count = std::max(frame_count, 1); - m_frame_duration = std::max(frame_duration, 0); - - if (m_texture != nullptr) { - core::dimension2d<u32> size = m_texture->getOriginalSize(); - if (size.Height < (u64)m_frame_count) - m_frame_count = size.Height; - } else { - // No need to step an animation if we have nothing to draw - m_frame_count = 1; - } } void GUIAnimatedImage::draw() { - // Render the current frame - if (m_texture != nullptr) { - video::IVideoDriver *driver = Environment->getVideoDriver(); + if (m_texture == nullptr) + return; - const video::SColor color(255, 255, 255, 255); - const video::SColor colors[] = {color, color, color, color}; + video::IVideoDriver *driver = Environment->getVideoDriver(); + + core::dimension2d<u32> size = m_texture->getOriginalSize(); + + if ((u32)m_frame_count > size.Height) + m_frame_count = size.Height; + if (m_frame_idx >= m_frame_count) + m_frame_idx = m_frame_count - 1; - core::dimension2d<u32> size = m_texture->getOriginalSize(); - size.Height /= m_frame_count; + size.Height /= m_frame_count; - draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, - core::rect<s32>(core::position2d<s32>(0, size.Height * m_frame_idx), size), - NoClip ? nullptr : &AbsoluteClippingRect, colors, true); + core::rect<s32> rect(core::position2d<s32>(0, size.Height * m_frame_idx), size); + core::rect<s32> *cliprect = NoClip ? nullptr : &AbsoluteClippingRect; + + if (m_middle.getArea() == 0) { + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, rect, cliprect, + colors, true); + } else { + draw2DImage9Slice(driver, m_texture, AbsoluteRect, rect, m_middle, cliprect); } // Step the animation @@ -55,7 +52,7 @@ void GUIAnimatedImage::draw() m_global_time = new_global_time; // Advance by the number of elapsed frames, looping if necessary - m_frame_idx += u32(m_frame_time / m_frame_duration); + m_frame_idx += (u32)(m_frame_time / m_frame_duration); m_frame_idx %= m_frame_count; // If 1 or more frames have elapsed, reset the frame time counter with @@ -63,11 +60,3 @@ void GUIAnimatedImage::draw() m_frame_time %= m_frame_duration; } } - - -void GUIAnimatedImage::setFrameIndex(s32 frame) -{ - s32 idx = std::max(frame, 0); - if (idx > 0 && idx < m_frame_count) - m_frame_idx = idx; -} diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h index f8e6a506e..885aedece 100644 --- a/src/gui/guiAnimatedImage.h +++ b/src/gui/guiAnimatedImage.h @@ -1,6 +1,7 @@ #pragma once #include "irrlichttypes_extrabloated.h" +#include <algorithm> #include <string> class ISimpleTextureSource; @@ -8,21 +9,33 @@ class ISimpleTextureSource; class GUIAnimatedImage : public gui::IGUIElement { public: GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect<s32> &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc); + s32 id, const core::rect<s32> &rectangle); virtual void draw() override; - void setFrameIndex(s32 frame); + void setTexture(video::ITexture *texture) { m_texture = texture; }; + video::ITexture *getTexture() const { return m_texture; }; + + void setMiddleRect(const core::rect<s32> &middle) { m_middle = middle; }; + core::rect<s32> getMiddleRect() const { return m_middle; }; + + void setFrameDuration(u64 duration) { m_frame_duration = duration; }; + u64 getFrameDuration() const { return m_frame_duration; }; + + void setFrameCount(s32 count) { m_frame_count = std::max(count, 1); }; + s32 getFrameCount() const { return m_frame_count; }; + + void setFrameIndex(s32 frame) { m_frame_idx = std::max(frame, 0); }; s32 getFrameIndex() const { return m_frame_idx; }; private: - ISimpleTextureSource *m_tsrc; - video::ITexture *m_texture = nullptr; + u64 m_global_time = 0; s32 m_frame_idx = 0; s32 m_frame_count = 1; - u64 m_frame_duration = 1; + u64 m_frame_duration = 0; u64 m_frame_time = 0; + + core::rect<s32> m_middle; }; diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp index 21c1e88cf..8d0d1c010 100644 --- a/src/gui/guiBackgroundImage.cpp +++ b/src/gui/guiBackgroundImage.cpp @@ -44,25 +44,19 @@ void GUIBackgroundImage::draw() core::rect<s32> rect = AbsoluteRect; if (m_autoclip) - rect.LowerRightCorner += Parent->getAbsolutePosition().getSize(); + rect.LowerRightCorner += Parent->getAbsoluteClippingRect().getSize(); video::IVideoDriver *driver = Environment->getVideoDriver(); + core::rect<s32> srcrect(core::position2d<s32>(0, 0), + core::dimension2di(texture->getOriginalSize())); + if (m_middle.getArea() == 0) { const video::SColor color(255, 255, 255, 255); const video::SColor colors[] = {color, color, color, color}; - draw2DImageFilterScaled(driver, texture, rect, - core::rect<s32>(core::position2d<s32>(0, 0), - core::dimension2di(texture->getOriginalSize())), - nullptr, colors, true); + draw2DImageFilterScaled(driver, texture, rect, srcrect, nullptr, colors, true); } else { - core::rect<s32> middle = m_middle; - // `-x` is interpreted as `w - x` - if (middle.LowerRightCorner.X < 0) - middle.LowerRightCorner.X += texture->getOriginalSize().Width; - if (middle.LowerRightCorner.Y < 0) - middle.LowerRightCorner.Y += texture->getOriginalSize().Height; - draw2DImage9Slice(driver, texture, rect, middle); + draw2DImage9Slice(driver, texture, rect, srcrect, m_middle); } IGUIElement::draw(); diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index ba95b81c3..c38d901c4 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -320,15 +320,9 @@ void GUIButton::draw() sourceRect, &AbsoluteClippingRect,
image_colors, UseAlphaChannel);
} else {
- core::rect<s32> middle = BgMiddle;
- // `-x` is interpreted as `w - x`
- if (middle.LowerRightCorner.X < 0)
- middle.LowerRightCorner.X += texture->getOriginalSize().Width;
- if (middle.LowerRightCorner.Y < 0)
- middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
draw2DImage9Slice(driver, texture,
ScaleImage ? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
- middle, &AbsoluteClippingRect, image_colors);
+ sourceRect, BgMiddle, &AbsoluteClippingRect, image_colors);
}
// END PATCH
}
diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index b507ffece..4ab770a99 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -32,15 +32,15 @@ using namespace gui; GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc, bool noclip) - : GUIButton (environment, parent, id, rectangle, tsrc, noclip) + : GUIButton(environment, parent, id, rectangle, tsrc, noclip) { - m_image = Environment->addImage( - core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), this); - m_image->setScaleImage(isScalingImage()); + GUIButton::setScaleImage(true); + m_image = new GUIAnimatedImage(environment, this, id, rectangle); sendToBack(m_image); } -void GUIButtonImage::setForegroundImage(video::ITexture *image) +void GUIButtonImage::setForegroundImage(video::ITexture *image, + const core::rect<s32> &middle) { if (image == m_foreground_image) return; @@ -52,11 +52,12 @@ void GUIButtonImage::setForegroundImage(video::ITexture *image) m_foreground_image->drop(); m_foreground_image = image; - m_image->setImage(image); + m_image->setTexture(image); + m_image->setMiddleRect(middle); } //! Set element properties from a StyleSpec -void GUIButtonImage::setFromStyle(const StyleSpec& style) +void GUIButtonImage::setFromStyle(const StyleSpec &style) { GUIButton::setFromStyle(style); @@ -67,19 +68,13 @@ void GUIButtonImage::setFromStyle(const StyleSpec& style) getTextureSource()); setForegroundImage(guiScalingImageButton(driver, texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); - setScaleImage(true); + AbsoluteRect.getWidth(), AbsoluteRect.getHeight()), + style.getRect(StyleSpec::FGIMG_MIDDLE, m_image->getMiddleRect())); } else { - setForegroundImage(nullptr); + setForegroundImage(); } } -void GUIButtonImage::setScaleImage(bool scaleImage) -{ - GUIButton::setScaleImage(scaleImage); - m_image->setScaleImage(scaleImage); -} - GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment, const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc, IGUIElement *parent, s32 id, const wchar_t *text, diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h index 59a25b4f0..554934518 100644 --- a/src/gui/guiButtonImage.h +++ b/src/gui/guiButtonImage.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiButton.h" #include "IGUIButton.h" +#include "guiAnimatedImage.h" using namespace irr; @@ -32,12 +33,11 @@ public: s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc, bool noclip = false); - void setForegroundImage(video::ITexture *image = nullptr); + void setForegroundImage(video::ITexture *image = nullptr, + const core::rect<s32> &middle = core::rect<s32>()); //! Set element properties from a StyleSpec - virtual void setFromStyle(const StyleSpec& style) override; - - virtual void setScaleImage(bool scaleImage=true) override; + virtual void setFromStyle(const StyleSpec &style) override; //! Do not drop returned handle static GUIButtonImage *addButton(gui::IGUIEnvironment *environment, @@ -47,5 +47,5 @@ public: private: video::ITexture *m_foreground_image = nullptr; - gui::IGUIImage *m_image; + GUIAnimatedImage *m_image; }; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 01e10ea2e..280f7e45b 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -76,9 +76,9 @@ GUIChatConsole::GUIChatConsole( m_background_color.setBlue(clamp_u8(myround(console_color.Z))); } - u16 chat_font_size = g_settings->getU16("chat_font_size"); + const u16 chat_font_size = g_settings->getU16("chat_font_size"); m_font = g_fontengine->getFont(chat_font_size != 0 ? - chat_font_size : FONT_SIZE_UNSPECIFIED, FM_Mono); + rangelim(chat_font_size, 5, 72) : FONT_SIZE_UNSPECIFIED, FM_Mono); if (!m_font) { errorstream << "GUIChatConsole: Unable to load mono font" << std::endl; diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp deleted file mode 100644 index b8887a4af..000000000 --- a/src/gui/guiConfirmRegistration.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* -Minetest -Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto - <muhammadrifqipriyosusanto@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "guiConfirmRegistration.h" -#include "client/client.h" -#include "guiButton.h" -#include <IGUICheckBox.h> -#include <IGUIButton.h> -#include <IGUIStaticText.h> -#include <IGUIFont.h> -#include "guiEditBoxWithScrollbar.h" -#include "porting.h" - -#ifdef HAVE_TOUCHSCREENGUI - #include "client/renderingengine.h" -#endif - -#include "gettext.h" - -// Continuing from guiPasswordChange.cpp -const int ID_confirmPassword = 262; -const int ID_confirm = 263; -const int ID_intotext = 264; -const int ID_cancel = 265; -const int ID_message = 266; - -GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, - gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, - const std::string &playername, const std::string &password, - bool *aborted, ISimpleTextureSource *tsrc) : - GUIModalMenu(env, parent, id, menumgr), - m_client(client), m_playername(playername), m_password(password), - m_aborted(aborted), m_tsrc(tsrc) -{ -#ifdef HAVE_TOUCHSCREENGUI - m_touchscreen_visible = false; -#endif -} - -GUIConfirmRegistration::~GUIConfirmRegistration() -{ - removeChildren(); -} - -void GUIConfirmRegistration::removeChildren() -{ - const core::list<gui::IGUIElement *> &children = getChildren(); - core::list<gui::IGUIElement *> children_copy; - for (gui::IGUIElement *i : children) - children_copy.push_back(i); - for (gui::IGUIElement *i : children_copy) - i->remove(); -} - -void GUIConfirmRegistration::regenerateGui(v2u32 screensize) -{ - acceptInput(); - removeChildren(); - - /* - Calculate new sizes and positions - */ -#ifdef HAVE_TOUCHSCREENGUI - const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2; -#else - const float s = m_gui_scale; -#endif - DesiredRect = core::rect<s32>( - screensize.X / 2 - 600 * s / 2, - screensize.Y / 2 - 360 * s / 2, - screensize.X / 2 + 600 * s / 2, - screensize.Y / 2 + 360 * s / 2 - ); - recalculateAbsolutePosition(false); - - v2s32 size = DesiredRect.getSize(); - v2s32 topleft_client(0, 0); - - const wchar_t *text; - - /* - Add stuff - */ - s32 ypos = 30 * s; - { - core::rect<s32> rect2(0, 0, 540 * s, 180 * s); - rect2 += topleft_client + v2s32(30 * s, ypos); - static const std::string info_text_template = strgettext( - "You are about to join this server with the name \"%s\" for the " - "first time.\n" - "If you proceed, a new account using your credentials will be " - "created on this server.\n" - "Please retype your password and click 'Register and Join' to " - "confirm account creation, or click 'Cancel' to abort."); - char info_text_buf[1024]; - porting::mt_snprintf(info_text_buf, sizeof(info_text_buf), - info_text_template.c_str(), m_playername.c_str()); - - std::wstring info_text_w = utf8_to_wide(info_text_buf); - gui::IGUIEditBox *e = new GUIEditBoxWithScrollBar(info_text_w.c_str(), - true, Environment, this, ID_intotext, rect2, false, true); - e->drop(); - e->setMultiLine(true); - e->setWordWrap(true); - e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); - } - - ypos += 200 * s; - { - core::rect<s32> rect2(0, 0, 540 * s, 30 * s); - rect2 += topleft_client + v2s32(30 * s, ypos); - gui::IGUIEditBox *e = Environment->addEditBox(m_pass_confirm.c_str(), - rect2, true, this, ID_confirmPassword); - e->setPasswordBox(true); - Environment->setFocus(e); - } - - ypos += 50 * s; - { - core::rect<s32> rect2(0, 0, 230 * s, 35 * s); - rect2 = rect2 + v2s32(size.X / 2 - 220 * s, ypos); - text = wgettext("Register and Join"); - GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_confirm, text); - delete[] text; - } - { - core::rect<s32> rect2(0, 0, 120 * s, 35 * s); - rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos); - text = wgettext("Cancel"); - GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_cancel, text); - delete[] text; - } - { - core::rect<s32> rect2(0, 0, 500 * s, 40 * s); - rect2 += topleft_client + v2s32(30 * s, ypos + 40 * s); - text = wgettext("Passwords do not match!"); - IGUIElement *e = Environment->addStaticText( - text, rect2, false, true, this, ID_message); - e->setVisible(false); - delete[] text; - } -} - -void GUIConfirmRegistration::drawMenu() -{ - gui::IGUISkin *skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver *driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140, 0, 0, 0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); - - gui::IGUIElement::draw(); -#ifdef __ANDROID__ - getAndroidUIInput(); -#endif -} - -void GUIConfirmRegistration::closeMenu(bool goNext) -{ - if (goNext) { - m_client->confirmRegistration(); - } else { - *m_aborted = true; - infostream << "Connect aborted [Escape]" << std::endl; - } - quitMenu(); -} - -void GUIConfirmRegistration::acceptInput() -{ - gui::IGUIElement *e; - e = getElementFromId(ID_confirmPassword); - if (e) - m_pass_confirm = e->getText(); -} - -bool GUIConfirmRegistration::processInput() -{ - if (utf8_to_wide(m_password) != m_pass_confirm) { - gui::IGUIElement *e = getElementFromId(ID_message); - if (e) - e->setVisible(true); - return false; - } - return true; -} - -bool GUIConfirmRegistration::OnEvent(const SEvent &event) -{ - if (event.EventType == EET_KEY_INPUT_EVENT) { - // clang-format off - if ((event.KeyInput.Key == KEY_ESCAPE || - event.KeyInput.Key == KEY_CANCEL) && - event.KeyInput.PressedDown) { - closeMenu(false); - return true; - } - // clang-format on - if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) { - acceptInput(); - if (processInput()) - closeMenu(true); - return true; - } - } - - if (event.EventType != EET_GUI_EVENT) - return Parent ? Parent->OnEvent(event) : false; - - if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { - if (!canTakeFocus(event.GUIEvent.Element)) { - infostream << "GUIConfirmRegistration: Not allowing focus change." - << std::endl; - // Returning true disables focus change - return true; - } - } else if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { - switch (event.GUIEvent.Caller->getID()) { - case ID_confirm: - acceptInput(); - if (processInput()) - closeMenu(true); - return true; - case ID_cancel: - closeMenu(false); - return true; - } - } else if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { - switch (event.GUIEvent.Caller->getID()) { - case ID_confirmPassword: - acceptInput(); - if (processInput()) - closeMenu(true); - return true; - } - } - - return false; -} - -#ifdef __ANDROID__ -bool GUIConfirmRegistration::getAndroidUIInput() -{ - if (!hasAndroidUIInput() || m_jni_field_name != "password") - return false; - - // still waiting - if (porting::getInputDialogState() == -1) - return true; - - m_jni_field_name.clear(); - - gui::IGUIElement *e = getElementFromId(ID_confirmPassword); - - if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) - return false; - - std::string text = porting::getInputDialogValue(); - e->setText(utf8_to_wide(text).c_str()); - return false; -} -#endif diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h deleted file mode 100644 index d8387201d..000000000 --- a/src/gui/guiConfirmRegistration.h +++ /dev/null @@ -1,68 +0,0 @@ -/* -Minetest -Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto - <muhammadrifqipriyosusanto@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#pragma once - -#include "irrlichttypes_extrabloated.h" -#include "modalMenu.h" -#include <string> - -class Client; -class ISimpleTextureSource; - -class GUIConfirmRegistration : public GUIModalMenu -{ -public: - GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, IMenuManager *menumgr, Client *client, - const std::string &playername, const std::string &password, - bool *aborted, ISimpleTextureSource *tsrc); - ~GUIConfirmRegistration(); - - void removeChildren(); - /* - Remove and re-add (or reposition) stuff - */ - void regenerateGui(v2u32 screensize); - - void drawMenu(); - - void closeMenu(bool goNext); - - void acceptInput(); - - bool processInput(); - - bool OnEvent(const SEvent &event); -#ifdef __ANDROID__ - bool getAndroidUIInput(); -#endif - -private: - std::wstring getLabelByID(s32 id) { return L""; } - std::string getNameByID(s32 id) { return "password"; } - - Client *m_client = nullptr; - const std::string &m_playername; - const std::string &m_password; - bool *m_aborted = nullptr; - std::wstring m_pass_confirm = L""; - ISimpleTextureSource *m_tsrc; -}; diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b65b31304..01f3f8fd5 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -622,9 +622,9 @@ void GUIEngine::updateTopLeftTextSize() } /******************************************************************************/ -s32 GUIEngine::playSound(const SimpleSoundSpec &spec, bool looped) +s32 GUIEngine::playSound(const SimpleSoundSpec &spec) { - s32 handle = m_sound_manager->playSound(spec, looped); + s32 handle = m_sound_manager->playSound(spec); return handle; } diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index d7e6485ef..2f182ca81 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -296,7 +296,7 @@ private: clouddata m_cloud; /** start playing a sound and return handle */ - s32 playSound(const SimpleSoundSpec &spec, bool looped); + s32 playSound(const SimpleSoundSpec &spec); /** stop playing a sound started with playSound() */ void stopSound(s32 handle); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 85bd04900..1f9914e72 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -127,7 +127,8 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, GUIFormSpecMenu::~GUIFormSpecMenu() { - removeChildren(); + removeAllChildren(); + removeTooltip(); for (auto &table_it : m_tables) table_it.second->drop(); @@ -137,8 +138,6 @@ GUIFormSpecMenu::~GUIFormSpecMenu() checkbox_it.second->drop(); for (auto &scrollbar_it : m_scrollbars) scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); for (auto &tooltip_rect_it : m_tooltip_rects) tooltip_rect_it.first->drop(); for (auto &clickthrough_it : m_clickthrough_elements) @@ -176,14 +175,8 @@ void GUIFormSpecMenu::create(GUIFormSpecMenu *&cur_formspec, Client *client, } } -void GUIFormSpecMenu::removeChildren() +void GUIFormSpecMenu::removeTooltip() { - const core::list<gui::IGUIElement*> &children = getChildren(); - - while (!children.empty()) { - (*children.getLast())->remove(); - } - if (m_tooltip_element) { m_tooltip_element->remove(); m_tooltip_element->drop(); @@ -201,16 +194,7 @@ void GUIFormSpecMenu::setInitialFocus() // 5. first focusable (not statictext, not tabheader) // 6. first child element - core::list<gui::IGUIElement*> children = getChildren(); - - // in case "children" contains any NULL elements, remove them - for (core::list<gui::IGUIElement*>::Iterator it = children.begin(); - it != children.end();) { - if (*it) - ++it; - else - it = children.erase(it); - } + const auto& children = getChildren(); // 1. first empty editbox for (gui::IGUIElement *it : children) { @@ -238,8 +222,7 @@ void GUIFormSpecMenu::setInitialFocus() } // 4. last button - for (core::list<gui::IGUIElement*>::Iterator it = children.getLast(); - it != children.end(); --it) { + for (auto it = children.rbegin(); it != children.rend(); ++it) { if ((*it)->getType() == gui::EGUIET_BUTTON) { Environment->setFocus(*it); return; @@ -259,7 +242,7 @@ void GUIFormSpecMenu::setInitialFocus() if (children.empty()) Environment->setFocus(this); else - Environment->setFocus(*(children.begin())); + Environment->setFocus(children.front()); } GUITable* GUIFormSpecMenu::getTable(const std::string &tablename) @@ -784,101 +767,99 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) { std::vector<std::string> parts; - if (!precheckElement("image", element, 2, 3, parts)) + if (!precheckElement("image", element, 2, 4, parts)) return; - if (parts.size() >= 3) { - std::vector<std::string> v_pos = split(parts[0],','); - std::vector<std::string> v_geom = split(parts[1],','); - std::string name = unescape_string(parts[2]); + size_t offset = parts.size() >= 3; + + std::vector<std::string> v_pos = split(parts[0],','); + MY_CHECKPOS("image", 0); - MY_CHECKPOS("image", 0); + std::vector<std::string> v_geom; + if (parts.size() >= 3) { + v_geom = split(parts[1],','); MY_CHECKGEOM("image", 1); + } - v2s32 pos; - v2s32 geom; + std::string name = unescape_string(parts[1 + offset]); + video::ITexture *texture = m_tsrc->getTexture(name); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); + v2s32 pos; + v2s32 geom; + + if (parts.size() < 3) { + if (texture != nullptr) { + core::dimension2du dim = texture->getOriginalSize(); + geom.X = dim.Width; + geom.Y = dim.Height; } else { - pos = getElementBasePos(&v_pos); - geom.X = stof(v_geom[0]) * (float)imgsize.X; - geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + geom = v2s32(0); } + } - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<<std::endl; - - video::ITexture *texture = m_tsrc->getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + if (parts.size() >= 3) + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + if (parts.size() >= 3) { + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } - - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size(), - 1 - ); - core::rect<s32> rect(pos, pos + geom); - gui::IGUIImage *e = Environment->addImage(rect, data->current_parent, - spec.fid, 0, true); - e->setImage(texture); - e->setScaleImage(true); - auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); - - // images should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; } - // Else: 2 arguments in "parts" - - std::vector<std::string> v_pos = split(parts[0],','); - std::string name = unescape_string(parts[1]); - - MY_CHECKPOS("image", 0); - - v2s32 pos = getElementBasePos(&v_pos); - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<<std::endl; - - video::ITexture *texture = m_tsrc->getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } + warningstream << "Invalid use of image without a size[] element" << std::endl; FieldSpec spec( name, L"", L"", - 258 + m_fields.size() + 258 + m_fields.size(), + 1 ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, - data->current_parent, spec.fid, 0); + + core::rect<s32> rect = core::rect<s32>(pos, pos + geom); + + core::rect<s32> middle; + if (parts.size() >= 4) + parseMiddleRect(parts[3], &middle); + + // Temporary fix for issue #12581 in 5.6.0. + // Use legacy image when not rendering 9-slice image because GUIAnimatedImage + // uses NNAA filter which causes visual artifacts when image uses alpha blending. + + gui::IGUIElement *e; + if (middle.getArea() > 0) { + GUIAnimatedImage *image = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); + + image->setTexture(texture); + image->setMiddleRect(middle); + e = image; + } + else { + gui::IGUIImage *image = Environment->addImage(rect, data->current_parent, spec.fid, nullptr, true); + image->setImage(texture); + image->setScaleImage(true); + image->grab(); // compensate for drop in addImage + e = image; + } + auto style = getDefaultStyleForElement("image", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); - // images should let events through - e->grab(); + // Animated images should let events through m_clickthrough_elements.push_back(e); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) { std::vector<std::string> parts; - if (!precheckElement("animated_image", element, 6, 7, parts)) + if (!precheckElement("animated_image", element, 6, 8, parts)) return; std::vector<std::string> v_pos = split(parts[0], ','); @@ -904,7 +885,8 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el } if (!data->explicit_size) - warningstream << "Invalid use of animated_image without a size[] element" << std::endl; + warningstream << "Invalid use of animated_image without a size[] element" + << std::endl; FieldSpec spec( name, @@ -917,9 +899,17 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el core::rect<s32> rect = core::rect<s32>(pos, pos + geom); - GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, spec.fid, - rect, texture_name, frame_count, frame_duration, m_tsrc); + core::rect<s32> middle; + if (parts.size() >= 8) + parseMiddleRect(parts[7], &middle); + + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); + e->setTexture(m_tsrc->getTexture(texture_name)); + e->setMiddleRect(middle); + e->setFrameDuration(frame_duration); + e->setFrameCount(frame_count); if (parts.size() >= 7) e->setFrameIndex(stoi(parts[6]) - 1); @@ -1044,6 +1034,35 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, m_fields.push_back(spec); } +bool GUIFormSpecMenu::parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect) +{ + core::rect<s32> rect; + std::vector<std::string> v_rect = split(value, ','); + + if (v_rect.size() == 1) { + s32 x = stoi(v_rect[0]); + rect.UpperLeftCorner = core::vector2di(x, x); + rect.LowerRightCorner = core::vector2di(-x, -x); + } else if (v_rect.size() == 2) { + s32 x = stoi(v_rect[0]); + s32 y = stoi(v_rect[1]); + rect.UpperLeftCorner = core::vector2di(x, y); + rect.LowerRightCorner = core::vector2di(-x, -y); + // `-x` is interpreted as `w - x` + } else if (v_rect.size() == 4) { + rect.UpperLeftCorner = core::vector2di(stoi(v_rect[0]), stoi(v_rect[1])); + rect.LowerRightCorner = core::vector2di(stoi(v_rect[2]), stoi(v_rect[3])); + } else { + warningstream << "Invalid rectangle string format: \"" << value + << "\"" << std::endl; + return false; + } + + *parsed_rect = rect; + + return true; +} + void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element) { std::vector<std::string> parts; @@ -1085,25 +1104,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme } core::rect<s32> middle; - if (parts.size() >= 5) { - std::vector<std::string> v_middle = split(parts[4], ','); - if (v_middle.size() == 1) { - s32 x = stoi(v_middle[0]); - middle.UpperLeftCorner = core::vector2di(x, x); - middle.LowerRightCorner = core::vector2di(-x, -x); - } else if (v_middle.size() == 2) { - s32 x = stoi(v_middle[0]); - s32 y = stoi(v_middle[1]); - middle.UpperLeftCorner = core::vector2di(x, y); - middle.LowerRightCorner = core::vector2di(-x, -y); - // `-x` is interpreted as `w - x` - } else if (v_middle.size() == 4) { - middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); - middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); - } else { - warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; - } - } + if (parts.size() >= 5) + parseMiddleRect(parts[4], &middle); if (!data->explicit_size && !clip) warningstream << "invalid use of unclipped background without a size[] element" << std::endl; @@ -1124,17 +1126,15 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme rect = core::rect<s32>(-pos, pos); } - GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid, - rect, name, middle, m_tsrc, clip); + GUIBackgroundImage *e = new GUIBackgroundImage(Environment, data->background_parent.get(), + spec.fid, rect, name, middle, m_tsrc, clip); FATAL_ERROR_IF(!e, "Failed to create background formspec element"); e->setNotClipped(true); - e->setVisible(false); // the element is drawn manually before all others - - m_backgrounds.push_back(e); m_fields.push_back(spec); + e->drop(); } void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element) @@ -1686,7 +1686,7 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element) { - MY_CHECKCLIENT("list"); + MY_CHECKCLIENT("hypertext"); std::vector<std::string> parts; if (!precheckElement("hypertext", element, 4, 4, parts)) @@ -1746,25 +1746,27 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) return; std::vector<std::string> v_pos = split(parts[0],','); - std::string text = parts[1]; MY_CHECKPOS("label",0); if(!data->explicit_size) warningstream<<"invalid use of label without a size[] element"<<std::endl; - std::vector<std::string> lines = split(text, '\n'); - auto style = getDefaultStyleForElement("label", ""); gui::IGUIFont *font = style.getFont(); if (!font) font = m_font; - for (unsigned int i = 0; i != lines.size(); i++) { - std::wstring wlabel_colors = translate_string( - utf8_to_wide(unescape_string(lines[i]))); - // Without color escapes to get the font dimensions - std::wstring wlabel_plain = unescape_enriched(wlabel_colors); + EnrichedString str(unescape_string(utf8_to_wide(parts[1]))); + size_t str_pos = 0; + + for (size_t i = 0; str_pos < str.size(); ++i) { + // Split per line + size_t str_nl = str.getString().find(L'\n', str_pos); + if (str_nl == std::wstring::npos) + str_nl = str.getString().size(); + EnrichedString line = str.substr(str_pos, str_nl - str_pos); + str_pos += line.size() + 1; core::rect<s32> rect; @@ -1781,7 +1783,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) rect = core::rect<s32>( pos.X, pos.Y, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, + pos.X + font->getDimension(line.c_str()).Width, pos.Y + imgsize.Y); } else { @@ -1803,19 +1805,19 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) rect = core::rect<s32>( pos.X, pos.Y - m_btn_height, - pos.X + font->getDimension(wlabel_plain.c_str()).Width, + pos.X + font->getDimension(line.c_str()).Width, pos.Y + m_btn_height); } FieldSpec spec( "", - wlabel_colors, + L"", L"", 258 + m_fields.size(), 4 ); gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, data->current_parent, + line, rect, false, false, data->current_parent, spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); @@ -3049,7 +3051,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } // Remove children - removeChildren(); + removeAllChildren(); + removeTooltip(); for (auto &table_it : m_tables) table_it.second->drop(); @@ -3059,8 +3062,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) checkbox_it.second->drop(); for (auto &scrollbar_it : m_scrollbars) scrollbar_it.second->drop(); - for (auto &background_it : m_backgrounds) - background_it->drop(); for (auto &tooltip_rect_it : m_tooltip_rects) tooltip_rect_it.first->drop(); for (auto &clickthrough_it : m_clickthrough_elements) @@ -3082,7 +3083,6 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) mydata.current_parent = this; m_inventorylists.clear(); - m_backgrounds.clear(); m_tables.clear(); m_checkboxes.clear(); m_scrollbars.clear(); @@ -3229,8 +3229,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) offset = v2s32(0,0); } - double gui_scaling = g_settings->getFloat("gui_scaling"); - double screen_dpi = RenderingEngine::getDisplayDensity() * 96; + const double gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 42.0f); + const double screen_dpi = RenderingEngine::getDisplayDensity() * 96; double use_imgsize; if (m_lock) { @@ -3336,10 +3336,19 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); + // Add a new element that will hold all the background elements as its children. + // Because it is the first added element, all backgrounds will be behind all + // the other elements. + // (We use an arbitrarily big rect. The actual size is determined later by + // clipping to `this`.) + core::rect<s32> background_parent_rect(0, 0, 100000, 100000); + mydata.background_parent.reset(new gui::IGUIElement(EGUIET_ELEMENT, Environment, + this, -1, background_parent_rect)); + pos_offset = v2f32(); // used for formspec versions < 3 - core::list<IGUIElement *>::Iterator legacy_sort_start = Children.getLast(); + std::list<IGUIElement *>::iterator legacy_sort_start = std::prev(Children.end()); // last element if (enable_prepends) { // Backup the coordinates so that prepends can use the coordinates of choice. @@ -3354,7 +3363,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // legacy sorting for formspec versions < 3 if (m_formspec_version >= 3) // prepends do not need to be reordered - legacy_sort_start = Children.getLast(); + legacy_sort_start = std::prev(Children.end()); // last element else if (version_backup >= 3) // only prepends elements have to be reordered legacySortElements(legacy_sort_start); @@ -3435,7 +3444,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) } } -void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from) +void GUIFormSpecMenu::legacySortElements(std::list<IGUIElement *>::iterator from) { /* Draw order for formspec_version <= 2: @@ -3452,17 +3461,16 @@ void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator fro if (from == Children.end()) from = Children.begin(); else - from++; + ++from; - core::list<IGUIElement *>::Iterator to = Children.end(); + std::list<IGUIElement *>::iterator to = Children.end(); // 1: Copy into a sortable container - std::vector<IGUIElement *> elements; - for (auto it = from; it != to; ++it) - elements.emplace_back(*it); + std::vector<IGUIElement *> elements(from, to); // 2: Sort the container std::stable_sort(elements.begin(), elements.end(), [this] (const IGUIElement *a, const IGUIElement *b) -> bool { + // TODO: getSpecByID is a linear search. It should made O(1), or cached here. const FieldSpec *spec_a = getSpecByID(a->getID()); const FieldSpec *spec_b = getSpecByID(b->getID()); return spec_a && spec_b && @@ -3470,10 +3478,7 @@ void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator fro }); // 3: Re-assign the pointers - for (auto e : elements) { - *from = e; - from++; - } + reorderChildren(from, to, elements); } #ifdef __ANDROID__ @@ -3591,15 +3596,6 @@ void GUIFormSpecMenu::drawMenu() } } - /* - Draw backgrounds - */ - for (gui::IGUIElement *e : m_backgrounds) { - e->setVisible(true); - e->draw(); - e->setVisible(false); - } - // Some elements are only visible while being drawn for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(true); @@ -3607,12 +3603,11 @@ void GUIFormSpecMenu::drawMenu() /* This is where all the drawing happens. */ - core::list<IGUIElement*>::Iterator it = Children.begin(); - for (; it != Children.end(); ++it) - if ((*it)->isNotClipped() || + for (auto child : Children) + if (child->isNotClipped() || AbsoluteClippingRect.isRectCollided( - (*it)->getAbsolutePosition())) - (*it)->draw(); + child->getAbsolutePosition())) + child->draw(); for (gui::IGUIElement *e : m_clickthrough_elements) e->setVisible(false); @@ -4520,7 +4515,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(); s.send = false; @@ -4565,7 +4560,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (s.ftype == f_Button || s.ftype == f_CheckBox) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; if (s.is_exit) { @@ -4590,7 +4585,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(quit_mode_no); @@ -4608,7 +4603,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.fdefault = L""; } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { if (!s.sound.empty() && m_sound_manager) - m_sound_manager->playSound(s.sound, false, 1.0f); + m_sound_manager->playSound(SimpleSoundSpec(s.sound, 1.0f)); s.send = true; acceptInput(); s.send = false; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 0b4d3879d..c01ff817b 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <unordered_set> #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "inventorymanager.h" #include "modalMenu.h" #include "guiInventoryList.h" @@ -211,7 +212,7 @@ public: m_lockscreensize = basescreensize; } - void removeChildren(); + void removeTooltip(); void setInitialFocus(); void setFocus(const std::string &elementname) @@ -313,7 +314,6 @@ protected: std::vector<GUIInventoryList *> m_inventorylists; std::vector<ListRingSpec> m_inventory_rings; - std::vector<gui::IGUIElement *> m_backgrounds; std::unordered_map<std::string, bool> field_close_on_enter; std::unordered_map<std::string, bool> m_dropdown_index_event; std::vector<FieldSpec> m_fields; @@ -375,6 +375,7 @@ private: GUITable::TableOptions table_options; GUITable::TableColumns table_columns; gui::IGUIElement *current_parent = nullptr; + irr_ptr<gui::IGUIElement> background_parent; GUIInventoryList::Options inventorylist_options; @@ -456,6 +457,8 @@ private: void parseSetFocus(const std::string &element); void parseModel(parserData *data, const std::string &element); + bool parseMiddleRect(const std::string &value, core::rect<s32> *parsed_rect); + void tryClose(); void showTooltip(const std::wstring &text, const irr::video::SColor &color, @@ -466,7 +469,7 @@ private: * types were drawn before others. * This function sorts the elements in the old order for backwards compatibility. */ - void legacySortElements(core::list<IGUIElement *>::Iterator from); + void legacySortElements(std::list<IGUIElement *>::iterator from); int m_btn_height; gui::IGUIFont *m_font = nullptr; diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 29d5138f0..021f5f0a9 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -93,7 +93,8 @@ GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, GUIKeyChangeMenu::~GUIKeyChangeMenu() { - removeChildren(); + removeAllChildren(); + key_used_text = nullptr; for (key_setting *ks : key_settings) { delete[] ks->button_name; @@ -102,23 +103,10 @@ GUIKeyChangeMenu::~GUIKeyChangeMenu() key_settings.clear(); } -void GUIKeyChangeMenu::removeChildren() -{ - const core::list<gui::IGUIElement*> &children = getChildren(); - core::list<gui::IGUIElement*> children_copy; - for (gui::IGUIElement*i : children) { - children_copy.push_back(i); - } - - for (gui::IGUIElement *i : children_copy) { - i->remove(); - } - key_used_text = nullptr; -} - void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { - removeChildren(); + removeAllChildren(); + key_used_text = nullptr; const float s = m_gui_scale; DesiredRect = core::rect<s32>( @@ -136,7 +124,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 600 * s, 40 * s); rect += topleft + v2s32(25 * s, 3 * s); //gui::IGUIStaticText *t = - const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"); + const wchar_t *text = wgettext("Keybindings."); Environment->addStaticText(text, rect, false, true, this, -1); delete[] text; diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h index 1c0f40247..84a898774 100644 --- a/src/gui/guiKeyChangeMenu.h +++ b/src/gui/guiKeyChangeMenu.h @@ -46,7 +46,6 @@ public: IMenuManager *menumgr, ISimpleTextureSource *tsrc); ~GUIKeyChangeMenu(); - void removeChildren(); /* Remove and re-add (or reposition) stuff */ diff --git a/src/gui/guiMainMenu.h b/src/gui/guiMainMenu.h index 1dca8bf2d..9b8ff383c 100644 --- a/src/gui/guiMainMenu.h +++ b/src/gui/guiMainMenu.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include "gameparams.h" #include <string> #include <list> @@ -50,5 +51,7 @@ struct MainMenuData { // Data to be passed to the script MainMenuDataForScript script_data; + ELoginRegister allow_login_or_register = ELoginRegister::Any; + MainMenuData() = default; }; diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index c983260f6..c39df176b 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -51,23 +51,6 @@ GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env, { } -GUIPasswordChange::~GUIPasswordChange() -{ - removeChildren(); -} - -void GUIPasswordChange::removeChildren() -{ - const core::list<gui::IGUIElement *> &children = getChildren(); - core::list<gui::IGUIElement *> children_copy; - for (gui::IGUIElement *i : children) { - children_copy.push_back(i); - } - - for (gui::IGUIElement *i : children_copy) { - i->remove(); - } -} void GUIPasswordChange::regenerateGui(v2u32 screensize) { /* @@ -78,7 +61,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) /* Remove stuff */ - removeChildren(); + removeAllChildren(); /* Calculate new sizes and positions diff --git a/src/gui/guiPasswordChange.h b/src/gui/guiPasswordChange.h index 7141100c0..452702add 100644 --- a/src/gui/guiPasswordChange.h +++ b/src/gui/guiPasswordChange.h @@ -31,9 +31,7 @@ public: GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, ISimpleTextureSource *tsrc); - ~GUIPasswordChange(); - void removeChildren(); /* Remove and re-add (or reposition) stuff */ diff --git a/src/gui/guiPathSelectMenu.cpp b/src/gui/guiPathSelectMenu.cpp index 489927a11..9c63e06b5 100644 --- a/src/gui/guiPathSelectMenu.cpp +++ b/src/gui/guiPathSelectMenu.cpp @@ -32,13 +32,12 @@ GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, GUIFileSelectMenu::~GUIFileSelectMenu() { - removeChildren(); setlocale(LC_NUMERIC, "C"); } void GUIFileSelectMenu::regenerateGui(v2u32 screensize) { - removeChildren(); + removeAllChildren(); m_fileOpenDialog = 0; core::dimension2du size(600 * m_gui_scale, 400 * m_gui_scale); diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp index 0fe4c41bd..2d71f3453 100644 --- a/src/gui/guiScrollContainer.cpp +++ b/src/gui/guiScrollContainer.cpp @@ -59,12 +59,11 @@ bool GUIScrollContainer::OnEvent(const SEvent &event) void GUIScrollContainer::draw() { if (isVisible()) { - core::list<IGUIElement *>::Iterator it = Children.begin(); - for (; it != Children.end(); ++it) - if ((*it)->isNotClipped() || + for (auto child : Children) + if (child->isNotClipped() || AbsoluteClippingRect.isRectCollided( - (*it)->getAbsolutePosition())) - (*it)->draw(); + child->getAbsolutePosition())) + child->draw(); } } diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index 79ae1aea3..3929d678b 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -84,7 +84,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env, #endif core::rect<s32> relative_rect = m_scrollbar->getRelativePosition(); s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * density * - g_settings->getFloat("gui_scaling"); + g_settings->getFloat("gui_scaling", 0.5f, 20.0f); m_scrollbar->setRelativePosition(core::rect<s32>( relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y, relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index 61ab758a1..0f6f43fe9 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -45,32 +45,12 @@ GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env, { } -GUIVolumeChange::~GUIVolumeChange() -{ - removeChildren(); -} - -void GUIVolumeChange::removeChildren() -{ - if (gui::IGUIElement *e = getElementFromId(ID_soundText)) - e->remove(); - - if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton)) - e->remove(); - - if (gui::IGUIElement *e = getElementFromId(ID_soundSlider)) - e->remove(); - - if (gui::IGUIElement *e = getElementFromId(ID_soundMuteButton)) - e->remove(); -} - void GUIVolumeChange::regenerateGui(v2u32 screensize) { /* Remove stuff */ - removeChildren(); + removeAllChildren(); /* Calculate new sizes and positions */ diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h index 466e17f9d..ccdaca00b 100644 --- a/src/gui/guiVolumeChange.h +++ b/src/gui/guiVolumeChange.h @@ -31,9 +31,6 @@ public: GUIVolumeChange(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, ISimpleTextureSource *tsrc); - ~GUIVolumeChange(); - - void removeChildren(); /* Remove and re-add (or reposition) stuff */ diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 102492255..76d357340 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -64,10 +64,6 @@ public: // Remove all entries if there are duplicates m_stack.remove(menu); - /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast(); - assert(*i == menu); - m_stack.erase(i);*/ - if(!m_stack.empty()) m_stack.back()->setVisible(true); } diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 56a5d2cb9..cfc4d5c5a 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -40,7 +40,7 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, m_menumgr(menumgr), m_remap_dbl_click(remap_dbl_click) { - m_gui_scale = g_settings->getFloat("gui_scaling"); + m_gui_scale = std::max(g_settings->getFloat("gui_scaling"), 0.5f); #ifdef HAVE_TOUCHSCREENGUI float d = RenderingEngine::getDisplayDensity(); m_gui_scale *= 1.1 - 0.3 * d + 0.2 * d * d; @@ -108,19 +108,6 @@ void GUIModalMenu::quitMenu() #endif } -void GUIModalMenu::removeChildren() -{ - const core::list<gui::IGUIElement *> &children = getChildren(); - core::list<gui::IGUIElement *> children_copy; - for (gui::IGUIElement *i : children) { - children_copy.push_back(i); - } - - for (gui::IGUIElement *i : children_copy) { - i->remove(); - } -} - // clang-format off bool GUIModalMenu::DoubleClickDetection(const SEvent &event) { @@ -265,13 +252,6 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) return retval; m_jni_field_name = field_name; - /*~ Imperative, as in "Enter/type in text". - Don't forget the space. */ - std::string message = gettext("Enter "); - std::string label = wide_to_utf8(getLabelByID(hovered->getID())); - if (label.empty()) - label = "text"; - message += strgettext(label) + ":"; // single line text input int type = 2; diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 06e78f06b..e37c41533 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -47,7 +47,6 @@ public: bool canTakeFocus(gui::IGUIElement *e); void draw(); void quitMenu(); - void removeChildren(); virtual void regenerateGui(v2u32 screensize) = 0; virtual void drawMenu() = 0; diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index ebe1a6325..d483c136e 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -843,7 +843,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; // adapt to similar behaviour as pc screen - double d = g_settings->getFloat("mouse_sensitivity") * 3.0f; + const double d = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f) * 3.0f; m_camera_yaw_change -= dx * d; m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180); diff --git a/src/hud.cpp b/src/hud.cpp index e4ad7940f..841c90758 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -63,5 +63,6 @@ const struct EnumString es_HudBuiltinElement[] = {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, {HUD_FLAG_MINIMAP_RADAR_VISIBLE, "minimap_radar"}, + {HUD_FLAG_BASIC_DEBUG, "basic_debug"}, {0, NULL}, }; @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) #define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) #define HUD_FLAG_MINIMAP_RADAR_VISIBLE (1 << 6) +#define HUD_FLAG_BASIC_DEBUG (1 << 7) #define HUD_PARAM_HOTBAR_ITEMCOUNT 1 #define HUD_PARAM_HOTBAR_IMAGE 2 diff --git a/src/inventory.cpp b/src/inventory.cpp index d14b12f9d..6d2b7fba3 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -503,11 +503,6 @@ void InventoryList::deSerialize(std::istream &is) throw SerializationError(ss.str()); } -InventoryList::InventoryList(const InventoryList &other) -{ - *this = other; -} - InventoryList & InventoryList::operator = (const InventoryList &other) { m_items = other.m_items; @@ -535,21 +530,6 @@ bool InventoryList::operator == (const InventoryList &other) const return true; } -const std::string &InventoryList::getName() const -{ - return m_name; -} - -u32 InventoryList::getSize() const -{ - return m_items.size(); -} - -u32 InventoryList::getWidth() const -{ - return m_width; -} - u32 InventoryList::getUsedSlots() const { u32 num = 0; @@ -560,18 +540,6 @@ u32 InventoryList::getUsedSlots() const return num; } -const ItemStack& InventoryList::getItem(u32 i) const -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - -ItemStack& InventoryList::getItem(u32 i) -{ - assert(i < m_size); // Pre-condition - return m_items[i]; -} - ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) { if(i >= m_items.size()) @@ -960,16 +928,6 @@ InventoryList * Inventory::getList(const std::string &name) return m_lists[i]; } -std::vector<const InventoryList*> Inventory::getLists() -{ - std::vector<const InventoryList*> lists; - lists.reserve(m_lists.size()); - for (auto list : m_lists) { - lists.push_back(list); - } - return lists; -} - bool Inventory::deleteList(const std::string &name) { s32 i = getListIndex(name); diff --git a/src/inventory.h b/src/inventory.h index eb063d4ad..8b31de3a8 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -198,7 +198,7 @@ public: void serialize(std::ostream &os, bool incremental) const; void deSerialize(std::istream &is); - InventoryList(const InventoryList &other); + InventoryList(const InventoryList &other) { *this = other; } InventoryList & operator = (const InventoryList &other); bool operator == (const InventoryList &other) const; bool operator != (const InventoryList &other) const @@ -206,15 +206,25 @@ public: return !(*this == other); } - const std::string &getName() const; - u32 getSize() const; - u32 getWidth() const; + const std::string &getName() const { return m_name; } + u32 getSize() const { return static_cast<u32>(m_items.size()); } + u32 getWidth() const { return m_width; } // Count used slots u32 getUsedSlots() const; // Get reference to item - const ItemStack& getItem(u32 i) const; - ItemStack& getItem(u32 i); + const ItemStack &getItem(u32 i) const + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + ItemStack &getItem(u32 i) + { + assert(i < m_size); // Pre-condition + return m_items[i]; + } + // Get reference to all items + const std::vector<ItemStack> &getItems() const { return m_items; } // Returns old item. Parameter can be an empty item. ItemStack changeItem(u32 i, const ItemStack &newitem); // Delete item @@ -271,7 +281,7 @@ public: private: std::vector<ItemStack> m_items; std::string m_name; - u32 m_size; + u32 m_size; // always the same as m_items.size() u32 m_width = 0; IItemDefManager *m_itemdef; bool m_dirty = true; @@ -301,7 +311,7 @@ public: InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; - std::vector<const InventoryList*> getLists(); + const std::vector<InventoryList *> &getLists() const { return m_lists; } bool deleteList(const std::string &name); // A shorthand for adding items. Returns leftover item (possibly empty). ItemStack addItem(const std::string &listname, const ItemStack &newitem) diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index a159bf786..ecdb56a97 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -172,7 +172,7 @@ void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject * sa->player_inventory_OnPut(*this, src_item, player); else assert(false); - + if (from_inv.type == InventoryLocation::DETACHED) sa->detached_inventory_OnTake(*this, src_item, player); else if (from_inv.type == InventoryLocation::NODEMETA) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index e785ea837..53be27319 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -1,7 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman - Copyright (c) 2016 Nathanaël Courant + Copyright (c) 2016 Nathanaëlle Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -59,7 +59,7 @@ struct SGUITTFace : public virtual irr::IReferenceCounted // Static variables. FT_Library CGUITTFont::c_library; -core::map<io::path, SGUITTFace*> CGUITTFont::c_faces; +std::map<io::path, SGUITTFace*> CGUITTFont::c_faces; bool CGUITTFont::c_libraryLoaded = false; scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0; scene::SMesh CGUITTFont::shared_plane_; @@ -292,9 +292,6 @@ shadow_offset(0), shadow_alpha(0), fallback(0) Driver->grab(); setInvisibleCharacters(L" "); - - // Glyphs aren't reference counted, so don't try to delete them when we free the array. - Glyphs.set_free_when_destroyed(false); } bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency) @@ -320,11 +317,11 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia // Grab the face. SGUITTFace* face = 0; - core::map<io::path, SGUITTFace*>::Node* node = c_faces.find(filename); - if (node == 0) + auto node = c_faces.find(filename); + if (node == c_faces.end()) { face = new SGUITTFace(); - c_faces.set(filename, face); + c_faces.emplace(filename, face); if (filesystem) { @@ -334,7 +331,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia { if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); - c_faces.remove(filename); + c_faces.erase(filename); delete face; face = 0; return false; @@ -349,7 +346,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia { if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); - c_faces.remove(filename); + c_faces.erase(filename); delete face; face = 0; return false; @@ -362,7 +359,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia { if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); - c_faces.remove(filename); + c_faces.erase(filename); delete face; face = 0; return false; @@ -372,7 +369,7 @@ bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antia else { // Using another instance of this face. - face = node->getValue(); + face = node->second; face->grab(); } @@ -411,21 +408,20 @@ CGUITTFont::~CGUITTFont() { // Delete the glyphs and glyph pages. reset_images(); - CGUITTAssistDelete::Delete(Glyphs); - //Glyphs.clear(); + Glyphs.clear(); // We aren't using this face anymore. - core::map<io::path, SGUITTFace*>::Node* n = c_faces.find(filename); - if (n) + auto n = c_faces.find(filename); + if (n != c_faces.end()) { - SGUITTFace* f = n->getValue(); + SGUITTFace* f = n->second; // Drop our face. If this was the last face, the destructor will clean up. if (f->drop()) - c_faces.remove(filename); + c_faces.erase(filename); // If there are no more faces referenced by FreeType, clean up. - if (c_faces.size() == 0) + if (c_faces.empty()) { FT_Done_FreeType(c_library); c_libraryLoaded = false; @@ -478,7 +474,7 @@ CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) { CGUITTGlyphPage* page = 0; - + // Name of our page. io::path name("TTFontGlyphPage_"); name += tt_face->family_name; @@ -585,7 +581,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio core::ustring utext = text.getString(); // Set up our render map. - core::map<u32, CGUITTGlyphPage*> Render_Map; + std::map<u32, CGUITTGlyphPage*> Render_Map; // Start parsing characters. u32 n; @@ -640,7 +636,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio page->render_colors.push_back(colors[iter.getPos()]); else page->render_colors.push_back(video::SColor(255,255,255,255)); - Render_Map.set(glyph.glyph_page, page); + Render_Map[glyph.glyph_page] = page; } if (n > 0) { @@ -673,14 +669,14 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio // Draw now. update_glyph_pages(); - core::map<u32, CGUITTGlyphPage*>::Iterator j = Render_Map.getIterator(); - while (!j.atEnd()) + auto it = Render_Map.begin(); + auto ie = Render_Map.end(); + core::array<core::vector2di> tmp_positions; + core::array<core::recti> tmp_source_rects; + while (it != ie) { - core::map<u32, CGUITTGlyphPage*>::Node* n = j.getNode(); - j++; - if (n == 0) continue; - - CGUITTGlyphPage* page = n->getValue(); + CGUITTGlyphPage* page = it->second; + ++it; if (shadow_offset) { for (size_t i = 0; i < page->render_positions.size(); ++i) @@ -698,10 +694,8 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio do ++i; while (i < page->render_positions.size() && page->render_colors[i] == colprev); - core::array<core::vector2di> tmp_positions; - core::array<core::recti> tmp_source_rects; - tmp_positions.set_pointer(&page->render_positions[ibegin], i - ibegin, false, false); // no copy - tmp_source_rects.set_pointer(&page->render_source_rects[ibegin], i - ibegin, false, false); + tmp_positions.set_data(&page->render_positions[ibegin], i - ibegin); + tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin); --i; if (!use_transparency) diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index 7b04ae828..2721364f5 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -1,7 +1,7 @@ /* CGUITTFont FreeType class for Irrlicht Copyright (c) 2009-2010 John Norman - Copyright (c) 2016 Nathanaël Courant + Copyright (c) 2016 Nathanaëlle Courant This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any @@ -34,8 +34,10 @@ #include <irrlicht.h> #include <ft2build.h> #include <vector> +#include <map> #include <irrUString.h> #include "util/enriched_string.h" +#include "util/basic_macros.h" #include FT_FREETYPE_H namespace irr @@ -45,23 +47,34 @@ namespace gui struct SGUITTFace; class CGUITTFont; - //! Class to assist in deleting glyphs. - class CGUITTAssistDelete - { - public: - template <class T, typename TAlloc> - static void Delete(core::array<T, TAlloc>& a) - { - TAlloc allocator; - allocator.deallocate(a.pointer()); - } - }; - //! Structure representing a single TrueType glyph. struct SGUITTGlyph { //! Constructor. - SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} + SGUITTGlyph() : + isLoaded(false), + glyph_page(0), + source_rect(), + offset(), + advance(), + surface(0), + parent(0) + {} + + DISABLE_CLASS_COPY(SGUITTGlyph); + + //! This class would be trivially copyable except for the reference count on `surface`. + SGUITTGlyph(SGUITTGlyph &&other) : + isLoaded(other.isLoaded), + glyph_page(other.glyph_page), + source_rect(other.source_rect), + offset(other.offset), + advance(other.advance), + surface(other.surface), + parent(other.parent) + { + other.surface = 0; + } //! Destructor. ~SGUITTGlyph() { unload(); } @@ -345,7 +358,7 @@ namespace gui private: // Manages the FreeType library. static FT_Library c_library; - static core::map<io::path, SGUITTFace*> c_faces; + static std::map<io::path, SGUITTFace*> c_faces; static bool c_libraryLoaded; static scene::IMesh* shared_plane_ptr_; static scene::SMesh shared_plane_; diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index baf0ea626..a4749bab1 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2002-2012 Nikolaus Gebhardt -// Copyright (C) 2016 Nathanaël Courant: +// Copyright (C) 2016 Nathanaëlle 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 diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 74ef62008..fc58e3e90 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -1,5 +1,5 @@ // Copyright (C) 2002-2012 Nikolaus Gebhardt -// Copyright (C) 2016 Nathanaël Courant +// Copyright (C) 2016 Nathanaëlle 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 diff --git a/src/itemdef.cpp b/src/itemdef.cpp index d79d6b263..a34805b8e 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -154,8 +154,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const os << serializeString16(node_placement_prediction); // Version from ContentFeatures::serialize to keep in sync - sound_place.serialize(os, CONTENTFEATURES_VERSION); - sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); + sound_place.serialize(os, protocol_version); + sound_place_failed.serialize(os, protocol_version); writeF32(os, range); os << serializeString16(palette_image); @@ -168,7 +168,7 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const os << place_param2; } -void ItemDefinition::deSerialize(std::istream &is) +void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) { // Reset everything reset(); @@ -205,9 +205,8 @@ void ItemDefinition::deSerialize(std::istream &is) node_placement_prediction = deSerializeString16(is); - // Version from ContentFeatures::serialize to keep in sync - sound_place.deSerialize(is, CONTENTFEATURES_VERSION); - sound_place_failed.deSerialize(is, CONTENTFEATURES_VERSION); + sound_place.deSerialize(is, protocol_version); + sound_place_failed.deSerialize(is, protocol_version); range = readF32(is); palette_image = deSerializeString16(is); @@ -538,21 +537,21 @@ public: os << serializeString16(it.second); } } - void deSerialize(std::istream &is) + void deSerialize(std::istream &is, u16 protocol_version) { // Clear everything clear(); - // Deserialize - int version = readU8(is); - if(version != 0) + + if(readU8(is) != 0) throw SerializationError("unsupported ItemDefManager version"); + u16 count = readU16(is); for(u16 i=0; i<count; i++) { // Deserialize a string and grab an ItemDefinition from it std::istringstream tmp_is(deSerializeString16(is), std::ios::binary); ItemDefinition def; - def.deSerialize(tmp_is); + def.deSerialize(tmp_is, protocol_version); // Register registerItem(def); } diff --git a/src/itemdef.h b/src/itemdef.h index 3e302840f..035717379 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -97,7 +97,7 @@ struct ItemDefinition ~ItemDefinition(); void reset(); void serialize(std::ostream &os, u16 protocol_version) const; - void deSerialize(std::istream &is); + void deSerialize(std::istream &is, u16 protocol_version); private: void resetInitial(); }; @@ -177,7 +177,7 @@ public: const std::string &convert_to)=0; virtual void serialize(std::ostream &os, u16 protocol_version)=0; - virtual void deSerialize(std::istream &is)=0; + virtual void deSerialize(std::istream &is, u16 protocol_version)=0; // Do stuff asked by threads that can only be done in the main thread virtual void processQueue(IGameDef *gamedef)=0; diff --git a/src/lighting.h b/src/lighting.h new file mode 100644 index 000000000..e0d9cee09 --- /dev/null +++ b/src/lighting.h @@ -0,0 +1,27 @@ +/* +Minetest +Copyright (C) 2021 x2048, Dmitry Kostenko <codeforsmile@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +/** Describes ambient light settings for a player + */ +struct Lighting +{ + float shadow_intensity {0.0f}; +}; diff --git a/src/log.cpp b/src/log.cpp index 3c61414e9..ef998f161 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -35,43 +35,30 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cerrno> #include <cstring> -const int BUFFER_LENGTH = 256; - -class StringBuffer : public std::streambuf { +class LevelTarget : public LogTarget { public: - StringBuffer() { - buffer_index = 0; - } - - int overflow(int c); - virtual void flush(const std::string &buf) = 0; - std::streamsize xsputn(const char *s, std::streamsize n); - void push_back(char c); - -private: - char buffer[BUFFER_LENGTH]; - int buffer_index; -}; - - -class LogBuffer : public StringBuffer { -public: - LogBuffer(Logger &logger, LogLevel lev) : - logger(logger), - level(lev) + LevelTarget(Logger &logger, LogLevel level, bool raw = false) : + m_logger(logger), + m_level(level), + m_raw(raw) {} - void flush(const std::string &buffer); - -private: - Logger &logger; - LogLevel level; -}; + virtual bool hasOutput() override { + return m_logger.hasOutput(m_level); + } + virtual void log(const std::string &buf) override { + if (!m_raw) { + m_logger.log(m_level, buf); + } else { + m_logger.logRaw(m_level, buf); + } + } -class RawLogBuffer : public StringBuffer { -public: - void flush(const std::string &buffer); +private: + Logger &m_logger; + LogLevel m_level; + bool m_raw; }; //// @@ -80,31 +67,33 @@ public: Logger g_logger; +#ifdef __ANDROID__ +AndroidLogOutput stdout_output; +AndroidLogOutput stderr_output; +#else StreamLogOutput stdout_output(std::cout); StreamLogOutput stderr_output(std::cerr); -std::ostream null_stream(NULL); - -RawLogBuffer raw_buf; - -LogBuffer none_buf(g_logger, LL_NONE); -LogBuffer error_buf(g_logger, LL_ERROR); -LogBuffer warning_buf(g_logger, LL_WARNING); -LogBuffer action_buf(g_logger, LL_ACTION); -LogBuffer info_buf(g_logger, LL_INFO); -LogBuffer verbose_buf(g_logger, LL_VERBOSE); - -// Connection -std::ostream *dout_con_ptr = &null_stream; -std::ostream *derr_con_ptr = &verbosestream; - -// Common streams -std::ostream rawstream(&raw_buf); -std::ostream dstream(&none_buf); -std::ostream errorstream(&error_buf); -std::ostream warningstream(&warning_buf); -std::ostream actionstream(&action_buf); -std::ostream infostream(&info_buf); -std::ostream verbosestream(&verbose_buf); +#endif + +LevelTarget none_target_raw(g_logger, LL_NONE, true); +LevelTarget none_target(g_logger, LL_NONE); +LevelTarget error_target(g_logger, LL_ERROR); +LevelTarget warning_target(g_logger, LL_WARNING); +LevelTarget action_target(g_logger, LL_ACTION); +LevelTarget info_target(g_logger, LL_INFO); +LevelTarget verbose_target(g_logger, LL_VERBOSE); +LevelTarget trace_target(g_logger, LL_TRACE); + +thread_local LogStream dstream(none_target); +thread_local LogStream rawstream(none_target_raw); +thread_local LogStream errorstream(error_target); +thread_local LogStream warningstream(warning_target); +thread_local LogStream actionstream(action_target); +thread_local LogStream infostream(info_target); +thread_local LogStream verbosestream(verbose_target); +thread_local LogStream tracestream(trace_target); +thread_local LogStream derr_con(verbose_target); +thread_local LogStream dout_con(trace_target); // Android #ifdef __ANDROID__ @@ -118,29 +107,15 @@ static unsigned int g_level_to_android[] = { //ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, // LL_INFO ANDROID_LOG_VERBOSE, // LL_VERBOSE + ANDROID_LOG_VERBOSE, // LL_TRACE }; -class AndroidSystemLogOutput : public ICombinedLogOutput { - public: - AndroidSystemLogOutput() - { - g_logger.addOutput(this); - } - ~AndroidSystemLogOutput() - { - g_logger.removeOutput(this); - } - void logRaw(LogLevel lev, const std::string &line) - { - STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, - mismatch_between_android_and_internal_loglevels); - __android_log_print(g_level_to_android[lev], - PROJECT_NAME_C, "%s", line.c_str()); - } -}; - -AndroidSystemLogOutput g_android_log_output; - +void AndroidLogOutput::logRaw(LogLevel lev, const std::string &line) { + STATIC_ASSERT(ARRLEN(g_level_to_android) == LL_MAX, + mismatch_between_android_and_internal_loglevels); + __android_log_print(g_level_to_android[lev], + PROJECT_NAME_C, "%s", line.c_str()); +} #endif /////////////////////////////////////////////////////////////////////////////// @@ -164,6 +139,8 @@ LogLevel Logger::stringToLevel(const std::string &name) return LL_INFO; else if (name == "verbose") return LL_VERBOSE; + else if (name == "trace") + return LL_TRACE; else return LL_MAX; } @@ -175,26 +152,35 @@ void Logger::addOutput(ILogOutput *out) void Logger::addOutput(ILogOutput *out, LogLevel lev) { + MutexAutoLock lock(m_mutex); m_outputs[lev].push_back(out); + m_has_outputs[lev] = true; } void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask) { + MutexAutoLock lock(m_mutex); for (size_t i = 0; i < LL_MAX; i++) { - if (mask & LOGLEVEL_TO_MASKLEVEL(i)) + if (mask & LOGLEVEL_TO_MASKLEVEL(i)) { m_outputs[i].push_back(out); + m_has_outputs[i] = true; + } } } void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev) { + MutexAutoLock lock(m_mutex); assert(lev < LL_MAX); - for (size_t i = 0; i <= lev; i++) + for (size_t i = 0; i <= lev; i++) { m_outputs[i].push_back(out); + m_has_outputs[i] = true; + } } LogLevelMask Logger::removeOutput(ILogOutput *out) { + MutexAutoLock lock(m_mutex); LogLevelMask ret_mask = 0; for (size_t i = 0; i < LL_MAX; i++) { std::vector<ILogOutput *>::iterator it; @@ -203,6 +189,7 @@ LogLevelMask Logger::removeOutput(ILogOutput *out) if (it != m_outputs[i].end()) { ret_mask |= LOGLEVEL_TO_MASKLEVEL(i); m_outputs[i].erase(it); + m_has_outputs[i] = !m_outputs[i].empty(); } } return ret_mask; @@ -236,6 +223,7 @@ const std::string Logger::getLevelLabel(LogLevel lev) "ACTION", "INFO", "VERBOSE", + "TRACE", }; assert(lev < LL_MAX && lev >= 0); STATIC_ASSERT(ARRLEN(names) == LL_MAX, @@ -297,7 +285,6 @@ void Logger::logToOutputs(LogLevel lev, const std::string &combined, m_outputs[lev][i]->log(lev, combined, time, thread_name, payload_text); } - //// //// *LogOutput methods //// @@ -349,6 +336,7 @@ void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) m_stream << "\033[37m"; break; case LL_VERBOSE: + case LL_TRACE: // verbose is darker than info m_stream << "\033[2m"; break; @@ -396,55 +384,12 @@ void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line) color = "\x1b(c@#BBB)"; break; case LL_VERBOSE: // dark grey + case LL_TRACE: color = "\x1b(c@#888)"; break; default: break; } } - - m_buffer.push(color.append(line)); -} - -//// -//// *Buffer methods -//// - -int StringBuffer::overflow(int c) -{ - push_back(c); - return c; -} - - -std::streamsize StringBuffer::xsputn(const char *s, std::streamsize n) -{ - for (int i = 0; i < n; ++i) - push_back(s[i]); - return n; -} - -void StringBuffer::push_back(char c) -{ - if (c == '\n' || c == '\r') { - if (buffer_index) - flush(std::string(buffer, buffer_index)); - buffer_index = 0; - } else { - buffer[buffer_index++] = c; - if (buffer_index >= BUFFER_LENGTH) { - flush(std::string(buffer, buffer_index)); - buffer_index = 0; - } - } -} - - -void LogBuffer::flush(const std::string &buffer) -{ - logger.log(level, buffer); -} - -void RawLogBuffer::flush(const std::string &buffer) -{ - g_logger.logRaw(LL_NONE, buffer); + MutexAutoLock lock(m_buffer_mutex); + m_buffer.emplace(color.append(line)); } @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <atomic> #include <map> #include <queue> #include <string> @@ -28,6 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #if !defined(_WIN32) // POSIX #include <unistd.h> #endif +#include "threading/mutex_auto_lock.h" +#include "util/basic_macros.h" +#include "util/stream.h" #include "irrlichttypes.h" class ILogOutput; @@ -39,6 +43,7 @@ enum LogLevel { LL_ACTION, // In-game actions LL_INFO, LL_VERBOSE, + LL_TRACE, LL_MAX, }; @@ -67,12 +72,13 @@ public: // Logs without a prefix void logRaw(LogLevel lev, const std::string &text); - void setTraceEnabled(bool enable) { m_trace_enabled = enable; } - bool getTraceEnabled() { return m_trace_enabled; } - static LogLevel stringToLevel(const std::string &name); static const std::string getLevelLabel(LogLevel lev); + bool hasOutput(LogLevel level) { + return m_has_outputs[level].load(std::memory_order_relaxed); + } + static LogColor color_mode; private: @@ -84,6 +90,7 @@ private: const std::string getThreadName(); std::vector<ILogOutput *> m_outputs[LL_MAX]; + std::atomic<bool> m_has_outputs[LL_MAX]; // Should implement atomic loads and stores (even though it's only // written to when one thread has access currently). @@ -91,7 +98,6 @@ private: volatile bool m_silenced_levels[LL_MAX]; std::map<std::thread::id, std::string> m_thread_names; mutable std::mutex m_mutex; - bool m_trace_enabled; }; class ILogOutput { @@ -163,57 +169,207 @@ public: void clear() { + MutexAutoLock lock(m_buffer_mutex); m_buffer = std::queue<std::string>(); } bool empty() const { + MutexAutoLock lock(m_buffer_mutex); return m_buffer.empty(); } std::string get() { - if (empty()) + MutexAutoLock lock(m_buffer_mutex); + if (m_buffer.empty()) return ""; - std::string s = m_buffer.front(); + std::string s = std::move(m_buffer.front()); m_buffer.pop(); return s; } private: + // g_logger serializes calls to logRaw() with a mutex, but that + // doesn't prevent get() / clear() from being called on top of it. + // This mutex prevents that. + mutable std::mutex m_buffer_mutex; std::queue<std::string> m_buffer; Logger &m_logger; }; +#ifdef __ANDROID__ +class AndroidLogOutput : public ICombinedLogOutput { +public: + void logRaw(LogLevel lev, const std::string &line); +}; +#endif -extern StreamLogOutput stdout_output; -extern StreamLogOutput stderr_output; -extern std::ostream null_stream; +/* + * LogTarget + * + * This is the interface that sits between the LogStreams and the global logger. + * Primarily used to route streams to log levels, but could also enable other + * custom behavior. + * + */ +class LogTarget { +public: + // Must be thread-safe. These can be called from any thread. + virtual bool hasOutput() = 0; + virtual void log(const std::string &buf) = 0; +}; -extern std::ostream *dout_con_ptr; -extern std::ostream *derr_con_ptr; -extern std::ostream *derr_server_ptr; -extern Logger g_logger; +/* + * StreamProxy + * + * An ostream-like object that can proxy to a real ostream or do nothing, + * depending on how it is configured. See LogStream below. + * + */ +class StreamProxy { +public: + StreamProxy(std::ostream *os) : m_os(os) { } + + template<typename T> + StreamProxy& operator<<(T&& arg) { + if (m_os) { + *m_os << std::forward<T>(arg); + } + return *this; + } -// Writes directly to all LL_NONE log outputs for g_logger with no prefix. -extern std::ostream rawstream; + StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { + if (m_os) { + *m_os << manip; + } + return *this; + } -extern std::ostream errorstream; -extern std::ostream warningstream; -extern std::ostream actionstream; -extern std::ostream infostream; -extern std::ostream verbosestream; -extern std::ostream dstream; +private: + std::ostream *m_os; +}; -#define TRACEDO(x) do { \ - if (g_logger.getTraceEnabled()) { \ - x; \ - } \ -} while (0) -#define TRACESTREAM(x) TRACEDO(verbosestream x) +/* + * LogStream + * + * The public interface for log streams (infostream, verbosestream, etc). + * + * LogStream minimizes the work done when a given stream is off. (meaning + * it has no output targets, so it goes to /dev/null) + * + * For example, consider: + * + * verbosestream << "hello world" << 123 << std::endl; + * + * The compiler evaluates this as: + * + * (((verbosestream << "hello world") << 123) << std::endl) + * ^ ^ + * + * If `verbosestream` is on, the innermost expression (marked by ^) will return + * a StreamProxy that forwards to a real ostream, that feeds into the logger. + * However, if `verbosestream` is off, it will return a StreamProxy that does + * nothing on all later operations. Specifically, CPU time won't be wasted + * writing "hello world" and 123 into a buffer, or formatting the log entry. + * + * It is also possible to directly check if the stream is on/off: + * + * if (verbosestream) { + * auto data = ComputeExpensiveDataForTheLog(); + * verbosestream << data << endl; + * } + * +*/ -#define dout_con (*dout_con_ptr) -#define derr_con (*derr_con_ptr) +class LogStream { +public: + LogStream() = delete; + DISABLE_CLASS_COPY(LogStream); + + LogStream(LogTarget &target) : + m_target(target), + m_buffer(std::bind(&LogStream::internalFlush, this, std::placeholders::_1)), + m_dummy_buffer(), + m_stream(&m_buffer), + m_dummy_stream(&m_dummy_buffer), + m_proxy(&m_stream), + m_dummy_proxy(nullptr) { } + + template<typename T> + StreamProxy& operator<<(T&& arg) { + StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy; + sp << std::forward<T>(arg); + return sp; + } + StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { + StreamProxy& sp = m_target.hasOutput() ? m_proxy : m_dummy_proxy; + sp << manip; + return sp; + } + + operator bool() { + return m_target.hasOutput(); + } + + void internalFlush(const std::string &buf) { + m_target.log(buf); + } + + operator std::ostream&() { + return m_target.hasOutput() ? m_stream : m_dummy_stream; + } + +private: + // 10 streams per thread x (256 + overhead) ~ 3K per thread + static const int BUFFER_LENGTH = 256; + LogTarget &m_target; + StringStreamBuffer<BUFFER_LENGTH> m_buffer; + DummyStreamBuffer m_dummy_buffer; + std::ostream m_stream; + std::ostream m_dummy_stream; + StreamProxy m_proxy; + StreamProxy m_dummy_proxy; + +}; + +#ifdef __ANDROID__ +extern AndroidLogOutput stdout_output; +extern AndroidLogOutput stderr_output; +#else +extern StreamLogOutput stdout_output; +extern StreamLogOutput stderr_output; +#endif + +extern Logger g_logger; + +/* + * By making the streams thread_local, each thread has its own + * private buffer. Two or more threads can write to the same stream + * simultaneously (lock-free), and there won't be any interference. + * + * The finished lines are sent to a LogTarget which is a global (not thread-local) + * object, and from there relayed to g_logger. The final writes are serialized + * by the mutex in g_logger. +*/ + +extern thread_local LogStream dstream; +extern thread_local LogStream rawstream; // Writes directly to all LL_NONE log outputs with no prefix. +extern thread_local LogStream errorstream; +extern thread_local LogStream warningstream; +extern thread_local LogStream actionstream; +extern thread_local LogStream infostream; +extern thread_local LogStream verbosestream; +extern thread_local LogStream tracestream; +// TODO: Search/replace these with verbose/tracestream +extern thread_local LogStream derr_con; +extern thread_local LogStream dout_con; + +#define TRACESTREAM(x) do { \ + if (tracestream) { \ + tracestream x; \ + } \ +} while (0) diff --git a/src/main.cpp b/src/main.cpp index 5ea212d8a..ebd1f740e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" // must be included before anything irrlicht, see comment in the file #include "irrlicht.h" // createDevice #include "irrlichttypes_extrabloated.h" +#include "benchmark/benchmark.h" #include "chat_interface.h" #include "debug.h" #include "unittest/test.h" @@ -212,7 +213,19 @@ int main(int argc, char *argv[]) return 1; #endif } + + // Run benchmarks + if (cmd_args.getFlag("run-benchmarks")) { +#if BUILD_BENCHMARKS + return run_benchmarks(); +#else + errorstream << "Benchmark support is not enabled in this binary. " + << "If you want to enable it, compile project with BUILD_BENCHMARKS=1 flag." + << std::endl; + return 1; #endif + } +#endif // __ANDROID__ GameStartData game_params; #ifdef SERVER @@ -277,6 +290,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Set network port (UDP)")))); allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, _("Run the unit tests and exit")))); + allowed_options->insert(std::make_pair("run-benchmarks", ValueSpec(VALUETYPE_FLAG, + _("Run the benchmarks and exit")))); allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, _("Same as --world (deprecated)")))); allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, @@ -453,14 +468,6 @@ static bool setup_log_params(const Settings &cmd_args) } } - // If trace is enabled, enable logging of certain things - if (cmd_args.getFlag("trace")) { - dstream << _("Enabling trace level debug output") << std::endl; - g_logger.setTraceEnabled(true); - dout_con_ptr = &verbosestream; // This is somewhat old - socket_enable_debug_output = true; // Sockets doesn't use log.h - } - // In certain cases, output info level on stderr if (cmd_args.getFlag("info") || cmd_args.getFlag("verbose") || cmd_args.getFlag("trace") || cmd_args.getFlag("speedtests")) @@ -470,6 +477,12 @@ static bool setup_log_params(const Settings &cmd_args) if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace")) g_logger.addOutput(&stderr_output, LL_VERBOSE); + if (cmd_args.getFlag("trace")) { + dstream << _("Enabling trace level debug output") << std::endl; + g_logger.addOutput(&stderr_output, LL_TRACE); + socket_enable_debug_output = true; + } + return true; } @@ -599,7 +612,7 @@ static void init_log_streams(const Settings &cmd_args) warningstream << "Deprecated use of debug_log_level with an " "integer value; please update your configuration." << std::endl; static const char *lev_name[] = - {"", "error", "action", "info", "verbose"}; + {"", "error", "action", "info", "verbose", "trace"}; int lev_i = atoi(conf_loglev.c_str()); if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { warningstream << "Supplied invalid debug_log_level!" diff --git a/src/map.cpp b/src/map.cpp index a11bbb96a..a53680065 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -102,7 +102,7 @@ MapSector * Map::getSectorNoGenerateNoLock(v2s16 p) return sector; } - std::map<v2s16, MapSector*>::iterator n = m_sectors.find(p); + auto n = m_sectors.find(p); if (n == m_sectors.end()) return NULL; @@ -165,24 +165,32 @@ MapNode Map::getNode(v3s16 p, bool *is_valid_position) return node; } -// throws InvalidPositionException if not found -void Map::setNode(v3s16 p, MapNode & n) +static void set_node_in_block(MapBlock *block, v3s16 relpos, MapNode n) { - v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreate(blockpos); - v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; // Never allow placing CONTENT_IGNORE, it causes problems if(n.getContent() == CONTENT_IGNORE){ + const NodeDefManager *nodedef = block->getParent()->getNodeDefManager(); + v3s16 blockpos = block->getPos(); + v3s16 p = blockpos * MAP_BLOCKSIZE + relpos; bool temp_bool; - errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE" + errorstream<<"Not allowing to place CONTENT_IGNORE" <<" while trying to replace \"" - <<m_nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name + <<nodedef->get(block->getNodeNoCheck(relpos, &temp_bool)).name <<"\" at "<<PP(p)<<" (block "<<PP(blockpos)<<")"<<std::endl; return; } block->setNodeNoCheck(relpos, n); } +// throws InvalidPositionException if not found +void Map::setNode(v3s16 p, MapNode & n) +{ + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; + set_node_in_block(block, relpos, n); +} + void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map<v3s16, MapBlock*> &modified_blocks, bool remove_metadata) @@ -190,8 +198,14 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, // Collect old node for rollback RollbackNode rollback_oldnode(this, p, m_gamedef); + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + if (block->isDummy()) + throw InvalidPositionException(); + v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; + // This is needed for updating the lighting - MapNode oldnode = getNode(p); + MapNode oldnode = block->getNodeUnsafe(relpos); // Remove node metadata if (remove_metadata) { @@ -199,18 +213,29 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } // Set the node on the map - // Ignore light (because calling voxalgo::update_lighting_nodes) - n.setLight(LIGHTBANK_DAY, 0, m_nodedef); - n.setLight(LIGHTBANK_NIGHT, 0, m_nodedef); - setNode(p, n); + const ContentFeatures &cf = m_nodedef->get(n); + const ContentFeatures &oldcf = m_nodedef->get(oldnode); + if (cf.lightingEquivalent(oldcf)) { + // No light update needed, just copy over the old light. + n.setLight(LIGHTBANK_DAY, oldnode.getLightRaw(LIGHTBANK_DAY, oldcf), cf); + n.setLight(LIGHTBANK_NIGHT, oldnode.getLightRaw(LIGHTBANK_NIGHT, oldcf), cf); + set_node_in_block(block, relpos, n); + + modified_blocks[blockpos] = block; + } else { + // Ignore light (because calling voxalgo::update_lighting_nodes) + n.setLight(LIGHTBANK_DAY, 0, cf); + n.setLight(LIGHTBANK_NIGHT, 0, cf); + set_node_in_block(block, relpos, n); - // Update lighting - std::vector<std::pair<v3s16, MapNode> > oldnodes; - oldnodes.emplace_back(p, oldnode); - voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); + // Update lighting + std::vector<std::pair<v3s16, MapNode> > oldnodes; + oldnodes.emplace_back(p, oldnode); + voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); - for (auto &modified_block : modified_blocks) { - modified_block.second->expireDayNightDiff(); + for (auto &modified_block : modified_blocks) { + modified_block.second->expireDayNightDiff(); + } } // Report for rollback @@ -221,22 +246,6 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, 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, if it was removed.) - */ - - for (const v3s16 &dir : g_7dirs) { - v3s16 p2 = p + dir; - - bool is_valid_position; - MapNode n2 = getNode(p2, &is_valid_position); - if(is_valid_position && - (m_nodedef->get(n2).isLiquid() || - n2.getContent() == CONTENT_AIR)) - m_transforming_liquid.push_back(p2); - } } void Map::removeNodeAndUpdate(v3s16 p, @@ -314,10 +323,10 @@ struct TimeOrderedMapBlock { /* Updates usage timers */ -void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, +void Map::timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks, std::vector<v3s16> *unloaded_blocks) { - bool save_before_unloading = (mapType() == MAPTYPE_SERVER); + bool save_before_unloading = maySaveBlocks(); // Profile modified reasons Profiler modprofiler; @@ -327,10 +336,11 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, u32 saved_blocks_count = 0; u32 block_count_all = 0; + const auto start_time = porting::getTimeUs(); beginSave(); // If there is no practical limit, we spare creation of mapblock_queue - if (max_loaded_blocks == U32_MAX) { + if (max_loaded_blocks < 0) { for (auto §or_it : m_sectors) { MapSector *sector = sector_it.second; @@ -368,6 +378,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } + // Delete sector if we emptied it if (all_blocks_deleted) { sector_deletion_queue.push_back(sector_it.first); } @@ -386,8 +397,9 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } block_count_all = mapblock_queue.size(); + // Delete old blocks, and blocks over the limit from the memory - while (!mapblock_queue.empty() && (mapblock_queue.size() > max_loaded_blocks + while (!mapblock_queue.empty() && ((s32)mapblock_queue.size() > max_loaded_blocks || mapblock_queue.top().block->getUsageTimer() > unload_timeout)) { TimeOrderedMapBlock b = mapblock_queue.top(); mapblock_queue.pop(); @@ -416,6 +428,7 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, deleted_blocks_count++; block_count_all--; } + // Delete empty sectors for (auto §or_it : m_sectors) { if (sector_it.second->empty()) { @@ -423,7 +436,11 @@ void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, } } } + endSave(); + const auto end_time = porting::getTimeUs(); + + reportMetrics(end_time - start_time, saved_blocks_count, block_count_all); // Finally delete the empty sectors deleteSectors(sector_deletion_queue); @@ -502,11 +519,11 @@ struct NodeNeighbor { { } }; -void Map::transforming_liquid_add(v3s16 p) { +void ServerMap::transforming_liquid_add(v3s16 p) { m_transforming_liquid.push_back(p); } -void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, +void ServerMap::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, ServerEnvironment *env) { u32 loopcount = 0; @@ -1209,7 +1226,12 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, m_savedir = savedir; m_map_saving_enabled = false; - m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)"); + m_save_time_counter = mb->addCounter( + "minetest_map_save_time", "Time spent saving blocks (in microseconds)"); + m_save_count_counter = mb->addCounter( + "minetest_map_saved_blocks", "Number of blocks saved"); + m_loaded_blocks_gauge = mb->addGauge( + "minetest_map_loaded_blocks", "Number of loaded blocks"); m_map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); @@ -1540,6 +1562,29 @@ bool ServerMap::isBlockInQueue(v3s16 pos) return m_emerge && m_emerge->isBlockInQueue(pos); } +void ServerMap::addNodeAndUpdate(v3s16 p, MapNode n, + std::map<v3s16, MapBlock*> &modified_blocks, + bool remove_metadata) +{ + Map::addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + + /* + 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.) + */ + + for (const v3s16 &dir : g_7dirs) { + v3s16 p2 = p + dir; + + bool is_valid_position; + MapNode n2 = getNode(p2, &is_valid_position); + if(is_valid_position && + (m_nodedef->get(n2).isLiquid() || + n2.getContent() == CONTENT_AIR)) + m_transforming_liquid.push_back(p2); + } +} + // N.B. This requires no synchronization, since data will not be modified unless // the VoxelManipulator being updated belongs to the same thread. void ServerMap::updateVManip(v3s16 pos) @@ -1562,6 +1607,13 @@ void ServerMap::updateVManip(v3s16 pos) vm->m_is_dirty = true; } +void ServerMap::reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) +{ + m_loaded_blocks_gauge->set(all_blocks); + m_save_time_counter->increment(save_time_us); + m_save_count_counter->increment(saved_blocks); +} + void ServerMap::save(ModifiedState save_level) { if (!m_map_saving_enabled) { @@ -1569,7 +1621,7 @@ void ServerMap::save(ModifiedState save_level) return; } - u64 start_time = porting::getTimeNs(); + const auto start_time = porting::getTimeUs(); if(save_level == MOD_STATE_CLEAN) infostream<<"ServerMap: Saving whole map, this can take time." @@ -1630,8 +1682,8 @@ void ServerMap::save(ModifiedState save_level) modprofiler.print(infostream); } - auto end_time = porting::getTimeNs(); - m_save_time_counter->increment(end_time - start_time); + const auto end_time = porting::getTimeUs(); + reportMetrics(end_time - start_time, block_count, block_count_all); } void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst) @@ -1864,6 +1916,7 @@ MMVManip::MMVManip(Map *map): VoxelManipulator(), m_map(map) { + assert(map); } void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, @@ -1871,6 +1924,8 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, { TimeTaker timer1("initialEmerge", &emerge_time); + assert(m_map); + // Units of these are MapBlocks v3s16 p_min = blockpos_min; v3s16 p_max = blockpos_max; @@ -1954,6 +2009,7 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks, { if(m_area.getExtent() == v3s16(0,0,0)) return; + assert(m_map); /* Copy data of all blocks @@ -1974,4 +2030,33 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks, } } +MMVManip *MMVManip::clone() const +{ + MMVManip *ret = new MMVManip(); + + const s32 size = m_area.getVolume(); + ret->m_area = m_area; + if (m_data) { + ret->m_data = new MapNode[size]; + memcpy(ret->m_data, m_data, size * sizeof(MapNode)); + } + if (m_flags) { + ret->m_flags = new u8[size]; + memcpy(ret->m_flags, m_flags, size * sizeof(u8)); + } + + ret->m_is_dirty = m_is_dirty; + // Even if the copy is disconnected from a map object keep the information + // needed to write it back to one + ret->m_loaded_blocks = m_loaded_blocks; + + return ret; +} + +void MMVManip::reparent(Map *map) +{ + assert(map && !m_map); + m_map = map; +} + //END @@ -54,10 +54,6 @@ struct BlockMakeData; MapEditEvent */ -#define MAPTYPE_BASE 0 -#define MAPTYPE_SERVER 1 -#define MAPTYPE_CLIENT 2 - enum MapEditEventType{ // Node added (changed from air or something else to something) MEET_ADDNODE, @@ -127,11 +123,6 @@ public: virtual ~Map(); DISABLE_CLASS_COPY(Map); - virtual s32 mapType() const - { - return MAPTYPE_BASE; - } - /* Drop (client) or delete (server) the map. */ @@ -180,7 +171,7 @@ public: /* These handle lighting but not faces. */ - void addNodeAndUpdate(v3s16 p, MapNode n, + virtual void addNodeAndUpdate(v3s16 p, MapNode n, std::map<v3s16, MapBlock*> &modified_blocks, bool remove_metadata = true); void removeNodeAndUpdate(v3s16 p, @@ -200,6 +191,11 @@ public: virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); } + /* + Return true unless the map definitely cannot save blocks. + */ + virtual bool maySaveBlocks() { return true; } + // Server implements these. // Client leaves them as no-op. virtual bool saveBlock(MapBlock *block) { return false; } @@ -207,14 +203,14 @@ public: /* Updates usage timers and unloads unused blocks and sectors. - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ - void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, + void timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks, std::vector<v3s16> *unloaded_blocks=NULL); /* Unloads all blocks with a zero refCount(). - Saves modified blocks before unloading on MAPTYPE_SERVER. + Saves modified blocks before unloading if possible. */ void unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks=NULL); @@ -226,9 +222,6 @@ public: // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); - void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks, - ServerEnvironment *env); - /* Node metadata These are basically coordinate wrappers to MapBlock @@ -267,39 +260,29 @@ public: Variables */ - void transforming_liquid_add(v3s16 p); - bool isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes); protected: - friend class LuaVoxelManip; - IGameDef *m_gamedef; std::set<MapEventReceiver*> m_event_receivers; - std::map<v2s16, MapSector*> m_sectors; + std::unordered_map<v2s16, MapSector*> m_sectors; // Be sure to set this to NULL when the cached sector is deleted MapSector *m_sector_cache = nullptr; v2s16 m_sector_cache_p; - // Queued transforming water nodes - UniqueQueue<v3s16> m_transforming_liquid; - // This stores the properties of the nodes on the map. const NodeDefManager *m_nodedef; + // Can be implemented by child class + virtual void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) {} + bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera, const core::aabbox3d<s16> &block_bounds, v3s16 &check); bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target, float step, float stepfac, float start_offset, float end_offset, u32 needed_count); - -private: - f32 m_transforming_liquid_loop_count_multiplier = 1.0f; - u32 m_unprocessed_count = 0; - u64 m_inc_trending_up_start_time = 0; // milliseconds - bool m_queue_size_timer_started = false; }; /* @@ -317,11 +300,6 @@ public: ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb); ~ServerMap(); - s32 mapType() const - { - return MAPTYPE_SERVER; - } - /* Get a sector from somewhere. - Check memory @@ -352,7 +330,7 @@ public: - Create blank filled with CONTENT_IGNORE */ - MapBlock *emergeBlock(v3s16 p, bool create_blank=true); + MapBlock *emergeBlock(v3s16 p, bool create_blank=true) override; /* Try to get a block. @@ -364,33 +342,37 @@ public: bool isBlockInQueue(v3s16 pos); + void addNodeAndUpdate(v3s16 p, MapNode n, + std::map<v3s16, MapBlock*> &modified_blocks, + bool remove_metadata) override; + /* Database functions */ static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); // Call these before and after saving of blocks - void beginSave(); - void endSave(); + void beginSave() override; + void endSave() override; - void save(ModifiedState save_level); + void save(ModifiedState save_level) override; void listAllLoadableBlocks(std::vector<v3s16> &dst); void listAllLoadedBlocks(std::vector<v3s16> &dst); MapgenParams *getMapgenParams(); - bool saveBlock(MapBlock *block); + bool saveBlock(MapBlock *block) override; static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1); MapBlock* loadBlock(v3s16 p); // Database version void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); - bool deleteBlock(v3s16 blockpos); + bool deleteBlock(v3s16 blockpos) override; void updateVManip(v3s16 pos); // For debug printing - virtual void PrintInfo(std::ostream &out); + void PrintInfo(std::ostream &out) override; bool isSavingEnabled(){ return m_map_saving_enabled; } @@ -406,9 +388,20 @@ public: bool repairBlockLight(v3s16 blockpos, std::map<v3s16, MapBlock *> *modified_blocks); + void transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks, + ServerEnvironment *env); + + void transforming_liquid_add(v3s16 p); + MapSettingsManager settings_mgr; +protected: + + void reportMetrics(u64 save_time_us, u32 saved_blocks, u32 all_blocks) override; + private: + friend class LuaVoxelManip; + // Emerge manager EmergeManager *m_emerge; @@ -419,6 +412,13 @@ private: std::set<v3s16> m_chunks_in_progress; + // Queued transforming water nodes + UniqueQueue<v3s16> m_transforming_liquid; + f32 m_transforming_liquid_loop_count_multiplier = 1.0f; + u32 m_unprocessed_count = 0; + u64 m_inc_trending_up_start_time = 0; // milliseconds + bool m_queue_size_timer_started = false; + /* Metadata is re-written on disk only if this is true. This is reset to false when written on disk. @@ -427,7 +427,10 @@ private: MapDatabase *dbase = nullptr; MapDatabase *dbase_ro = nullptr; + // Map metrics + MetricGaugePtr m_loaded_blocks_gauge; MetricCounterPtr m_save_time_counter; + MetricCounterPtr m_save_count_counter; }; @@ -453,10 +456,25 @@ public: void blitBackAll(std::map<v3s16, MapBlock*> * modified_blocks, bool overwrite_generated = true); + /* + Creates a copy of this VManip including contents, the copy will not be + associated with a Map. + */ + MMVManip *clone() const; + + // Reassociates a copied VManip to a map + void reparent(Map *map); + + // Is it impossible to call initialEmerge / blitBackAll? + inline bool isOrphan() const { return !m_map; } + bool m_is_dirty = false; protected: - Map *m_map; + MMVManip() {}; + + // may be null + Map *m_map = nullptr; /* key = blockpos value = flags describing the block diff --git a/src/mapblock.cpp b/src/mapblock.cpp index e3a6caa19..2bbc0ebbf 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -221,33 +221,36 @@ void MapBlock::expireDayNightDiff() /* Serialization */ + // List relevant id-name pairs for ids in the block using nodedef -// Renumbers the content IDs (starting at 0 and incrementing -// use static memory requires about 65535 * sizeof(int) ram in order to be -// sure we can handle all content ids. But it's absolutely worth it as it's -// a speedup of 4 for one of the major time consuming functions on storing -// mapblocks. -static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1]; +// Renumbers the content IDs (starting at 0 and incrementing) static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, const NodeDefManager *nodedef) { - memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t)); - - std::set<content_t> unknown_contents; + // The static memory requires about 65535 * sizeof(int) RAM in order to be + // sure we can handle all content ids. But it's absolutely worth it as it's + // a speedup of 4 for one of the major time consuming functions on storing + // mapblocks. + thread_local std::unique_ptr<content_t[]> mapping; + static_assert(sizeof(content_t) == 2, "content_t must be 16-bit"); + if (!mapping) + mapping = std::make_unique<content_t[]>(USHRT_MAX + 1); + + memset(mapping.get(), 0xFF, (USHRT_MAX + 1) * sizeof(content_t)); + + std::unordered_set<content_t> unknown_contents; content_t id_counter = 0; for (u32 i = 0; i < MapBlock::nodecount; i++) { content_t global_id = nodes[i].getContent(); content_t id = CONTENT_IGNORE; // Try to find an existing mapping - if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) { - id = getBlockNodeIdMapping_mapping[global_id]; - } - else - { + if (mapping[global_id] != 0xFFFF) { + id = mapping[global_id]; + } else { // We have to assign a new mapping id = id_counter++; - getBlockNodeIdMapping_mapping[global_id] = id; + mapping[global_id] = id; const ContentFeatures &f = nodedef->get(global_id); const std::string &name = f.name; @@ -265,6 +268,7 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, << "Name for node id " << unknown_content << " not known" << std::endl; } } + // Correct ids in the block to match nodedef based on names. // Unknown ones are added to nodedef. // Will not update itself to match id-name pairs in nodedef. diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index acdb1a0f0..1d439abeb 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -71,7 +71,7 @@ DungeonGen::DungeonGen(const NodeDefManager *ndef, dp.num_dungeons = 1; dp.notifytype = GENNOTIFY_DUNGEON; - dp.np_alt_wall = + dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); } } diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index d767bd264..99db50426 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -238,7 +238,8 @@ u32 Mapgen::getBlockSeed(v3s16 p, s32 seed) u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) { - u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed; + // Multiply by unsigned number to avoid signed overflow (UB) + u32 n = 1619U * p.X + 31337U * p.Y + 52591U * p.Z + 1013U * seed; n = (n >> 13) ^ n; return (n * (n * n * 60493 + 19990303) + 1376312589); } @@ -452,9 +453,8 @@ void Mapgen::lightSpread(VoxelArea &a, std::queue<std::pair<v3s16, u8>> &queue, !ndef->get(n).light_propagates) return; - // Since this recursive function only terminates when there is no light from - // either bank left, we need to take the max of both banks into account for - // the case where spreading has stopped for one light bank but not the other. + // MYMAX still needed here because we only exit early if both banks have + // nothing to propagate anymore. light = MYMAX(light_day, n.param1 & 0x0F) | MYMAX(light_night, n.param1 & 0xF0); @@ -469,12 +469,9 @@ void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nm bool propagate_shadow) { ScopeProfiler sp(g_profiler, "EmergeThread: update lighting", SPT_AVG); - //TimeTaker t("updateLighting"); propagateSunlight(nmin, nmax, propagate_shadow); spreadLight(full_nmin, full_nmax); - - //printf("updateLighting: %dms\n", t.stop()); } @@ -1041,6 +1038,8 @@ void MapgenParams::readParams(const Settings *settings) settings->getS16NoEx("chunksize", chunksize); settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen); + chunksize = rangelim(chunksize, 1, 10); + delete bparams; bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL); if (bparams) { diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 61db4f3b9..ef5de6029 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -190,12 +190,38 @@ public: void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax); + /** + * Set light in entire area to fixed value. + * @param light Light value (contains both banks) + * @param nmin Area to operate on + * @param nmax ^ + */ void setLighting(u8 light, v3s16 nmin, v3s16 nmax); - void lightSpread(VoxelArea &a, std::queue<std::pair<v3s16, u8>> &queue, - const v3s16 &p, u8 light); + /** + * Run all lighting calculations. + * @param nmin Area to spread sunlight in + * @param nmax ^ + * @param full_nmin Area to recalculate light in + * @param full_nmax ^ + * @param propagate_shadow see propagateSunlight() + */ void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax, bool propagate_shadow = true); + /** + * Spread sunlight from the area above downwards. + * Note that affected nodes have their night bank cleared so you want to + * run a light spread afterwards. + * @param nmin Area to operate on + * @param nmax ^ + * @param propagate_shadow Ignore obstructions above and spread sun anyway + */ void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow); + /** + * Spread light in the given area. + * Artificial light is taken from nodedef, sunlight must already be set. + * @param nmin Area to operate on + * @param nmax ^ + */ void spreadLight(const v3s16 &nmin, const v3s16 &nmax); virtual void makeChunk(BlockMakeData *data) {} @@ -218,6 +244,18 @@ public: static void setDefaultSettings(Settings *settings); private: + /** + * Spread light to the node at the given position, add to queue if changed. + * The given light value is diminished once. + * @param a VoxelArea being operated on + * @param queue Queue for later lightSpread() calls + * @param p Node position + * @param light Light value (contains both banks) + * + */ + void lightSpread(VoxelArea &a, std::queue<std::pair<v3s16, u8>> &queue, + const v3s16 &p, u8 light); + // isLiquidHorizontallyFlowable() is a helper function for updateLiquid() // that checks whether there are floodable nodes without liquid beneath // the node at index vi. diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 342455029..6b249ea1f 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -177,7 +177,7 @@ void MapgenFlatParams::setDefaultSettings(Settings *settings) int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) { s16 stone_level = ground_level; - float n_terrain = + float n_terrain = ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) ? NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed) : 0.0f; diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index fabb1b2b1..c9071cecf 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -131,6 +131,7 @@ void MapgenFractalParams::readParams(const Settings *settings) settings->getNoiseParams("mgfractal_np_cave1", np_cave1); settings->getNoiseParams("mgfractal_np_cave2", np_cave2); settings->getNoiseParams("mgfractal_np_dungeons", np_dungeons); + iterations = std::max<u16>(iterations, 1); } diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index f08cc190f..8b4c96cd5 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -273,7 +273,7 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po pos.Y - biome_closest_blend->max_pos.Y) return biome_closest_blend; - return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); + return (biome_closest) ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); } diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index 5814f433a..4f0c35548 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -498,8 +498,8 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, } // randval ranges from -1..1 - /* - Note: can generate values slightly larger than 1 + /* + Note: can generate values slightly larger than 1 but this can't be changed as mapgen must be deterministic accross versions. */ float randval = (float)pr.next() / float(pr.RANDOM_RANGE / 2) - 1.f; diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 73bd620fb..42f020e71 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -266,10 +266,12 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, std::vector<aabb3f> &boxes = *p_boxes; if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) { - const std::vector<aabb3f> &fixed = nodebox.fixed; + const auto &fixed = nodebox.fixed; int facedir = n.getFaceDir(nodemgr, true); u8 axisdir = facedir>>2; facedir&=0x03; + + boxes.reserve(boxes.size() + fixed.size()); for (aabb3f box : fixed) { if (nodebox.type == NODEBOX_LEVELED) box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS; @@ -437,41 +439,43 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, { size_t boxes_size = boxes.size(); boxes_size += nodebox.fixed.size(); + const auto &c = nodebox.getConnected(); + if (neighbors & 1) - boxes_size += nodebox.connect_top.size(); + boxes_size += c.connect_top.size(); else - boxes_size += nodebox.disconnected_top.size(); + boxes_size += c.disconnected_top.size(); if (neighbors & 2) - boxes_size += nodebox.connect_bottom.size(); + boxes_size += c.connect_bottom.size(); else - boxes_size += nodebox.disconnected_bottom.size(); + boxes_size += c.disconnected_bottom.size(); if (neighbors & 4) - boxes_size += nodebox.connect_front.size(); + boxes_size += c.connect_front.size(); else - boxes_size += nodebox.disconnected_front.size(); + boxes_size += c.disconnected_front.size(); if (neighbors & 8) - boxes_size += nodebox.connect_left.size(); + boxes_size += c.connect_left.size(); else - boxes_size += nodebox.disconnected_left.size(); + boxes_size += c.disconnected_left.size(); if (neighbors & 16) - boxes_size += nodebox.connect_back.size(); + boxes_size += c.connect_back.size(); else - boxes_size += nodebox.disconnected_back.size(); + boxes_size += c.disconnected_back.size(); if (neighbors & 32) - boxes_size += nodebox.connect_right.size(); + boxes_size += c.connect_right.size(); else - boxes_size += nodebox.disconnected_right.size(); + boxes_size += c.disconnected_right.size(); if (neighbors == 0) - boxes_size += nodebox.disconnected.size(); + boxes_size += c.disconnected.size(); if (neighbors < 4) - boxes_size += nodebox.disconnected_sides.size(); + boxes_size += c.disconnected_sides.size(); boxes.reserve(boxes_size); @@ -484,47 +488,47 @@ void transformNodeBox(const MapNode &n, const NodeBox &nodebox, BOXESPUSHBACK(nodebox.fixed); if (neighbors & 1) { - BOXESPUSHBACK(nodebox.connect_top); + BOXESPUSHBACK(c.connect_top); } else { - BOXESPUSHBACK(nodebox.disconnected_top); + BOXESPUSHBACK(c.disconnected_top); } if (neighbors & 2) { - BOXESPUSHBACK(nodebox.connect_bottom); + BOXESPUSHBACK(c.connect_bottom); } else { - BOXESPUSHBACK(nodebox.disconnected_bottom); + BOXESPUSHBACK(c.disconnected_bottom); } if (neighbors & 4) { - BOXESPUSHBACK(nodebox.connect_front); + BOXESPUSHBACK(c.connect_front); } else { - BOXESPUSHBACK(nodebox.disconnected_front); + BOXESPUSHBACK(c.disconnected_front); } if (neighbors & 8) { - BOXESPUSHBACK(nodebox.connect_left); + BOXESPUSHBACK(c.connect_left); } else { - BOXESPUSHBACK(nodebox.disconnected_left); + BOXESPUSHBACK(c.disconnected_left); } if (neighbors & 16) { - BOXESPUSHBACK(nodebox.connect_back); + BOXESPUSHBACK(c.connect_back); } else { - BOXESPUSHBACK(nodebox.disconnected_back); + BOXESPUSHBACK(c.disconnected_back); } if (neighbors & 32) { - BOXESPUSHBACK(nodebox.connect_right); + BOXESPUSHBACK(c.connect_right); } else { - BOXESPUSHBACK(nodebox.disconnected_right); + BOXESPUSHBACK(c.disconnected_right); } if (neighbors == 0) { - BOXESPUSHBACK(nodebox.disconnected); + BOXESPUSHBACK(c.disconnected); } if (neighbors < 4) { - BOXESPUSHBACK(nodebox.disconnected_sides); + BOXESPUSHBACK(c.disconnected_sides); } } diff --git a/src/modchannels.cpp b/src/modchannels.cpp index 301dcb092..9626e8e0c 100644 --- a/src/modchannels.cpp +++ b/src/modchannels.cpp @@ -88,8 +88,7 @@ bool ModChannelMgr::canWriteOnChannel(const std::string &channel) const void ModChannelMgr::registerChannel(const std::string &channel) { - m_registered_channels[channel] = - std::unique_ptr<ModChannel>(new ModChannel(channel)); + m_registered_channels[channel] = std::make_unique<ModChannel>(channel); } bool ModChannelMgr::setChannelState(const std::string &channel, ModChannelState state) diff --git a/src/network/address.cpp b/src/network/address.cpp index 90e561802..cf2e6208d 100644 --- a/src/network/address.cpp +++ b/src/network/address.cpp @@ -230,14 +230,14 @@ void Address::setPort(u16 port) m_port = port; } -void Address::print(std::ostream *s) const +void Address::print(std::ostream& s) const { if (m_addr_family == AF_INET6) - *s << "[" << serializeString() << "]:" << m_port; + s << "[" << serializeString() << "]:" << m_port; else if (m_addr_family == AF_INET) - *s << serializeString() << ":" << m_port; + s << serializeString() << ":" << m_port; else - *s << "(undefined)"; + s << "(undefined)"; } bool Address::isLocalhost() const diff --git a/src/network/address.h b/src/network/address.h index c2f5f2eef..692bf82c5 100644 --- a/src/network/address.h +++ b/src/network/address.h @@ -59,7 +59,7 @@ public: int getFamily() const { return m_addr_family; } bool isIPv6() const { return m_addr_family == AF_INET6; } bool isZero() const; - void print(std::ostream *s) const; + void print(std::ostream &s) const; std::string serializeString() const; bool isLocalhost() const; diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index a98a5e7d1..6a78b4652 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -123,6 +123,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61, { "TOCLIENT_MINIMAP_MODES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MinimapModes }, // 0x62, + { "TOCLIENT_SET_LIGHTING", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SetLighting }, // 0x63, }; const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 48ad60ac6..25c1d2690 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -101,11 +101,20 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) // Authenticate using that method, or abort if there wasn't any method found if (chosen_auth_mechanism != AUTH_MECHANISM_NONE) { - if (chosen_auth_mechanism == AUTH_MECHANISM_FIRST_SRP && - !m_simple_singleplayer_mode && - !getServerAddress().isLocalhost() && - g_settings->getBool("enable_register_confirmation")) { - promptConfirmRegistration(chosen_auth_mechanism); + bool is_register = chosen_auth_mechanism == AUTH_MECHANISM_FIRST_SRP; + ELoginRegister mode = is_register ? ELoginRegister::Register : ELoginRegister::Login; + if (m_allow_login_or_register != ELoginRegister::Any && + m_allow_login_or_register != mode) { + m_chosen_auth_mech = AUTH_MECHANISM_NONE; + m_access_denied = true; + if (m_allow_login_or_register == ELoginRegister::Login) { + m_access_denied_reason = + gettext("Name is not registered. To create an account on this server, click 'Register'"); + } else { + m_access_denied_reason = + gettext("Name is taken. Please choose another name"); + } + m_con->Disconnect(); } else { startAuth(chosen_auth_mechanism); } @@ -183,7 +192,7 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) m_access_denied_reason = "Unknown"; if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) { - // 13/03/15 Legacy code from 0.4.12 and lesser but is still used + // Legacy code from 0.4.12 and older but is still used // in some places of the server code if (pkt->getSize() >= 2) { std::wstring wide_reason; @@ -196,14 +205,14 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) if (pkt->getSize() < 1) return; - u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; + u8 denyCode; *pkt >> denyCode; + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = accessDeniedStrings[denyCode]; - } u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; @@ -220,9 +229,8 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) // Until then (which may be never), this is outside // of the defined protocol. *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = "Unknown"; - } } } @@ -562,6 +570,10 @@ void Client::handleCommand_HP(NetworkPacket *pkt) u16 hp; *pkt >> hp; + bool damage_effect = true; + try { + *pkt >> damage_effect; + } catch (PacketError &e) {}; player->hp = hp; @@ -573,6 +585,7 @@ void Client::handleCommand_HP(NetworkPacket *pkt) ClientEvent *event = new ClientEvent(); event->type = CE_PLAYER_DAMAGE; event->player_damage.amount = oldhp - hp; + event->player_damage.effect = damage_effect; m_client_event_queue.push(event); } } @@ -765,7 +778,7 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - m_nodedef->deSerialize(tmp_os); + m_nodedef->deSerialize(tmp_os, m_proto_ver); m_nodedef_received = true; } @@ -784,7 +797,7 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - m_itemdef->deSerialize(tmp_os); + m_itemdef->deSerialize(tmp_os, m_proto_ver); m_itemdef_received = true; } @@ -805,44 +818,38 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) */ s32 server_id; - std::string name; - float gain; - u8 type; // 0=local, 1=positional, 2=object + SimpleSoundSpec spec; + SoundLocation type; // 0=local, 1=positional, 2=object v3f pos; u16 object_id; - bool loop; - float fade = 0.0f; - float pitch = 1.0f; bool ephemeral = false; - *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; + *pkt >> server_id >> spec.name >> spec.gain >> (u8 &)type >> pos >> object_id >> spec.loop; try { - *pkt >> fade; - *pkt >> pitch; + *pkt >> spec.fade; + *pkt >> spec.pitch; *pkt >> ephemeral; } catch (PacketError &e) {}; // Start playing int client_id = -1; switch(type) { - case 0: // local - client_id = m_sound->playSound(name, loop, gain, fade, pitch); - break; - case 1: // positional - client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); - break; - case 2: - { // object + case SoundLocation::Local: + client_id = m_sound->playSound(spec); + break; + case SoundLocation::Position: + client_id = m_sound->playSoundAt(spec, pos); + break; + case SoundLocation::Object: + { ClientActiveObject *cao = m_env.getActiveObject(object_id); if (cao) pos = cao->getPosition(); - client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); + client_id = m_sound->playSoundAt(spec, pos); break; } - default: - break; } if (client_id != -1) { @@ -987,18 +994,18 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.amount = readU16(is); p.time = readF32(is); - p.minpos = readV3F32(is); - p.maxpos = readV3F32(is); - p.minvel = readV3F32(is); - p.maxvel = readV3F32(is); - p.minacc = readV3F32(is); - p.maxacc = readV3F32(is); - p.minexptime = readF32(is); - p.maxexptime = readF32(is); - p.minsize = readF32(is); - p.maxsize = readF32(is); + + // older protocols do not support tweening, and send only + // static ranges, so we can't just use the normal serialization + // functions for the older values. + p.pos.start.legacyDeSerialize(is); + p.vel.start.legacyDeSerialize(is); + p.acc.start.legacyDeSerialize(is); + p.exptime.start.legacyDeSerialize(is); + p.size.start.legacyDeSerialize(is); + p.collisiondetection = readU8(is); - p.texture = deSerializeString32(is); + p.texture.string = deSerializeString32(is); server_id = readU32(is); @@ -1011,6 +1018,8 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.glow = readU8(is); p.object_collision = readU8(is); + bool legacy_format = true; + // This is kinda awful do { u16 tmp_param0 = readU16(is); @@ -1019,7 +1028,70 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.node.param0 = tmp_param0; p.node.param2 = readU8(is); p.node_tile = readU8(is); - } while (0); + + // v >= 5.6.0 + f32 tmp_sbias = readF32(is); + if (is.eof()) + break; + + // initial bias must be stored separately in the stream to preserve + // backwards compatibility with older clients, which do not support + // a bias field in their range "format" + p.pos.start.bias = tmp_sbias; + p.vel.start.bias = readF32(is); + p.acc.start.bias = readF32(is); + p.exptime.start.bias = readF32(is); + p.size.start.bias = readF32(is); + + p.pos.end.deSerialize(is); + p.vel.end.deSerialize(is); + p.acc.end.deSerialize(is); + p.exptime.end.deSerialize(is); + p.size.end.deSerialize(is); + + // properties for legacy texture field + p.texture.deSerialize(is, m_proto_ver, true); + + p.drag.deSerialize(is); + p.jitter.deSerialize(is); + p.bounce.deSerialize(is); + ParticleParamTypes::deSerializeParameterValue(is, p.attractor_kind); + using ParticleParamTypes::AttractorKind; + if (p.attractor_kind != AttractorKind::none) { + p.attract.deSerialize(is); + p.attractor_origin.deSerialize(is); + p.attractor_attachment = readU16(is); + /* we only check the first bit, in order to allow this value + * to be turned into a bit flag field later if needed */ + p.attractor_kill = !!(readU8(is) & 1); + if (p.attractor_kind != AttractorKind::point) { + p.attractor_direction.deSerialize(is); + p.attractor_direction_attachment = readU16(is); + } + } + p.radius.deSerialize(is); + + u16 texpoolsz = readU16(is); + p.texpool.reserve(texpoolsz); + for (u16 i = 0; i < texpoolsz; ++i) { + ServerParticleTexture newtex; + newtex.deSerialize(is, m_proto_ver); + p.texpool.push_back(newtex); + } + + legacy_format = false; + } while(0); + + if (legacy_format) { + // there's no tweening data to be had, so we need to set the + // legacy params to constant values, otherwise everything old + // will tween to zero + p.pos.end = p.pos.start; + p.vel.end = p.vel.start; + p.acc.end = p.acc.start; + p.exptime.end = p.exptime.start; + p.size.end = p.size.start; + } auto event = new ClientEvent(); event->type = CE_ADD_PARTICLESPAWNER; @@ -1331,10 +1403,13 @@ void Client::handleCommand_HudSetMoon(NetworkPacket *pkt) void Client::handleCommand_HudSetStars(NetworkPacket *pkt) { - StarParams stars; + StarParams stars = SkyboxDefaults::getStarDefaults(); *pkt >> stars.visible >> stars.count >> stars.starcolor >> stars.scale; + try { + *pkt >> stars.day_opacity; + } catch (PacketError &e) {}; ClientEvent *event = new ClientEvent(); event->type = CE_SET_STARS; @@ -1682,3 +1757,11 @@ void Client::handleCommand_MinimapModes(NetworkPacket *pkt) if (m_minimap) m_minimap->setModeIndex(mode); } + +void Client::handleCommand_SetLighting(NetworkPacket *pkt) +{ + Lighting& lighting = m_env.getLocalPlayer()->getLighting(); + + if (pkt->getRemainingBytes() >= 4) + *pkt >> lighting.shadow_intensity; +} diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 2d3cf6e88..6fb676f25 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -41,25 +41,14 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG - #define LOG(a) a #define PROFILE(a) #else - #if 0 - /* this mutex is used to achieve log message consistency */ - std::mutex log_message_mutex; - #define LOG(a) \ - { \ - MutexAutoLock loglock(log_message_mutex); \ - a; \ - } - #else - // Prevent deadlocks until a solution is found after 5.2.0 (TODO) - #define LOG(a) a - #endif - #define PROFILE(a) a #endif +// TODO: Clean this up. +#define LOG(a) a + #define PING_TIMEOUT 5.0 u16 BufferedPacket::getSeqnum() const diff --git a/src/network/connection.h b/src/network/connection.h index 1afb4ae84..b5ae24882 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -194,7 +194,7 @@ struct BufferedPacket { u16 getSeqnum() const; - inline const size_t size() const { return m_data.size(); } + inline size_t size() const { return m_data.size(); } u8 *data; // Direct memory access float time = 0.0f; // Seconds from buffering the packet or re-sending @@ -752,8 +752,8 @@ protected: void putEvent(ConnectionEventPtr e); void TriggerSend(); - - bool ConnectedToServer() + + bool ConnectedToServer() { return getPeerNoEx(PEER_ID_SERVER) != nullptr; } diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index dca065ae1..90936b43d 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -32,22 +32,18 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG -#define LOG(a) a #define PROFILE(a) #undef DEBUG_CONNECTION_KBPS #else /* this mutex is used to achieve log message consistency */ -std::mutex log_conthread_mutex; -#define LOG(a) \ - { \ - MutexAutoLock loglock(log_conthread_mutex); \ - a; \ - } #define PROFILE(a) a //#define DEBUG_CONNECTION_KBPS #undef DEBUG_CONNECTION_KBPS #endif +// TODO: Clean this up. +#define LOG(a) a + #define WINDOW_SIZE 5 static session_t readPeerId(const u8 *packetdata) diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index a5ff53216..3ab839f8d 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -207,9 +207,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Minimap modes PROTOCOL VERSION 40: TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added + Added new particlespawner parameters + [scheduled bump for 5.6.0] */ -#define LATEST_PROTOCOL_VERSION 40 +#define LATEST_PROTOCOL_VERSION 41 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range @@ -228,8 +230,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PASSWORD_SIZE 28 // Maximum password length. Allows for // base64-encoded SHA-1 (27+\0). -// See also: Formspec Version History in doc/lua_api.txt -#define FORMSPEC_API_VERSION 5 +// See also formspec [Version History] in doc/lua_api.txt +#define FORMSPEC_API_VERSION 6 #define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" @@ -511,11 +513,12 @@ enum ToClientCommand TOCLIENT_SPAWN_PARTICLE = 0x46, /* - v3f1000 pos - v3f1000 velocity - v3f1000 acceleration - f1000 expirationtime - f1000 size + -- struct range<T> { T min, T max, f32 bias }; + v3f pos + v3f velocity + v3f acceleration + f32 expirationtime + f32 size u8 bool collisiondetection u32 len u8[len] texture @@ -524,22 +527,26 @@ enum ToClientCommand TileAnimation animation u8 glow u8 object_collision + v3f drag + range<v3f> bounce */ TOCLIENT_ADD_PARTICLESPAWNER = 0x47, /* + -- struct range<T> { T min, T max, f32 bias }; + -- struct tween<T> { T start, T end }; u16 amount - f1000 spawntime - v3f1000 minpos - v3f1000 maxpos - v3f1000 minvel - v3f1000 maxvel - v3f1000 minacc - v3f1000 maxacc - f1000 minexptime - f1000 maxexptime - f1000 minsize - f1000 maxsize + f32 spawntime + v3f minpos + v3f maxpos + v3f minvel + v3f maxvel + v3f minacc + v3f maxacc + f32 minexptime + f32 maxexptime + f32 minsize + f32 maxsize u8 bool collisiondetection u32 len u8[len] texture @@ -549,6 +556,63 @@ enum ToClientCommand TileAnimation animation u8 glow u8 object_collision + + f32 pos_start_bias + f32 vel_start_bias + f32 acc_start_bias + f32 exptime_start_bias + f32 size_start_bias + + range<v3f> pos_end + -- i.e v3f pos_end_min + -- v3f pos_end_max + -- f32 pos_end_bias + range<v3f> vel_end + range<v3f> acc_end + + tween<range<v3f>> drag + -- i.e. v3f drag_start_min + -- v3f drag_start_max + -- f32 drag_start_bias + -- v3f drag_end_min + -- v3f drag_end_max + -- f32 drag_end_bias + tween<range<v3f>> jitter + tween<range<f32>> bounce + + u8 attraction_kind + none = 0 + point = 1 + line = 2 + plane = 3 + + if attraction_kind > none { + tween<range<f32>> attract_strength + tween<v3f> attractor_origin + u16 attractor_origin_attachment_object_id + u8 spawner_flags + bit 1: attractor_kill (particles dies on contact) + if attraction_mode > point { + tween<v3f> attractor_angle + u16 attractor_origin_attachment_object_id + } + } + + tween<range<v3f>> radius + tween<range<v3f>> drag + + u16 texpool_sz + texpool_sz.times { + u8 flags + -- bit 0: animated + -- other bits free & ignored as of proto v40 + tween<f32> alpha + tween<v2f> scale + if flags.animated { + TileAnimation animation + } + } + */ TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY = 0x48, // Obsolete @@ -735,6 +799,7 @@ enum ToClientCommand u32 count u8[4] starcolor (ARGB) f32 scale + f32 day_opacity */ TOCLIENT_SRP_BYTES_S_B = 0x60, @@ -762,7 +827,12 @@ enum ToClientCommand std::string extra */ - TOCLIENT_NUM_MSG_TYPES = 0x63, + TOCLIENT_SET_LIGHTING = 0x63, + /* + f32 shadow_intensity + */ + + TOCLIENT_NUM_MSG_TYPES = 0x64, }; enum ToServerCommand @@ -1001,7 +1071,7 @@ enum AuthMechanism AUTH_MECHANISM_FIRST_SRP = 1 << 2, }; -enum AccessDeniedCode { +enum AccessDeniedCode : u8 { SERVER_ACCESSDENIED_WRONG_PASSWORD, SERVER_ACCESSDENIED_UNEXPECTED_DATA, SERVER_ACCESSDENIED_SINGLEPLAYER, @@ -1024,18 +1094,18 @@ enum NetProtoCompressionMode { const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client", + "Your client sent something the server didn't expect. Try reconnecting or updating your client.", "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact server administrator.", - "Player name contains disallowed characters.", - "Player name not allowed.", - "Too many users.", + "Your client's version is not supported.\nPlease contact the server administrator.", + "Player name contains disallowed characters", + "Player name not allowed", + "Too many users", "Empty passwords are disallowed. Set a password and try again.", "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authentication failed. This is likely a server error.", + "Internal server error", "", - "Server shutting down.", - "This server has experienced an internal error. You will now be disconnected." + "Server shutting down", + "The server has experienced an internal error. You will now be disconnected." }; enum PlayerListModifer : u8 diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 44b65e8da..12665e7f1 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -176,7 +176,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 (may be sent as unrel over channel 1 too) { "TOCLIENT_HP", 0, true }, // 0x33 { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 + null_command_factory, // 0x35 { "TOCLIENT_FOV", 0, true }, // 0x36 { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 { "TOCLIENT_MEDIA", 2, true }, // 0x38 diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 6f60b8172..a5ee81a9c 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -108,8 +108,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt) // Use the highest version supported by both u8 depl_serial_v = std::min(client_max, our_max); // If it's lower than the lowest supported, give up. +#if SER_FMT_VER_LOWEST_READ > 0 if (depl_serial_v < SER_FMT_VER_LOWEST_READ) depl_serial_v = SER_FMT_VER_INVALID; +#endif if (depl_serial_v == SER_FMT_VER_INVALID) { actionstream << "Server: A mismatched client tried to connect from " << @@ -227,7 +229,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) Compose auth methods for answer */ std::string encpwd; // encrypted Password field for the user - bool has_auth = m_script->getAuth(playername, &encpwd, NULL); + bool has_auth = m_script->getAuth(playername, &encpwd, nullptr); u32 auth_mechs = 0; client->chosen_mech = AUTH_MECHANISM_NONE; @@ -444,7 +446,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) ("GOTBLOCKS length is too short"); } - m_clients.lock(); + ClientInterface::AutoLock lock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); for (u16 i = 0; i < count; i++) { @@ -452,7 +454,6 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) *pkt >> p; client->GotBlock(p); } - m_clients.unlock(); } void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, @@ -1462,11 +1463,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + const std::string playername = client->getName(); - std::string playername = client->getName(); - - std::string salt; - std::string verification_key; + std::string salt, verification_key; std::string addr_s = getPeerAddress(peer_id).serializeString(); u8 is_empty; @@ -1552,8 +1551,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - bool wantSudo = (cstate == CS_Active); - if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { actionstream << "Server: got SRP _A packet in wrong state " << cstate << " from " << getPeerAddress(peer_id).serializeString() << @@ -1561,6 +1558,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) return; } + const bool wantSudo = (cstate == CS_Active); + if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " "going on with mech " << client->chosen_mech << " from " << @@ -1607,8 +1606,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) client->chosen_mech = chosen; - std::string salt; - std::string verifier; + std::string salt, verifier; if (based_on == 0) { @@ -1658,10 +1656,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); - std::string playername = client->getName(); + const std::string addr_s = client->getAddress().serializeString(); + const std::string playername = client->getName(); - bool wantSudo = (cstate == CS_Active); + const bool wantSudo = (cstate == CS_Active); verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl; @@ -1721,8 +1719,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->create_player_on_auth_success) { m_script->createAuth(playername, client->enc_pwd); - std::string checkpwd; // not used, but needed for passing something - if (!m_script->getAuth(playername, &checkpwd, NULL)) { + if (!m_script->getAuth(playername, nullptr, nullptr)) { errorstream << "Server: " << playername << " cannot be authenticated (auth handler does not work?)" << std::endl; diff --git a/src/network/socket.cpp b/src/network/socket.cpp index 0bb7ea234..df15c89ba 100644 --- a/src/network/socket.cpp +++ b/src/network/socket.cpp @@ -198,7 +198,7 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) if (socket_enable_debug_output) { // Print packet destination and size dstream << (int)m_handle << " -> "; - destination.print(&dstream); + destination.print(dstream); dstream << ", size=" << size; // Print packet contents @@ -231,7 +231,7 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) int sent; if (m_addr_family == AF_INET6) { - struct sockaddr_in6 address = {0}; + struct sockaddr_in6 address = {}; address.sin6_family = AF_INET6; address.sin6_addr = destination.getAddress6(); address.sin6_port = htons(destination.getPort()); @@ -239,7 +239,7 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) sent = sendto(m_handle, (const char *)data, size, 0, (struct sockaddr *)&address, sizeof(struct sockaddr_in6)); } else { - struct sockaddr_in address = {0}; + struct sockaddr_in address = {}; address.sin_family = AF_INET; address.sin_addr = destination.getAddress(); address.sin_port = htons(destination.getPort()); @@ -295,7 +295,7 @@ int UDPSocket::Receive(Address &sender, void *data, int size) if (socket_enable_debug_output) { // Print packet sender and size dstream << (int)m_handle << " <- "; - sender.print(&dstream); + sender.print(dstream); dstream << ", size=" << received; // Print packet contents diff --git a/src/nodedef.cpp b/src/nodedef.cpp index c3fa5a35c..4022ac835 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -56,27 +56,12 @@ void NodeBox::reset() wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2); wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2); // no default for other parts - connect_top.clear(); - connect_bottom.clear(); - connect_front.clear(); - connect_left.clear(); - connect_back.clear(); - connect_right.clear(); - disconnected_top.clear(); - disconnected_bottom.clear(); - disconnected_front.clear(); - disconnected_left.clear(); - disconnected_back.clear(); - disconnected_right.clear(); - disconnected.clear(); - disconnected_sides.clear(); + connected.reset(); } void NodeBox::serialize(std::ostream &os, u16 protocol_version) const { - // Protocol >= 36 - const u8 version = 6; - writeU8(os, version); + writeU8(os, 6); // version. Protocol >= 36 switch (type) { case NODEBOX_LEVELED: @@ -99,7 +84,7 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const writeV3F32(os, wall_side.MinEdge); writeV3F32(os, wall_side.MaxEdge); break; - case NODEBOX_CONNECTED: + case NODEBOX_CONNECTED: { writeU8(os, type); #define WRITEBOX(box) \ @@ -109,22 +94,25 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const writeV3F32(os, i.MaxEdge); \ }; + const auto &c = getConnected(); + WRITEBOX(fixed); - WRITEBOX(connect_top); - WRITEBOX(connect_bottom); - WRITEBOX(connect_front); - WRITEBOX(connect_left); - WRITEBOX(connect_back); - WRITEBOX(connect_right); - WRITEBOX(disconnected_top); - WRITEBOX(disconnected_bottom); - WRITEBOX(disconnected_front); - WRITEBOX(disconnected_left); - WRITEBOX(disconnected_back); - WRITEBOX(disconnected_right); - WRITEBOX(disconnected); - WRITEBOX(disconnected_sides); + WRITEBOX(c.connect_top); + WRITEBOX(c.connect_bottom); + WRITEBOX(c.connect_front); + WRITEBOX(c.connect_left); + WRITEBOX(c.connect_back); + WRITEBOX(c.connect_right); + WRITEBOX(c.disconnected_top); + WRITEBOX(c.disconnected_bottom); + WRITEBOX(c.disconnected_front); + WRITEBOX(c.disconnected_left); + WRITEBOX(c.disconnected_back); + WRITEBOX(c.disconnected_right); + WRITEBOX(c.disconnected); + WRITEBOX(c.disconnected_sides); break; + } default: writeU8(os, type); break; @@ -133,8 +121,7 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const void NodeBox::deSerialize(std::istream &is) { - int version = readU8(is); - if (version < 6) + if (readU8(is) < 6) throw SerializationError("unsupported NodeBox version"); reset(); @@ -173,21 +160,23 @@ void NodeBox::deSerialize(std::istream &is) u16 count; + auto &c = getConnected(); + READBOXES(fixed); - READBOXES(connect_top); - READBOXES(connect_bottom); - READBOXES(connect_front); - READBOXES(connect_left); - READBOXES(connect_back); - READBOXES(connect_right); - READBOXES(disconnected_top); - READBOXES(disconnected_bottom); - READBOXES(disconnected_front); - READBOXES(disconnected_left); - READBOXES(disconnected_back); - READBOXES(disconnected_right); - READBOXES(disconnected); - READBOXES(disconnected_sides); + READBOXES(c.connect_top); + READBOXES(c.connect_bottom); + READBOXES(c.connect_front); + READBOXES(c.connect_left); + READBOXES(c.connect_back); + READBOXES(c.connect_right); + READBOXES(c.disconnected_top); + READBOXES(c.disconnected_bottom); + READBOXES(c.disconnected_front); + READBOXES(c.disconnected_left); + READBOXES(c.disconnected_back); + READBOXES(c.disconnected_right); + READBOXES(c.disconnected); + READBOXES(c.disconnected_sides); } } @@ -257,14 +246,13 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, align_style); } -void TileDef::deSerialize(std::istream &is, u8 contentfeatures_version, - NodeDrawType drawtype) +void TileDef::deSerialize(std::istream &is, NodeDrawType drawtype, u16 protocol_version) { - int version = readU8(is); - if (version < 6) + if (readU8(is) < 6) throw SerializationError("unsupported TileDef version"); + name = deSerializeString16(is); - animation.deSerialize(is, version); + animation.deSerialize(is, protocol_version); u16 flags = readU16(is); backface_culling = flags & TILE_FLAG_BACKFACE_CULLING; tileable_horizontal = flags & TILE_FLAG_TILEABLE_HORIZONTAL; @@ -291,7 +279,7 @@ void TextureSettings::readSettings() bool smooth_lighting = g_settings->getBool("smooth_lighting"); enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); enable_minimap = g_settings->getBool("enable_minimap"); - node_texture_size = g_settings->getU16("texture_min_size"); + node_texture_size = std::min<u16>(g_settings->getU16("texture_min_size"), 1); std::string leaves_style_str = g_settings->get("leaves_style"); std::string world_aligned_mode_str = g_settings->get("world_aligned_mode"); std::string autoscale_mode_str = g_settings->get("autoscale_mode"); @@ -409,9 +397,9 @@ void ContentFeatures::reset() drowning = 0; light_source = 0; damage_per_second = 0; - node_box = NodeBox(); - selection_box = NodeBox(); - collision_box = NodeBox(); + node_box.reset(); + selection_box.reset(); + collision_box.reset(); waving = 0; legacy_facedir_simple = false; legacy_wallmounted = false; @@ -456,16 +444,15 @@ u8 ContentFeatures::getAlphaForLegacy() const void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { - const u8 version = CONTENTFEATURES_VERSION; - writeU8(os, version); + writeU8(os, CONTENTFEATURES_VERSION); // general os << serializeString16(name); writeU16(os, groups.size()); for (const auto &group : groups) { os << serializeString16(group.first); - if (group.first.compare("bouncy") == 0) { - // Clients may choke on negative bouncy value + if (protocol_version < 41 && group.first.compare("bouncy") == 0) { + // Old clients may choke on negative bouncy value writeS16(os, abs(group.second)); } else { writeS16(os, group.second); @@ -542,9 +529,9 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const collision_box.serialize(os, protocol_version); // sound - sound_footstep.serialize(os, version); - sound_dig.serialize(os, version); - sound_dug.serialize(os, version); + sound_footstep.serialize(os, protocol_version); + sound_dig.serialize(os, protocol_version); + sound_dug.serialize(os, protocol_version); // legacy writeU8(os, legacy_facedir_simple); @@ -558,11 +545,9 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, liquid_move_physics); } -void ContentFeatures::deSerialize(std::istream &is) +void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) { - // version detection - const u8 version = readU8(is); - if (version < CONTENTFEATURES_VERSION) + if (readU8(is) < CONTENTFEATURES_VERSION) throw SerializationError("unsupported ContentFeatures version"); // general @@ -584,13 +569,13 @@ void ContentFeatures::deSerialize(std::istream &is) if (readU8(is) != 6) throw SerializationError("unsupported tile count"); for (TileDef &td : tiledef) - td.deSerialize(is, version, drawtype); + td.deSerialize(is, drawtype, protocol_version); for (TileDef &td : tiledef_overlay) - td.deSerialize(is, version, drawtype); + td.deSerialize(is, drawtype, protocol_version); if (readU8(is) != CF_SPECIAL_COUNT) throw SerializationError("unsupported CF_SPECIAL_COUNT"); for (TileDef &td : tiledef_special) - td.deSerialize(is, version, drawtype); + td.deSerialize(is, drawtype, protocol_version); setAlphaFromLegacy(readU8(is)); color.setRed(readU8(is)); color.setGreen(readU8(is)); @@ -641,9 +626,9 @@ void ContentFeatures::deSerialize(std::istream &is) collision_box.deSerialize(is); // sounds - sound_footstep.deSerialize(is, version); - sound_dig.deSerialize(is, version); - sound_dug.deSerialize(is, version); + sound_footstep.deSerialize(is, protocol_version); + sound_dig.deSerialize(is, protocol_version); + sound_dug.deSerialize(is, protocol_version); // read legacy properties legacy_facedir_simple = readU8(is); @@ -909,8 +894,15 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc solidness = 0; visual_solidness = 1; } else { - drawtype = NDT_NORMAL; - solidness = 2; + if (waving >= 1) { + // waving nodes must make faces so there are no gaps + drawtype = NDT_ALLFACES; + solidness = 0; + visual_solidness = 1; + } else { + drawtype = NDT_NORMAL; + solidness = 2; + } for (TileDef &td : tdef) td.name += std::string("^[noalpha"); } @@ -1091,10 +1083,8 @@ void NodeDefManager::clear() { ContentFeatures f; f.name = "unknown"; - TileDef unknownTile; - unknownTile.name = "unknown_node.png"; for (int t = 0; t < 6; t++) - f.tiledef[t] = unknownTile; + f.tiledef[t].name = "unknown_node.png"; // Insert directly into containers content_t c = CONTENT_UNKNOWN; m_content_features[c] = f; @@ -1296,22 +1286,23 @@ void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features, break; } case NODEBOX_CONNECTED: { + const auto &c = nodebox.getConnected(); // Add all possible connected boxes - boxVectorUnion(nodebox.fixed, box_union); - boxVectorUnion(nodebox.connect_top, box_union); - boxVectorUnion(nodebox.connect_bottom, box_union); - boxVectorUnion(nodebox.connect_front, box_union); - boxVectorUnion(nodebox.connect_left, box_union); - boxVectorUnion(nodebox.connect_back, box_union); - boxVectorUnion(nodebox.connect_right, box_union); - boxVectorUnion(nodebox.disconnected_top, box_union); - boxVectorUnion(nodebox.disconnected_bottom, box_union); - boxVectorUnion(nodebox.disconnected_front, box_union); - boxVectorUnion(nodebox.disconnected_left, box_union); - boxVectorUnion(nodebox.disconnected_back, box_union); - boxVectorUnion(nodebox.disconnected_right, box_union); - boxVectorUnion(nodebox.disconnected, box_union); - boxVectorUnion(nodebox.disconnected_sides, box_union); + boxVectorUnion(nodebox.fixed, box_union); + boxVectorUnion(c.connect_top, box_union); + boxVectorUnion(c.connect_bottom, box_union); + boxVectorUnion(c.connect_front, box_union); + boxVectorUnion(c.connect_left, box_union); + boxVectorUnion(c.connect_back, box_union); + boxVectorUnion(c.connect_right, box_union); + boxVectorUnion(c.disconnected_top, box_union); + boxVectorUnion(c.disconnected_bottom, box_union); + boxVectorUnion(c.disconnected_front, box_union); + boxVectorUnion(c.disconnected_left, box_union); + boxVectorUnion(c.disconnected_back, box_union); + boxVectorUnion(c.disconnected_right, box_union); + boxVectorUnion(c.disconnected, box_union); + boxVectorUnion(c.disconnected_sides, box_union); break; } default: { @@ -1548,12 +1539,13 @@ void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const } -void NodeDefManager::deSerialize(std::istream &is) +void NodeDefManager::deSerialize(std::istream &is, u16 protocol_version) { clear(); - int version = readU8(is); - if (version != 1) + + if (readU8(is) < 1) throw SerializationError("unsupported NodeDefinitionManager version"); + u16 count = readU16(is); std::istringstream is2(deSerializeString32(is), std::ios::binary); ContentFeatures f; @@ -1563,7 +1555,7 @@ void NodeDefManager::deSerialize(std::istream &is) // Read it from the string wrapper std::string wrapper = deSerializeString16(is2); std::istringstream wrapper_is(wrapper, std::ios::binary); - f.deSerialize(wrapper_is); + f.deSerialize(wrapper_is, protocol_version); // Check error conditions if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) { diff --git a/src/nodedef.h b/src/nodedef.h index ea50d4281..f4367cba9 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -36,9 +36,6 @@ class Client; #include "texture_override.h" // TextureOverride #include "tileanimation.h" -// PROTOCOL_VERSION >= 37 -static const u8 CONTENTFEATURES_VERSION = 13; - class IItemDefManager; class ITextureSource; class IShaderSource; @@ -99,17 +96,8 @@ enum NodeBoxType NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches }; -struct NodeBox +struct NodeBoxConnected { - enum NodeBoxType type; - // NODEBOX_REGULAR (no parameters) - // NODEBOX_FIXED - std::vector<aabb3f> fixed; - // NODEBOX_WALLMOUNTED - aabb3f wall_top; - aabb3f wall_bottom; - aabb3f wall_side; // being at the -X side - // NODEBOX_CONNECTED std::vector<aabb3f> connect_top; std::vector<aabb3f> connect_bottom; std::vector<aabb3f> connect_front; @@ -124,9 +112,35 @@ struct NodeBox std::vector<aabb3f> disconnected_right; std::vector<aabb3f> disconnected; std::vector<aabb3f> disconnected_sides; +}; + +struct NodeBox +{ + enum NodeBoxType type; + // NODEBOX_REGULAR (no parameters) + // NODEBOX_FIXED + std::vector<aabb3f> fixed; + // NODEBOX_WALLMOUNTED + aabb3f wall_top; + aabb3f wall_bottom; + aabb3f wall_side; // being at the -X side + // NODEBOX_CONNECTED + // (kept externally to not bloat the structure) + std::shared_ptr<NodeBoxConnected> connected; NodeBox() { reset(); } + ~NodeBox() = default; + + inline NodeBoxConnected &getConnected() { + if (!connected) + connected = std::make_shared<NodeBoxConnected>(); + return *connected; + } + inline const NodeBoxConnected &getConnected() const { + assert(connected); + return *connected; + } void reset(); void serialize(std::ostream &os, u16 protocol_version) const; @@ -269,8 +283,7 @@ struct TileDef } void serialize(std::ostream &os, u16 protocol_version) const; - void deSerialize(std::istream &is, u8 contentfeatures_version, - NodeDrawType drawtype); + void deSerialize(std::istream &is, NodeDrawType drawtype, u16 protocol_version); }; // Defines the number of special tiles per nodedef @@ -282,6 +295,10 @@ struct TileDef struct ContentFeatures { + // PROTOCOL_VERSION >= 37. This is legacy and should not be increased anymore, + // write checks that depend directly on the protocol version instead. + static const u8 CONTENTFEATURES_VERSION = 13; + /* Cached stuff */ @@ -290,7 +307,6 @@ struct ContentFeatures // up down right left back front TileSpec tiles[6]; // Special tiles - // - Currently used for flowing liquids TileSpec special_tiles[CF_SPECIAL_COUNT]; u8 solidness; // Used when choosing which face is drawn u8 visual_solidness; // When solidness=0, this tells how it looks like @@ -431,7 +447,7 @@ struct ContentFeatures ~ContentFeatures(); void reset(); void serialize(std::ostream &os, u16 protocol_version) const; - void deSerialize(std::istream &is); + void deSerialize(std::istream &is, u16 protocol_version); /* Some handy methods @@ -478,6 +494,12 @@ struct ContentFeatures return (liquid_alternative_flowing_id == f.liquid_alternative_flowing_id); } + bool lightingEquivalent(const ContentFeatures &other) const { + return light_propagates == other.light_propagates + && sunlight_propagates == other.sunlight_propagates + && light_source == other.light_source; + } + int getGroup(const std::string &group) const { return itemgroup_get(groups, group); @@ -533,7 +555,7 @@ public: */ inline const ContentFeatures& get(content_t c) const { return - c < m_content_features.size() ? + (c < m_content_features.size() && !m_content_features[c].name.empty()) ? m_content_features[c] : m_content_features[CONTENT_UNKNOWN]; } @@ -668,7 +690,7 @@ public: /*! * Writes the content of this manager to the given output stream. - * @param protocol_version serialization version of ContentFeatures + * @param protocol_version Active network protocol version */ void serialize(std::ostream &os, u16 protocol_version) const; @@ -676,8 +698,9 @@ public: * Restores the manager from a serialized stream. * This clears the previous state. * @param is input stream containing a serialized NodeDefManager + * @param protocol_version Active network protocol version */ - void deSerialize(std::istream &is); + void deSerialize(std::istream &is, u16 protocol_version); /*! * Used to indicate that node registration has finished. diff --git a/src/noise.cpp b/src/noise.cpp index 2f4de6855..99624f80d 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -35,16 +35,8 @@ #define NOISE_MAGIC_X 1619 #define NOISE_MAGIC_Y 31337 #define NOISE_MAGIC_Z 52591 -#define NOISE_MAGIC_SEED 1013 - -typedef float (*Interp2dFxn)( - float v00, float v10, float v01, float v11, - float x, float y); - -typedef float (*Interp3dFxn)( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z); +// Unsigned magic seed prevents undefined behavior. +#define NOISE_MAGIC_SEED 1013U FlagDesc flagdesc_noiseparams[] = { {"defaults", NOISE_FLAG_DEFAULTS}, @@ -197,47 +189,34 @@ inline float linearInterpolation(float v0, float v1, float t) inline float biLinearInterpolation( float v00, float v10, float v01, float v11, - float x, float y) -{ - float tx = easeCurve(x); - float ty = easeCurve(y); - float u = linearInterpolation(v00, v10, tx); - float v = linearInterpolation(v01, v11, tx); - return linearInterpolation(u, v, ty); -} - - -inline float biLinearInterpolationNoEase( - float v00, float v10, - float v01, float v11, - float x, float y) + float x, float y, + bool eased) { + // Inlining will optimize this branch out when possible + if (eased) { + x = easeCurve(x); + y = easeCurve(y); + } float u = linearInterpolation(v00, v10, x); float v = linearInterpolation(v01, v11, x); return linearInterpolation(u, v, y); } -float triLinearInterpolation( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z) -{ - float tx = easeCurve(x); - float ty = easeCurve(y); - float tz = easeCurve(z); - float u = biLinearInterpolationNoEase(v000, v100, v010, v110, tx, ty); - float v = biLinearInterpolationNoEase(v001, v101, v011, v111, tx, ty); - return linearInterpolation(u, v, tz); -} - -float triLinearInterpolationNoEase( +inline float triLinearInterpolation( float v000, float v100, float v010, float v110, float v001, float v101, float v011, float v111, - float x, float y, float z) + float x, float y, float z, + bool eased) { - float u = biLinearInterpolationNoEase(v000, v100, v010, v110, x, y); - float v = biLinearInterpolationNoEase(v001, v101, v011, v111, x, y); + // Inlining will optimize this branch out when possible + if (eased) { + x = easeCurve(x); + y = easeCurve(y); + z = easeCurve(z); + } + float u = biLinearInterpolation(v000, v100, v010, v110, x, y, false); + float v = biLinearInterpolation(v001, v101, v011, v111, x, y, false); return linearInterpolation(u, v, z); } @@ -255,10 +234,7 @@ float noise2d_gradient(float x, float y, s32 seed, bool eased) float v01 = noise2d(x0, y0+1, seed); float v11 = noise2d(x0+1, y0+1, seed); // Interpolate - if (eased) - return biLinearInterpolation(v00, v10, v01, v11, xl, yl); - - return biLinearInterpolationNoEase(v00, v10, v01, v11, xl, yl); + return biLinearInterpolation(v00, v10, v01, v11, xl, yl, eased); } @@ -282,17 +258,11 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) float v011 = noise3d(x0, y0 + 1, z0 + 1, seed); float v111 = noise3d(x0 + 1, y0 + 1, z0 + 1, seed); // Interpolate - if (eased) { - return triLinearInterpolation( - v000, v100, v010, v110, - v001, v101, v011, v111, - xl, yl, zl); - } - - return triLinearInterpolationNoEase( + return triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, - xl, yl, zl); + xl, yl, zl, + eased); } @@ -517,9 +487,6 @@ void Noise::gradientMap2D( s32 x0, y0; bool eased = np.flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED); - Interp2dFxn interpolate = eased ? - biLinearInterpolation : biLinearInterpolationNoEase; - x0 = std::floor(x); y0 = std::floor(y); u = x - (float)x0; @@ -546,7 +513,8 @@ void Noise::gradientMap2D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = interpolate(v00, v10, v01, v11, u, v); + gradient_buf[index++] = + biLinearInterpolation(v00, v10, v01, v11, u, v, eased); u += step_x; if (u >= 1.0) { @@ -582,8 +550,7 @@ void Noise::gradientMap3D( u32 nlx, nly, nlz; s32 x0, y0, z0; - Interp3dFxn interpolate = (np.flags & NOISE_FLAG_EASED) ? - triLinearInterpolation : triLinearInterpolationNoEase; + bool eased = np.flags & NOISE_FLAG_EASED; x0 = std::floor(x); y0 = std::floor(y); @@ -624,10 +591,11 @@ void Noise::gradientMap3D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = interpolate( + gradient_buf[index++] = triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, - u, v, w); + u, v, w, + eased); u += step_x; if (u >= 1.0) { diff --git a/src/particles.cpp b/src/particles.cpp index 14c987958..19b3418b7 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -18,7 +18,103 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "particles.h" -#include "util/serialize.h" +#include <type_traits> +using namespace ParticleParamTypes; + +#define PARAM_PVFN(n) ParticleParamTypes::n##ParameterValue +v2f PARAM_PVFN(pick) (float* f, const v2f a, const v2f b) { + return v2f( + numericalBlend(f[0], a.X, b.X), + numericalBlend(f[1], a.Y, b.Y) + ); +} + +v3f PARAM_PVFN(pick) (float* f, const v3f a, const v3f b) { + return v3f( + numericalBlend(f[0], a.X, b.X), + numericalBlend(f[1], a.Y, b.Y), + numericalBlend(f[2], a.Z, b.Z) + ); +} + +v2f PARAM_PVFN(interpolate) (float fac, const v2f a, const v2f b) + { return b.getInterpolated(a, fac); } +v3f PARAM_PVFN(interpolate) (float fac, const v3f a, const v3f b) + { return b.getInterpolated(a, fac); } + +#define PARAM_DEF_SRZR(T, wr, rd) \ + void PARAM_PVFN(serialize) (std::ostream& os, T v) {wr(os,v); } \ + void PARAM_PVFN(deSerialize)(std::istream& is, T& v) {v = rd(is);} + + +#define PARAM_DEF_NUM(T, wr, rd) PARAM_DEF_SRZR(T, wr, rd) \ + T PARAM_PVFN(interpolate)(float fac, const T a, const T b) \ + { return numericalBlend<T>(fac,a,b); } \ + T PARAM_PVFN(pick) (float* f, const T a, const T b) \ + { return numericalBlend<T>(f[0],a,b); } + +PARAM_DEF_NUM(u8, writeU8, readU8); PARAM_DEF_NUM(s8, writeS8, readS8); +PARAM_DEF_NUM(u16, writeU16, readU16); PARAM_DEF_NUM(s16, writeS16, readS16); +PARAM_DEF_NUM(u32, writeU32, readU32); PARAM_DEF_NUM(s32, writeS32, readS32); +PARAM_DEF_NUM(f32, writeF32, readF32); +PARAM_DEF_SRZR(v2f, writeV2F32, readV2F32); +PARAM_DEF_SRZR(v3f, writeV3F32, readV3F32); + +enum class ParticleTextureFlags : u8 { + /* each value specifies a bit in a bitmask; if the maximum value + * goes above 1<<7 the type of the flags field must be changed + * from u8, which will necessitate a protocol change! */ + + // the first bit indicates whether the texture is animated + animated = 1, + + /* the next three bits indicate the blending mode of the texture + * blendmode is encoded by (flags |= (u8)blend << 1); retrieve with + * (flags & ParticleTextureFlags::blend) >> 1. note that the third + * bit is currently reserved for adding more blend modes in the future */ + blend = 0x7 << 1, +}; + +/* define some shorthand so we don't have to repeat ourselves or use + * decltype everywhere */ +using FlagT = std::underlying_type_t<ParticleTextureFlags>; + +void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly) const +{ + /* newPropertiesOnly is used to de/serialize parameters of the legacy texture + * field, which are encoded separately from the texspec string */ + FlagT flags = 0; + if (animated) + flags |= FlagT(ParticleTextureFlags::animated); + if (blendmode != BlendMode::alpha) + flags |= FlagT(blendmode) << 1; + serializeParameterValue(os, flags); + + alpha.serialize(os); + scale.serialize(os); + if (!newPropertiesOnly) + os << serializeString32(string); + + if (animated) + animation.serialize(os, protocol_ver); +} + +void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly) +{ + FlagT flags = 0; + deSerializeParameterValue(is, flags); + + animated = !!(flags & FlagT(ParticleTextureFlags::animated)); + blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1); + + alpha.deSerialize(is); + scale.deSerialize(is); + if (!newPropertiesOnly) + string = deSerializeString32(is); + + if (animated) + animation.deSerialize(is, protocol_ver); +} void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const { @@ -28,7 +124,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const writeF32(os, expirationtime); writeF32(os, size); writeU8(os, collisiondetection); - os << serializeString32(texture); + os << serializeString32(texture.string); writeU8(os, vertical); writeU8(os, collision_removal); animation.serialize(os, 6); /* NOT the protocol ver */ @@ -37,6 +133,20 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const writeU16(os, node.param0); writeU8(os, node.param2); writeU8(os, node_tile); + writeV3F32(os, drag); + jitter.serialize(os); + bounce.serialize(os); +} + +template <typename T, T (reader)(std::istream& is)> +inline bool streamEndsBeforeParam(T& val, std::istream& is) +{ + // This is kinda awful + T tmp = reader(is); + if (is.eof()) + return true; + val = tmp; + return false; } void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) @@ -47,17 +157,20 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) expirationtime = readF32(is); size = readF32(is); collisiondetection = readU8(is); - texture = deSerializeString32(is); + texture.string = deSerializeString32(is); vertical = readU8(is); collision_removal = readU8(is); animation.deSerialize(is, 6); /* NOT the protocol ver */ glow = readU8(is); object_collision = readU8(is); - // This is kinda awful - u16 tmp_param0 = readU16(is); - if (is.eof()) + + if (streamEndsBeforeParam<u16, readU16>(node.param0, is)) return; - node.param0 = tmp_param0; node.param2 = readU8(is); node_tile = readU8(is); + + if (streamEndsBeforeParam<v3f, readV3F32>(drag, is)) + return; + jitter.deSerialize(is); + bounce.deSerialize(is); } diff --git a/src/particles.h b/src/particles.h index 6f518b771..3061deb83 100644 --- a/src/particles.h +++ b/src/particles.h @@ -20,19 +20,350 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include <string> +#include <sstream> +#include <vector> +#include <ctgmath> +#include <type_traits> #include "irrlichttypes_bloated.h" #include "tileanimation.h" #include "mapnode.h" +#include "util/serialize.h" +#include "util/numeric.h" // This file defines the particle-related structures that both the server and // client need. The ParticleManager and rendering is in client/particles.h -struct CommonParticleParams { +namespace ParticleParamTypes +{ + template <bool cond, typename T> + using enableIf = typename std::enable_if<cond, T>::type; + // std::enable_if_t does not appear to be present in GCC???? + // std::is_enum_v also missing. wtf. these are supposed to be + // present as of c++14 + + template<typename T> using BlendFunction = T(float,T,T); + #define DECL_PARAM_SRZRS(type) \ + void serializeParameterValue (std::ostream& os, type v); \ + void deSerializeParameterValue(std::istream& is, type& r); + #define DECL_PARAM_OVERLOADS(type) DECL_PARAM_SRZRS(type) \ + type interpolateParameterValue(float fac, const type a, const type b); \ + type pickParameterValue (float* facs, const type a, const type b); + + DECL_PARAM_OVERLOADS(u8); DECL_PARAM_OVERLOADS(s8); + DECL_PARAM_OVERLOADS(u16); DECL_PARAM_OVERLOADS(s16); + DECL_PARAM_OVERLOADS(u32); DECL_PARAM_OVERLOADS(s32); + DECL_PARAM_OVERLOADS(f32); + DECL_PARAM_OVERLOADS(v2f); + DECL_PARAM_OVERLOADS(v3f); + + /* C++ is a strongly typed language. this means that enums cannot be implicitly + * cast to integers, as they can be in C. while this may sound good in principle, + * it means that our normal serialization functions cannot be called on + * enumerations unless they are explicitly cast to a particular type first. this + * is problematic, because in C++ enums can have any integral type as an underlying + * type, and that type would need to be named everywhere an enumeration is + * de/serialized. + * + * this is obviously not cool, both in terms of writing legible, succinct code, + * and in terms of robustness: the underlying type might be changed at some point, + * e.g. if a bitmask gets too big for its britches. we could use an equivalent of + * `std::to_underlying(value)` everywhere we need to deal with enumerations, but + * that's hideous and unintuitive. instead, we supply the following functions to + * transparently map enumeration types to their underlying values. */ + + template <typename E, enableIf<std::is_enum<E>::value, bool> = true> + void serializeParameterValue(std::ostream& os, E k) { + serializeParameterValue(os, (std::underlying_type_t<E>)k); + } + + template <typename E, enableIf<std::is_enum<E>::value, bool> = true> + void deSerializeParameterValue(std::istream& is, E& k) { + std::underlying_type_t<E> v; + deSerializeParameterValue(is, v); + k = (E)v; + } + + /* this is your brain on C++. */ + + template <typename T, size_t PN> + struct Parameter + { + using ValType = T; + using pickFactors = float[PN]; + + T val = T(); + using This = Parameter<T, PN>; + + Parameter() = default; + + template <typename... Args> + Parameter(Args... args) : val(args...) {} + + virtual void serialize(std::ostream &os) const + { serializeParameterValue (os, this->val); } + virtual void deSerialize(std::istream &is) + { deSerializeParameterValue(is, this->val); } + + virtual T interpolate(float fac, const This& against) const + { + return interpolateParameterValue(fac, this->val, against.val); + } + + static T pick(float* f, const This& a, const This& b) + { + return pickParameterValue(f, a.val, b.val); + } + + operator T() const { return val; } + T operator=(T b) { return val = b; } + + }; + + template <typename T> T numericalBlend(float fac, T min, T max) + { return min + ((max - min) * fac); } + + template <typename T, size_t N> + struct VectorParameter : public Parameter<T,N> { + using This = VectorParameter<T,N>; + template <typename... Args> + VectorParameter(Args... args) : Parameter<T,N>(args...) {} + }; + + template <typename T, size_t PN> + inline std::string dump(const Parameter<T,PN>& p) + { + return std::to_string(p.val); + } + + template <typename T, size_t N> + inline std::string dump(const VectorParameter<T,N>& v) + { + std::ostringstream oss; + if (N == 3) + oss << PP(v.val); + else + oss << PP2(v.val); + return oss.str(); + } + + using u8Parameter = Parameter<u8, 1>; using s8Parameter = Parameter<s8, 1>; + using u16Parameter = Parameter<u16, 1>; using s16Parameter = Parameter<s16, 1>; + using u32Parameter = Parameter<u32, 1>; using s32Parameter = Parameter<s32, 1>; + + using f32Parameter = Parameter<f32, 1>; + + using v2fParameter = VectorParameter<v2f, 2>; + using v3fParameter = VectorParameter<v3f, 3>; + + template <typename T> + struct RangedParameter + { + using ValType = T; + using This = RangedParameter<T>; + + T min, max; + f32 bias = 0; + + RangedParameter() = default; + RangedParameter(T _min, T _max) : min(_min), max(_max) {} + template <typename M> RangedParameter(M b) : min(b), max(b) {} + + // these functions handle the old range serialization "format"; bias must + // be manually encoded in a separate part of the stream. NEVER ADD FIELDS + // TO THESE FUNCTIONS + void legacySerialize(std::ostream& os) const + { + min.serialize(os); + max.serialize(os); + } + void legacyDeSerialize(std::istream& is) + { + min.deSerialize(is); + max.deSerialize(is); + } + + // these functions handle the format used by new fields. new fields go here + void serialize(std::ostream &os) const + { + legacySerialize(os); + writeF32(os, bias); + } + void deSerialize(std::istream &is) + { + legacyDeSerialize(is); + bias = readF32(is); + } + + This interpolate(float fac, const This against) const + { + This r; + r.min = min.interpolate(fac, against.min); + r.max = max.interpolate(fac, against.max); + r.bias = bias; + return r; + } + + T pickWithin() const + { + typename T::pickFactors values; + auto p = numericAbsolute(bias) + 1; + for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) { + if (bias < 0) + values[i] = 1.0f - pow(myrand_float(), p); + else + values[i] = pow(myrand_float(), p); + } + return T::pick(values, min, max); + } + + }; + + template <typename T> + inline std::string dump(const RangedParameter<T>& r) + { + std::ostringstream s; + s << "range<" << dump(r.min) << " ~ " << dump(r.max); + if (r.bias != 0) + s << " :: " << r.bias; + s << ">"; + return s.str(); + } + + enum class TweenStyle : u8 { fwd, rev, pulse, flicker }; + + template <typename T> + struct TweenedParameter + { + using ValType = T; + using This = TweenedParameter<T>; + + TweenStyle style = TweenStyle::fwd; + u16 reps = 1; + f32 beginning = 0.0f; + + T start, end; + + TweenedParameter() = default; + TweenedParameter(T _start, T _end) : start(_start), end(_end) {} + template <typename M> TweenedParameter(M b) : start(b), end(b) {} + + T blend(float fac) const + { + // warp time coordinates in accordance w/ settings + if (fac > beginning) { + // remap for beginning offset + auto len = 1 - beginning; + fac -= beginning; + fac /= len; + + // remap for repetitions + fac *= reps; + if (fac > 1) // poor man's modulo + fac -= (decltype(reps))fac; + + // remap for style + switch (style) { + case TweenStyle::fwd: /* do nothing */ break; + case TweenStyle::rev: fac = 1.0f - fac; break; + case TweenStyle::pulse: + case TweenStyle::flicker: { + if (fac > 0.5f) { + fac = 1.f - (fac*2.f - 1.f); + } else { + fac = fac * 2; + } + if (style == TweenStyle::flicker) { + fac *= myrand_range(0.7f, 1.0f); + } + } + } + if (fac>1.f) + fac = 1.f; + else if (fac<0.f) + fac = 0.f; + } else { + fac = (style == TweenStyle::rev) ? 1.f : 0.f; + } + + return start.interpolate(fac, end); + } + + void serialize(std::ostream &os) const + { + writeU8(os, static_cast<u8>(style)); + writeU16(os, reps); + writeF32(os, beginning); + start.serialize(os); + end.serialize(os); + } + void deSerialize(std::istream &is) + { + style = static_cast<TweenStyle>(readU8(is)); + reps = readU16(is); + beginning = readF32(is); + start.deSerialize(is); + end.deSerialize(is); + } + }; + + template <typename T> + inline std::string dump(const TweenedParameter<T>& t) + { + std::ostringstream s; + const char* icon; + switch (t.style) { + case TweenStyle::fwd: icon = "→"; break; + case TweenStyle::rev: icon = "←"; break; + case TweenStyle::pulse: icon = "↔"; break; + case TweenStyle::flicker: icon = "↯"; break; + } + s << "tween<"; + if (t.reps != 1) + s << t.reps << "x "; + s << dump(t.start) << " "<<icon<<" " << dump(t.end) << ">"; + return s.str(); + } + + enum class AttractorKind : u8 { none, point, line, plane }; + enum class BlendMode : u8 { alpha, add, sub, screen }; + + // these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations + using v3fRange = RangedParameter<v3fParameter>; + using f32Range = RangedParameter<f32Parameter>; + + using v2fTween = TweenedParameter<v2fParameter>; + using v3fTween = TweenedParameter<v3fParameter>; + using f32Tween = TweenedParameter<f32Parameter>; + using v3fRangeTween = TweenedParameter<v3fRange>; + using f32RangeTween = TweenedParameter<f32Range>; + + #undef DECL_PARAM_SRZRS + #undef DECL_PARAM_OVERLOADS +} + +struct ParticleTexture +{ + bool animated = false; + ParticleParamTypes::BlendMode blendmode = ParticleParamTypes::BlendMode::alpha; + TileAnimationParams animation; + ParticleParamTypes::f32Tween alpha{1.0f}; + ParticleParamTypes::v2fTween scale{v2f(1.0f)}; +}; + +struct ServerParticleTexture : public ParticleTexture +{ + std::string string; + void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false) const; + void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false); +}; + +struct CommonParticleParams +{ bool collisiondetection = false; bool collision_removal = false; bool object_collision = false; bool vertical = false; - std::string texture; + ServerParticleTexture texture; struct TileAnimationParams animation; u8 glow = 0; MapNode node; @@ -58,22 +389,42 @@ struct CommonParticleParams { } }; -struct ParticleParameters : CommonParticleParams { - v3f pos; - v3f vel; - v3f acc; - f32 expirationtime = 1; - f32 size = 1; +struct ParticleParameters : CommonParticleParams +{ + v3f pos, vel, acc, drag; + f32 size = 1, expirationtime = 1; + ParticleParamTypes::f32Range bounce; + ParticleParamTypes::v3fRange jitter; void serialize(std::ostream &os, u16 protocol_ver) const; void deSerialize(std::istream &is, u16 protocol_ver); }; -struct ParticleSpawnerParameters : CommonParticleParams { +struct ParticleSpawnerParameters : CommonParticleParams +{ u16 amount = 1; - v3f minpos, maxpos, minvel, maxvel, minacc, maxacc; f32 time = 1; - f32 minexptime = 1, maxexptime = 1, minsize = 1, maxsize = 1; + + std::vector<ServerParticleTexture> texpool; + + ParticleParamTypes::v3fRangeTween + pos, vel, acc, drag, radius, jitter; + + ParticleParamTypes::AttractorKind + attractor_kind; + ParticleParamTypes::v3fTween + attractor_origin, attractor_direction; + // object IDs + u16 attractor_attachment = 0, + attractor_direction_attachment = 0; + // do particles disappear when they cross the attractor threshold? + bool attractor_kill = true; + + ParticleParamTypes::f32RangeTween + exptime{1.0f}, + size {1.0f}, + attract{0.0f}, + bounce {0.0f}; // For historical reasons no (de-)serialization methods here }; diff --git a/src/player.cpp b/src/player.cpp index 347be30f1..1e064c1da 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -71,7 +71,7 @@ Player::Player(const char *name, IItemDefManager *idef): HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE | - HUD_FLAG_MINIMAP_RADAR_VISIBLE; + HUD_FLAG_MINIMAP_RADAR_VISIBLE | HUD_FLAG_BASIC_DEBUG; hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT; diff --git a/src/player.h b/src/player.h index d769acdad..beca82f66 100644 --- a/src/player.h +++ b/src/player.h @@ -134,12 +134,12 @@ public: std::vector<CollisionInfo> *collision_info) {} - const v3f &getSpeed() const + v3f getSpeed() const { return m_speed; } - void setSpeed(const v3f &speed) + void setSpeed(v3f speed) { m_speed = speed; } diff --git a/src/porting.cpp b/src/porting.cpp index caf9e9be3..09627431c 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -608,7 +608,7 @@ void initializePaths() // First try $XDG_CACHE_HOME/PROJECT_NAME const char *cache_dir = getenv("XDG_CACHE_HOME"); const char *home_dir = getenv("HOME"); - if (cache_dir) { + if (cache_dir && cache_dir[0] != '\0') { path_cache = std::string(cache_dir) + DIR_DELIM + PROJECT_NAME; } else if (home_dir) { // Then try $HOME/.cache/PROJECT_NAME diff --git a/src/porting_android.cpp b/src/porting_android.cpp index c71fe5ad8..83b590b99 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -213,6 +213,18 @@ void openURIAndroid(const std::string &url) jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl); } +void shareFileAndroid(const std::string &path) +{ + jmethodID url_open = jnienv->GetMethodID(nativeActivity, "shareFile", + "(Ljava/lang/String;)V"); + + FATAL_ERROR_IF(url_open == nullptr, + "porting::shareFileAndroid unable to find java openURI method"); + + jstring jurl = jnienv->NewStringUTF(path.c_str()); + jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl); +} + int getInputDialogState() { jmethodID dialogstate = jnienv->GetMethodID(nativeActivity, diff --git a/src/porting_android.h b/src/porting_android.h index 239815922..265825fbd 100644 --- a/src/porting_android.h +++ b/src/porting_android.h @@ -61,6 +61,13 @@ void showInputDialog(const std::string &acceptButton, void openURIAndroid(const std::string &url); /** + * Opens a share intent to the file at path + * + * @param path + */ +void shareFileAndroid(const std::string &path); + +/** * WORKAROUND for not working callbacks from java -> c++ * get current state of input dialog */ diff --git a/src/remoteplayer.h b/src/remoteplayer.h index e33630841..0ab33adfe 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "skyparams.h" +#include "lighting.h" class PlayerSAO; @@ -125,6 +126,10 @@ public: *frame_speed = local_animation_speed; } + void setLighting(const Lighting &lighting) { m_lighting = lighting; } + + const Lighting& getLighting() const { return m_lighting; } + void setDirty(bool dirty) { m_dirty = true; } u16 protocol_version = 0; @@ -160,5 +165,7 @@ private: MoonParams m_moon_params; StarParams m_star_params; + Lighting m_lighting; + session_t m_peer_id = PEER_ID_INEXISTENT; }; diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt index d07f6ab1b..3e84b46c7 100644 --- a/src/script/common/CMakeLists.txt +++ b/src/script/common/CMakeLists.txt @@ -3,6 +3,7 @@ set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c_packer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/helper.cpp PARENT_SCOPE) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 8a5a3fe71..166980025 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -42,7 +42,7 @@ struct EnumString es_TileAnimationType[] = {TAT_NONE, "none"}, {TAT_VERTICAL_FRAMES, "vertical_frames"}, {TAT_SHEET_2D, "sheet_2d"}, - {0, NULL}, + {0, nullptr}, }; /******************************************************************************/ @@ -198,7 +198,7 @@ void read_object_properties(lua_State *L, int index, prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX); if (prop->hp_max < sao->getHP()) { - PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); + PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP_MAX); sao->setHP(prop->hp_max, reason); } } @@ -550,13 +550,6 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // tiles = {} lua_getfield(L, index, "tiles"); - // If nil, try the deprecated name "tile_images" instead - if(lua_isnil(L, -1)){ - lua_pop(L, 1); - warn_if_field_exists(L, index, "tile_images", - "Deprecated; new name is \"tiles\"."); - lua_getfield(L, index, "tile_images"); - } if(lua_istable(L, -1)){ int table = lua_gettop(L); lua_pushnil(L); @@ -613,13 +606,6 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // special_tiles = {} lua_getfield(L, index, "special_tiles"); - // If nil, try the deprecated name "special_materials" instead - if(lua_isnil(L, -1)){ - lua_pop(L, 1); - warn_if_field_exists(L, index, "special_materials", - "Deprecated; new name is \"special_tiles\"."); - lua_getfield(L, index, "special_materials"); - } if(lua_istable(L, -1)){ int table = lua_gettop(L); lua_pushnil(L); @@ -1000,22 +986,25 @@ void push_nodebox(lua_State *L, const NodeBox &box) push_aabb3f(L, box.wall_side); lua_setfield(L, -2, "wall_side"); break; - case NODEBOX_CONNECTED: + case NODEBOX_CONNECTED: { lua_pushstring(L, "connected"); lua_setfield(L, -2, "type"); - push_box(L, box.connect_top); + const auto &c = box.getConnected(); + push_box(L, c.connect_top); lua_setfield(L, -2, "connect_top"); - push_box(L, box.connect_bottom); + push_box(L, c.connect_bottom); lua_setfield(L, -2, "connect_bottom"); - push_box(L, box.connect_front); + push_box(L, c.connect_front); lua_setfield(L, -2, "connect_front"); - push_box(L, box.connect_back); + push_box(L, c.connect_back); lua_setfield(L, -2, "connect_back"); - push_box(L, box.connect_left); + push_box(L, c.connect_left); lua_setfield(L, -2, "connect_left"); - push_box(L, box.connect_right); + push_box(L, c.connect_right); lua_setfield(L, -2, "connect_right"); + // half the boxes are missing here? break; + } default: FATAL_ERROR("Invalid box.type"); break; @@ -1048,22 +1037,26 @@ void push_palette(lua_State *L, const std::vector<video::SColor> *palette) /******************************************************************************/ void read_server_sound_params(lua_State *L, int index, - ServerSoundParams ¶ms) + ServerPlayingSound ¶ms) { if(index < 0) index = lua_gettop(L) + 1 + index; - // Clear - params = ServerSoundParams(); + if(lua_istable(L, index)){ + // Functional overlap: this may modify SimpleSoundSpec contents + getfloatfield(L, index, "fade", params.spec.fade); + getfloatfield(L, index, "pitch", params.spec.pitch); + getboolfield(L, index, "loop", params.spec.loop); + getfloatfield(L, index, "gain", params.gain); + + // Handle positional information getstringfield(L, index, "to_player", params.to_player); - getfloatfield(L, index, "fade", params.fade); - getfloatfield(L, index, "pitch", params.pitch); lua_getfield(L, index, "pos"); if(!lua_isnil(L, -1)){ v3f p = read_v3f(L, -1)*BS; params.pos = p; - params.type = ServerSoundParams::SSP_POSITIONAL; + params.type = SoundLocation::Position; } lua_pop(L, 1); lua_getfield(L, index, "object"); @@ -1072,13 +1065,12 @@ void read_server_sound_params(lua_State *L, int index, ServerActiveObject *sao = ObjectRef::getobject(ref); if(sao){ params.object = sao->getId(); - params.type = ServerSoundParams::SSP_OBJECT; + params.type = SoundLocation::Object; } } lua_pop(L, 1); params.max_hear_distance = BS*getfloatfield_default(L, index, "max_hear_distance", params.max_hear_distance/BS); - getboolfield(L, index, "loop", params.loop); getstringfield(L, index, "exclude_player", params.exclude_player); } } @@ -1143,20 +1135,24 @@ NodeBox read_nodebox(lua_State *L, int index) NODEBOXREAD(nodebox.wall_top, "wall_top"); NODEBOXREAD(nodebox.wall_bottom, "wall_bottom"); NODEBOXREAD(nodebox.wall_side, "wall_side"); - NODEBOXREADVEC(nodebox.connect_top, "connect_top"); - NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom"); - NODEBOXREADVEC(nodebox.connect_front, "connect_front"); - NODEBOXREADVEC(nodebox.connect_left, "connect_left"); - NODEBOXREADVEC(nodebox.connect_back, "connect_back"); - NODEBOXREADVEC(nodebox.connect_right, "connect_right"); - NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top"); - NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom"); - NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front"); - NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left"); - NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back"); - NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right"); - NODEBOXREADVEC(nodebox.disconnected, "disconnected"); - NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides"); + + if (nodebox.type == NODEBOX_CONNECTED) { + auto &c = nodebox.getConnected(); + NODEBOXREADVEC(c.connect_top, "connect_top"); + NODEBOXREADVEC(c.connect_bottom, "connect_bottom"); + NODEBOXREADVEC(c.connect_front, "connect_front"); + NODEBOXREADVEC(c.connect_left, "connect_left"); + NODEBOXREADVEC(c.connect_back, "connect_back"); + NODEBOXREADVEC(c.connect_right, "connect_right"); + NODEBOXREADVEC(c.disconnected_top, "disconnected_top"); + NODEBOXREADVEC(c.disconnected_bottom, "disconnected_bottom"); + NODEBOXREADVEC(c.disconnected_front, "disconnected_front"); + NODEBOXREADVEC(c.disconnected_left, "disconnected_left"); + NODEBOXREADVEC(c.disconnected_back, "disconnected_back"); + NODEBOXREADVEC(c.disconnected_right, "disconnected_right"); + NODEBOXREADVEC(c.disconnected, "disconnected"); + NODEBOXREADVEC(c.disconnected_sides, "disconnected_sides"); + } return nodebox; } @@ -1351,22 +1347,27 @@ void push_tool_capabilities(lua_State *L, } /******************************************************************************/ -void push_inventory_list(lua_State *L, Inventory *inv, const char *name) +void push_inventory_list(lua_State *L, const InventoryList &invlist) { - InventoryList *invlist = inv->getList(name); - if(invlist == NULL){ - lua_pushnil(L); - return; + push_items(L, invlist.getItems()); +} + +/******************************************************************************/ +void push_inventory_lists(lua_State *L, const Inventory &inv) +{ + const auto &lists = inv.getLists(); + lua_createtable(L, 0, lists.size()); + for(const InventoryList *list : lists) { + const std::string &name = list->getName(); + lua_pushlstring(L, name.c_str(), name.size()); + push_inventory_list(L, *list); + lua_rawset(L, -3); } - std::vector<ItemStack> items; - for(u32 i=0; i<invlist->getSize(); i++) - items.push_back(invlist->getItem(i)); - push_items(L, items); } /******************************************************************************/ void read_inventory_list(lua_State *L, int tableindex, - Inventory *inv, const char *name, Server* srv, int forcesize) + Inventory *inv, const char *name, IGameDef *gdef, int forcesize) { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; @@ -1378,7 +1379,7 @@ void read_inventory_list(lua_State *L, int tableindex, } // Get Lua-specified items to insert into the list - std::vector<ItemStack> items = read_items(L, tableindex,srv); + std::vector<ItemStack> items = read_items(L, tableindex, gdef); size_t listsize = (forcesize >= 0) ? forcesize : items.size(); // Create or resize/clear list @@ -1630,7 +1631,7 @@ void push_items(lua_State *L, const std::vector<ItemStack> &items) } /******************************************************************************/ -std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) +std::vector<ItemStack> read_items(lua_State *L, int index, IGameDef *gdef) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -1646,7 +1647,7 @@ std::vector<ItemStack> read_items(lua_State *L, int index, Server *srv) if (items.size() < (u32) key) { items.resize(key); } - items[key - 1] = read_item(L, -1, srv->idef()); + items[key - 1] = read_item(L, -1, gdef->idef()); lua_pop(L, 1); } return items; @@ -1697,24 +1698,19 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) void push_noiseparams(lua_State *L, NoiseParams *np) { lua_newtable(L); - push_float_string(L, np->offset); - lua_setfield(L, -2, "offset"); - push_float_string(L, np->scale); - lua_setfield(L, -2, "scale"); - push_float_string(L, np->persist); - lua_setfield(L, -2, "persistence"); - push_float_string(L, np->lacunarity); - lua_setfield(L, -2, "lacunarity"); - lua_pushnumber(L, np->seed); - lua_setfield(L, -2, "seed"); - lua_pushnumber(L, np->octaves); - lua_setfield(L, -2, "octaves"); + setfloatfield(L, -1, "offset", np->offset); + setfloatfield(L, -1, "scale", np->scale); + setfloatfield(L, -1, "persist", np->persist); + setfloatfield(L, -1, "persistence", np->persist); + setfloatfield(L, -1, "lacunarity", np->lacunarity); + setintfield( L, -1, "seed", np->seed); + setintfield( L, -1, "octaves", np->octaves); push_flags_string(L, flagdesc_noiseparams, np->flags, np->flags); lua_setfield(L, -2, "flags"); - push_v3_float_string(L, np->spread); + push_v3f(L, np->spread); lua_setfield(L, -2, "spread"); } @@ -1977,6 +1973,12 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushnumber(L, elem->number); lua_setfield(L, -2, "number"); + if (elem->type == HUD_ELEM_WAYPOINT) { + // waypoints reuse the item field to store precision, precision = item - 1 + lua_pushnumber(L, elem->item - 1); + lua_setfield(L, -2, "precision"); + } + // push the item field for waypoints as well for backwards compatibility lua_pushnumber(L, elem->item); lua_setfield(L, -2, "item"); @@ -2141,3 +2143,35 @@ void push_collision_move_result(lua_State *L, const collisionMoveResult &res) lua_setfield(L, -2, "collisions"); /**/ } + + +void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied) +{ + lua_newtable(L); + + lua_pushstring(L, spec.name.c_str()); + lua_setfield(L, -2, "name"); + + lua_pushstring(L, spec.author.c_str()); + lua_setfield(L, -2, "author"); + + lua_pushinteger(L, spec.release); + lua_setfield(L, -2, "release"); + + lua_pushstring(L, spec.desc.c_str()); + lua_setfield(L, -2, "description"); + + lua_pushstring(L, spec.path.c_str()); + lua_setfield(L, -2, "path"); + + lua_pushstring(L, spec.virtual_path.c_str()); + lua_setfield(L, -2, "virtual_path"); + + lua_newtable(L); + int i = 1; + for (const auto &dep : spec.unsatisfied_depends) { + lua_pushstring(L, dep.c_str()); + lua_rawseti(L, -2, i++); + } + lua_setfield(L, -2, "unsatisfied_depends"); +} diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index e762604a4..ade3e4c1e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -42,6 +42,7 @@ extern "C" { // We do a explicit path include because by default c_content.h include src/client/hud.h // prior to the src/hud.h, which is not good on server only build #include "../../hud.h" +#include "content/mods.h" namespace Json { class Value; } @@ -53,12 +54,13 @@ struct ItemDefinition; struct ToolCapabilities; struct ObjectProperties; struct SimpleSoundSpec; -struct ServerSoundParams; +struct ServerPlayingSound; class Inventory; +class InventoryList; struct NodeBox; struct ContentFeatures; struct TileDef; -class Server; +class IGameDef; struct DigParams; struct HitParams; struct EnumString; @@ -90,7 +92,7 @@ void read_soundspec (lua_State *L, int index, NodeBox read_nodebox (lua_State *L, int index); void read_server_sound_params (lua_State *L, int index, - ServerSoundParams ¶ms); + ServerPlayingSound ¶ms); void push_dig_params (lua_State *L, const DigParams ¶ms); @@ -120,11 +122,12 @@ void push_object_properties (lua_State *L, ObjectProperties *prop); void push_inventory_list (lua_State *L, - Inventory *inv, - const char *name); + const InventoryList &invlist); +void push_inventory_lists (lua_State *L, + const Inventory &inv); void read_inventory_list (lua_State *L, int tableindex, Inventory *inv, const char *name, - Server *srv, int forcesize=-1); + IGameDef *gdef, int forcesize=-1); MapNode readnode (lua_State *L, int index, const NodeDefManager *ndef); @@ -164,7 +167,7 @@ void push_items (lua_State *L, std::vector<ItemStack> read_items (lua_State *L, int index, - Server* srv); + IGameDef* gdef); void push_soundspec (lua_State *L, const SimpleSoundSpec &spec); @@ -202,3 +205,5 @@ void push_hud_element (lua_State *L, HudElement *elem); bool read_hud_change (lua_State *L, HudElementStat &stat, HudElement *elem, void **value); void push_collision_move_result(lua_State *L, const collisionMoveResult &res); + +void push_mod_spec(lua_State *L, const ModSpec &spec, bool include_unsatisfied); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 7898b197d..69da35b73 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -18,8 +18,8 @@ with this program; if not, write to the Free Software Foundation, Inc., */ extern "C" { -#include "lua.h" -#include "lauxlib.h" +#include <lua.h> +#include <lauxlib.h> } #include "util/numeric.h" @@ -29,55 +29,52 @@ extern "C" { #include "common/c_internal.h" #include "constants.h" #include <set> +#include <cmath> -#define CHECK_TYPE(index, name, type) { \ +#define CHECK_TYPE(index, name, type) do { \ int t = lua_type(L, (index)); \ if (t != (type)) { \ throw LuaError(std::string("Invalid ") + (name) + \ " (expected " + lua_typename(L, (type)) + \ " got " + lua_typename(L, t) + ")."); \ } \ - } -#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) + } while(0) + +#define CHECK_FLOAT(value, name) do {\ + if (std::isnan(value) || std::isinf(value)) { \ + throw LuaError("Invalid float value for '" name \ + "' (NaN or infinity)"); \ + } \ + } while (0) + +#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "vector coordinate " name, LUA_TNUMBER) +#define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE) /** - * A helper which sets (if available) the vector metatable from builtin as metatable - * for the table on top of the stack + * A helper which sets the vector metatable for the table on top of the stack */ static void set_vector_metatable(lua_State *L) { - // get vector.metatable - lua_getglobal(L, "vector"); - if (!lua_istable(L, -1)) { - // there is no global vector table - lua_pop(L, 1); - errorstream << "set_vector_metatable in c_converter.cpp: " << - "missing global vector table" << std::endl; - return; - } - lua_getfield(L, -1, "metatable"); - // set the metatable - lua_setmetatable(L, -3); - // pop vector global - lua_pop(L, 1); + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setmetatable(L, -2); } - -void push_float_string(lua_State *L, float value) +// Retrieve an integer vector where all components are optional +template<class T> +static bool getv3intfield(lua_State *L, int index, + const char *fieldname, T &result) { - auto str = ftos(value); - lua_pushstring(L, str.c_str()); + lua_getfield(L, index, fieldname); + bool got = false; + if (lua_istable(L, -1)) { + got |= getintfield(L, -1, "x", result.X); + got |= getintfield(L, -1, "y", result.Y); + got |= getintfield(L, -1, "z", result.Z); + } + lua_pop(L, 1); + return got; } void push_v3f(lua_State *L, v3f p) @@ -101,26 +98,6 @@ void push_v2f(lua_State *L, v2f p) lua_setfield(L, -2, "y"); } -void push_v3_float_string(lua_State *L, v3f p) -{ - lua_createtable(L, 0, 3); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); - push_float_string(L, p.Z); - lua_setfield(L, -2, "z"); -} - -void push_v2_float_string(lua_State *L, v2f p) -{ - lua_createtable(L, 0, 2); - push_float_string(L, p.X); - lua_setfield(L, -2, "x"); - push_float_string(L, p.Y); - lua_setfield(L, -2, "y"); -} - v2s16 read_v2s16(lua_State *L, int index) { v2s16 p; @@ -185,10 +162,12 @@ v2f check_v2f(lua_State *L, int index) lua_getfield(L, index, "x"); CHECK_POS_COORD("x"); p.X = lua_tonumber(L, -1); + CHECK_FLOAT(p.X, "x"); lua_pop(L, 1); lua_getfield(L, index, "y"); CHECK_POS_COORD("y"); p.Y = lua_tonumber(L, -1); + CHECK_FLOAT(p.Y, "y"); lua_pop(L, 1); return p; } @@ -216,17 +195,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") + CHECK_FLOAT(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") + CHECK_FLOAT(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") + CHECK_FLOAT(pos.Z, "z"); lua_pop(L, 1); return pos; } @@ -254,17 +233,17 @@ v3d check_v3d(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") + CHECK_FLOAT(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") + CHECK_FLOAT(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") + CHECK_FLOAT(pos.Z, "z"); lua_pop(L, 1); return pos; } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 6ad6f3212..2af726d16 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -61,23 +61,10 @@ bool getintfield(lua_State *L, int table, return got; } -template<class T> -bool getv3intfield(lua_State *L, int index, - const char *fieldname, T &result) -{ - lua_getfield(L, index, fieldname); - bool got = false; - if (lua_istable(L, -1)) { - got |= getintfield(L, -1, "x", result.X); - got |= getintfield(L, -1, "y", result.Y); - got |= getintfield(L, -1, "z", result.Z); - } - lua_pop(L, 1); - return got; -} - +// Retrieve an v3s16 where all components are optional (falls back to default) v3s16 getv3s16field_default(lua_State *L, int table, const char *fieldname, v3s16 default_); + bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result); size_t getstringlistfield(lua_State *L, int table, @@ -100,6 +87,7 @@ void setboolfield(lua_State *L, int table, const char *fieldname, bool value); v3f checkFloatPos (lua_State *L, int index); +v2f check_v2f (lua_State *L, int index); v3f check_v3f (lua_State *L, int index); v3s16 check_v3s16 (lua_State *L, int index); @@ -118,9 +106,6 @@ std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale); size_t read_stringlist (lua_State *L, int index, std::vector<std::string> *result); -void push_float_string (lua_State *L, float value); -void push_v3_float_string(lua_State *L, v3f p); -void push_v2_float_string(lua_State *L, v2f p); void push_v2s16 (lua_State *L, v2s16 p); void push_v2s32 (lua_State *L, v2s32 p); void push_v3s16 (lua_State *L, v3s16 p); diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index df82dba14..ddd2d184c 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -166,3 +166,17 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth) infostream << script_get_backtrace(L) << std::endl; } +void call_string_dump(lua_State *L, int idx) +{ + // Retrieve string.dump from insecure env to avoid it being tampered with + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); + if (!lua_isnil(L, -1)) + lua_getfield(L, -1, "string"); + else + lua_getglobal(L, "string"); + lua_getfield(L, -1, "dump"); + lua_remove(L, -2); // remove _G + lua_remove(L, -2); // remove 'string' table + lua_pushvalue(L, idx); + lua_call(L, 1, 1); +} diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 94cfd61fb..272a39941 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -55,6 +55,8 @@ extern "C" { #define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2) #define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3) #define CUSTOM_RIDX_HTTP_API_LUA (CUSTOM_RIDX_BASE + 4) +#define CUSTOM_RIDX_VECTOR_METATABLE (CUSTOM_RIDX_BASE + 5) +#define CUSTOM_RIDX_METATABLE_MAP (CUSTOM_RIDX_BASE + 6) // Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata @@ -138,3 +140,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); + +// Safely call string.dump on a function value +// (does not pop, leaves one value on stack) +void call_string_dump(lua_State *L, int idx); diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp new file mode 100644 index 000000000..597f5e447 --- /dev/null +++ b/src/script/common/c_packer.cpp @@ -0,0 +1,596 @@ +/* +Minetest +Copyright (C) 2022 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 <cstdio> +#include <cstring> +#include <cmath> +#include <cassert> +#include <unordered_set> +#include <unordered_map> +#include "c_packer.h" +#include "c_internal.h" +#include "log.h" +#include "debug.h" +#include "threading/mutex_auto_lock.h" + +extern "C" { +#include <lauxlib.h> +} + +// +// Helpers +// + +// convert negative index to absolute position on Lua stack +static inline int absidx(lua_State *L, int idx) +{ + assert(idx < 0); + return lua_gettop(L) + idx + 1; +} + +// does the type put anything into PackedInstr::sdata? +static inline bool uses_sdata(int type) +{ + switch (type) { + case LUA_TSTRING: + case LUA_TFUNCTION: + case LUA_TUSERDATA: + return true; + default: + return false; + } +} + +// does the type put anything into PackedInstr::<union>? +static inline bool uses_union(int type) +{ + switch (type) { + case LUA_TNIL: + case LUA_TSTRING: + case LUA_TFUNCTION: + return false; + default: + return true; + } +} + +static inline bool can_set_into(int ktype, int vtype) +{ + switch (ktype) { + case LUA_TNUMBER: + return !uses_union(vtype); + case LUA_TSTRING: + return !uses_sdata(vtype); + default: + return false; + } +} + +// is the key suitable for use with set_into? +static inline bool suitable_key(lua_State *L, int idx) +{ + if (lua_type(L, idx) == LUA_TSTRING) { + // strings may not have a NULL byte (-> lua_setfield) + size_t len; + const char *str = lua_tolstring(L, idx, &len); + return strlen(str) == len; + } else { + assert(lua_type(L, idx) == LUA_TNUMBER); + // numbers must fit into an s32 and be integers (-> lua_rawseti) + lua_Number n = lua_tonumber(L, idx); + return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX; + } +} + +namespace { + // checks if you left any values on the stack, for debugging + class StackChecker { + lua_State *L; + int top; + public: + StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {} + ~StackChecker() { + assert(lua_gettop(L) >= top); + if (lua_gettop(L) > top) { + rawstream << "Lua stack not cleaned up: " + << lua_gettop(L) << " != " << top + << " (false-positive if exception thrown)" << std::endl; + } + } + }; + + // Since an std::vector may reallocate, this is the only safe way to keep + // a reference to a particular element. + template <typename T> + class VectorRef { + std::vector<T> *vec; + size_t idx; + VectorRef(std::vector<T> *vec, size_t idx) : vec(vec), idx(idx) {} + public: + constexpr VectorRef() : vec(nullptr), idx(0) {} + static VectorRef<T> front(std::vector<T> &vec) { + return VectorRef(&vec, 0); + } + static VectorRef<T> back(std::vector<T> &vec) { + return VectorRef(&vec, vec.size() - 1); + } + T &operator*() { return (*vec)[idx]; } + T *operator->() { return &(*vec)[idx]; } + operator bool() const { return vec != nullptr; } + }; + + struct Packer { + PackInFunc fin; + PackOutFunc fout; + }; + + typedef std::pair<std::string, Packer> PackerTuple; +} + +static inline auto emplace(PackedValue &pv, s16 type) +{ + pv.i.emplace_back(); + auto ref = VectorRef<PackedInstr>::back(pv.i); + ref->type = type; + // Initialize fields that may be left untouched + if (type == LUA_TTABLE) { + ref->uidata1 = 0; + ref->uidata2 = 0; + } else if (type == LUA_TUSERDATA) { + ref->ptrdata = nullptr; + } else if (type == INSTR_POP) { + ref->sidata2 = 0; + } + return ref; +} + +// +// Management of registered packers +// + +static std::unordered_map<std::string, Packer> g_packers; +static std::mutex g_packers_lock; + +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout) +{ + // Store away callbacks + { + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) { + auto &ref = g_packers[regname]; + ref.fin = fin; + ref.fout = fout; + } else { + FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout, + "Packer registered twice with mismatching callbacks"); + } + } + + // Save metatable so we can identify instances later + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + if (lua_isnil(L, -1)) { + lua_newtable(L); + lua_pushvalue(L, -1); + lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + } + + luaL_getmetatable(L, regname); + FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name"); + + // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... } + // check first + lua_pushstring(L, regname); + lua_rawget(L, -3); + if (!lua_isnil(L, -1)) { + FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2), + "Packer registered twice with inconsistent metatable"); + } + lua_pop(L, 1); + // then set + lua_pushstring(L, regname); + lua_rawset(L, -3); + + lua_pop(L, 1); +} + +static bool find_packer(const char *regname, PackerTuple &out) +{ + MutexAutoLock autolock(g_packers_lock); + auto it = g_packers.find(regname); + if (it == g_packers.end()) + return false; + // copy data for thread safety + out.first = it->first; + out.second = it->second; + return true; +} + +static bool find_packer(lua_State *L, int idx, PackerTuple &out) +{ +#ifndef NDEBUG + StackChecker checker(L); +#endif + + // retrieve metatable of the object + if (lua_getmetatable(L, idx) != 1) + return false; + + // use our global table to map it to the registry name + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP); + assert(lua_istable(L, -1)); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return false; + } + + // load the associated data + bool found = find_packer(lua_tostring(L, -1), out); + FATAL_ERROR_IF(!found, "Inconsistent internal state"); + lua_pop(L, 3); + return true; +} + +// +// Packing implementation +// + +static VectorRef<PackedInstr> record_object(lua_State *L, int idx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ + const void *ptr = lua_topointer(L, idx); + assert(ptr); + auto found = seen.find(ptr); + if (found == seen.end()) { + seen[ptr] = pv.i.size(); + return VectorRef<PackedInstr>(); + } + s32 ref = found->second; + assert(ref < (s32)pv.i.size()); + // reuse the value from first time + auto r = emplace(pv, INSTR_PUSHREF); + r->ref = ref; + pv.i[ref].keep_ref = true; + return r; +} + +static VectorRef<PackedInstr> pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv, + std::unordered_map<const void *, s32> &seen) +{ +#ifndef NDEBUG + StackChecker checker(L); + assert(idx > 0); + assert(vidx > 0); +#endif + + switch (lua_type(L, idx)) { + case LUA_TNONE: + case LUA_TNIL: + return emplace(pv, LUA_TNIL); + case LUA_TBOOLEAN: { + auto r = emplace(pv, LUA_TBOOLEAN); + r->bdata = lua_toboolean(L, idx); + return r; + } + case LUA_TNUMBER: { + auto r = emplace(pv, LUA_TNUMBER); + r->ndata = lua_tonumber(L, idx); + return r; + } + case LUA_TSTRING: { + auto r = emplace(pv, LUA_TSTRING); + size_t len; + const char *str = lua_tolstring(L, idx, &len); + assert(str); + r->sdata.assign(str, len); + return r; + } + case LUA_TTABLE: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + break; // execution continues + } + case LUA_TFUNCTION: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + r = emplace(pv, LUA_TFUNCTION); + call_string_dump(L, idx); + size_t len; + const char *str = lua_tolstring(L, -1, &len); + assert(str); + r->sdata.assign(str, len); + lua_pop(L, 1); + return r; + } + case LUA_TUSERDATA: { + auto r = record_object(L, idx, pv, seen); + if (r) + return r; + PackerTuple ser; + if (!find_packer(L, idx, ser)) + throw LuaError("Cannot serialize unsupported userdata"); + pv.contains_userdata = true; + r = emplace(pv, LUA_TUSERDATA); + r->sdata = ser.first; + r->ptrdata = ser.second.fin(L, idx); + return r; + } + default: { + std::string err = "Cannot serialize type "; + err += lua_typename(L, lua_type(L, idx)); + throw LuaError(err); + } + } + + // LUA_TTABLE + lua_checkstack(L, 5); + + auto rtable = emplace(pv, LUA_TTABLE); + const int vi_table = vidx++; + + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + // key at -2, value at -1 + const int ktype = lua_type(L, -2), vtype = lua_type(L, -1); + if (ktype == LUA_TNUMBER) + rtable->uidata1++; // narr + else + rtable->uidata2++; // nrec + + // check if we can use a shortcut + if (can_set_into(ktype, vtype) && suitable_key(L, -2)) { + // push only the value + auto rval = pack_inner(L, absidx(L, -1), vidx, pv, seen); + rval->pop = rval->type != LUA_TTABLE; + // and where to put it: + rval->set_into = vi_table; + if (ktype == LUA_TSTRING) + rval->sdata = lua_tostring(L, -2); + else + rval->sidata1 = lua_tointeger(L, -2); + // pop tables after the fact + if (!rval->pop) { + auto ri1 = emplace(pv, INSTR_POP); + ri1->sidata1 = vidx; + } + } else { + // push the key and value + pack_inner(L, absidx(L, -2), vidx, pv, seen); + vidx++; + pack_inner(L, absidx(L, -1), vidx, pv, seen); + vidx++; + // push an instruction to set them + auto ri1 = emplace(pv, INSTR_SETTABLE); + ri1->set_into = vi_table; + ri1->sidata1 = vidx - 2; + ri1->sidata2 = vidx - 1; + ri1->pop = true; + vidx -= 2; + } + + lua_pop(L, 1); + } + + assert(vidx == vi_table + 1); + return rtable; +} + +PackedValue *script_pack(lua_State *L, int idx) +{ + if (idx < 0) + idx = absidx(L, idx); + + PackedValue pv; + std::unordered_map<const void *, s32> seen; + pack_inner(L, idx, 1, pv, seen); + + return new PackedValue(std::move(pv)); +} + +// +// Unpacking implementation +// + +void script_unpack(lua_State *L, PackedValue *pv) +{ + lua_newtable(L); // table at index top to track ref indices -> objects + const int top = lua_gettop(L); + int ctr = 0; + + for (size_t packed_idx = 0; packed_idx < pv->i.size(); packed_idx++) { + auto &i = pv->i[packed_idx]; + + // If leaving values on stack make sure there's space (every 5th iteration) + if (!i.pop && (ctr++) >= 5) { + lua_checkstack(L, 5); + ctr = 0; + } + + switch (i.type) { + /* Instructions */ + case INSTR_SETTABLE: + lua_pushvalue(L, top + i.sidata1); // key + lua_pushvalue(L, top + i.sidata2); // value + lua_rawset(L, top + i.set_into); + if (i.pop) { + if (i.sidata1 != i.sidata2) { + // removing moves indices so pop higher index first + lua_remove(L, top + std::max(i.sidata1, i.sidata2)); + lua_remove(L, top + std::min(i.sidata1, i.sidata2)); + } else { + lua_remove(L, top + i.sidata1); + } + } + continue; + case INSTR_POP: + lua_remove(L, top + i.sidata1); + if (i.sidata2 > 0) + lua_remove(L, top + i.sidata2); + continue; + case INSTR_PUSHREF: + lua_pushinteger(L, i.ref); + lua_rawget(L, top); + break; + + /* Lua types */ + case LUA_TNIL: + lua_pushnil(L); + break; + case LUA_TBOOLEAN: + lua_pushboolean(L, i.bdata); + break; + case LUA_TNUMBER: + lua_pushnumber(L, i.ndata); + break; + case LUA_TSTRING: + lua_pushlstring(L, i.sdata.data(), i.sdata.size()); + break; + case LUA_TTABLE: + lua_createtable(L, i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr); + break; + case LUA_TUSERDATA: { + PackerTuple ser; + sanity_check(find_packer(i.sdata.c_str(), ser)); + ser.second.fout(L, i.ptrdata); + i.ptrdata = nullptr; // ownership taken by callback + break; + } + + default: + assert(0); + break; + } + + if (i.keep_ref) { + lua_pushinteger(L, packed_idx); + lua_pushvalue(L, -2); + lua_rawset(L, top); + } + + if (i.set_into) { + if (!i.pop) + lua_pushvalue(L, -1); + if (uses_sdata(i.type)) + lua_rawseti(L, top + i.set_into, i.sidata1); + else + lua_setfield(L, top + i.set_into, i.sdata.c_str()); + } else { + if (i.pop) + lua_pop(L, 1); + } + } + + // as part of the unpacking process we take ownership of all userdata + pv->contains_userdata = false; + // leave exactly one value on the stack + lua_settop(L, top+1); + lua_remove(L, top); +} + +// +// PackedValue +// + +PackedValue::~PackedValue() +{ + if (!contains_userdata) + return; + for (auto &i : this->i) { + if (i.type == LUA_TUSERDATA && i.ptrdata) { + PackerTuple ser; + if (find_packer(i.sdata.c_str(), ser)) { + // tell it to deallocate object + ser.second.fout(nullptr, i.ptrdata); + } else { + assert(false); + } + } + } +} + +// +// script_dump_packed +// + +#ifndef NDEBUG +void script_dump_packed(const PackedValue *val) +{ + printf("instruction stream: [\n"); + for (const auto &i : val->i) { + printf("\t("); + switch (i.type) { + case INSTR_SETTABLE: + printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2); + break; + case INSTR_POP: + printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2); + break; + case INSTR_PUSHREF: + printf("PUSHREF(%d)", i.ref); + break; + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(i.bdata ? "true" : "false"); + break; + case LUA_TNUMBER: + printf("%f", i.ndata); + break; + case LUA_TSTRING: + printf("\"%s\"", i.sdata.c_str()); + break; + case LUA_TTABLE: + printf("table(%d, %d)", i.uidata1, i.uidata2); + break; + case LUA_TFUNCTION: + printf("function(%lu byte)", i.sdata.size()); + break; + case LUA_TUSERDATA: + printf("userdata %s %p", i.sdata.c_str(), i.ptrdata); + break; + default: + printf("!!UNKNOWN!!"); + break; + } + if (i.set_into) { + if (i.type >= 0 && uses_sdata(i.type)) + printf(", k=%d, into=%d", i.sidata1, i.set_into); + else if (i.type >= 0) + printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into); + else + printf(", into=%d", i.set_into); + } + if (i.keep_ref) + printf(", keep_ref"); + if (i.pop) + printf(", pop"); + printf(")\n"); + } + printf("]\n"); +} +#endif diff --git a/src/script/common/c_packer.h b/src/script/common/c_packer.h new file mode 100644 index 000000000..fe072c10a --- /dev/null +++ b/src/script/common/c_packer.h @@ -0,0 +1,126 @@ +/* +Minetest +Copyright (C) 2022 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. +*/ + +#pragma once + +#include <string> +#include <vector> +#include "irrlichttypes.h" +#include "util/basic_macros.h" + +extern "C" { +#include <lua.h> +} + +/* + This file defines an in-memory representation of Lua objects including + support for functions and userdata. It it used to move data between Lua + states and cannot be used for persistence or network transfer. +*/ + +#define INSTR_SETTABLE (-10) +#define INSTR_POP (-11) +#define INSTR_PUSHREF (-12) + +/** + * Represents a single instruction that pushes a new value or works with existing ones. + */ +struct PackedInstr +{ + s16 type; // LUA_T* or INSTR_* + u16 set_into; // set into table on stack + bool keep_ref; // is referenced later by INSTR_PUSHREF? + bool pop; // remove from stack? + union { + bool bdata; // boolean: value + lua_Number ndata; // number: value + struct { + u16 uidata1, uidata2; // table: narr, nrec + }; + struct { + /* + SETTABLE: key index, value index + POP: indices to remove + otherwise w/ set_into: numeric key, - + */ + s32 sidata1, sidata2; + }; + void *ptrdata; // userdata: implementation defined + s32 ref; // PUSHREF: index of referenced instr + }; + /* + - string: value + - function: buffer + - w/ set_into: string key (no null bytes!) + - userdata: name in registry + */ + std::string sdata; + + PackedInstr() : type(0), set_into(0), keep_ref(false), pop(false) {} +}; + +/** + * A packed value can be a primitive like a string or number but also a table + * including all of its contents. It is made up of a linear stream of + * 'instructions' that build the final value when executed. + */ +struct PackedValue +{ + std::vector<PackedInstr> i; + // Indicates whether there are any userdata pointers that need to be deallocated + bool contains_userdata = false; + + PackedValue() = default; + ~PackedValue(); + + DISABLE_CLASS_COPY(PackedValue) + + ALLOW_CLASS_MOVE(PackedValue) +}; + +/* + * Packing callback: Turns a Lua value at given index into a void* + */ +typedef void *(*PackInFunc)(lua_State *L, int idx); +/* + * Unpacking callback: Turns a void* back into the Lua value (left on top of stack) + * + * Note that this function must take ownership of the pointer, so make sure + * to free or keep the memory. + * `L` can be nullptr to indicate that data should just be discarded. + */ +typedef void (*PackOutFunc)(lua_State *L, void *ptr); +/* + * Register a packable type with the name of its metatable. + * + * Even though the callbacks are global this must be called for every Lua state + * that supports objects of this type. + * This function is thread-safe. + */ +void script_register_packer(lua_State *L, const char *regname, + PackInFunc fin, PackOutFunc fout); + +// Pack a Lua value +PackedValue *script_pack(lua_State *L, int idx); +// Unpack a Lua value (left on top of stack) +// Note that this may modify the PackedValue, reusability is not guaranteed! +void script_unpack(lua_State *L, PackedValue *val); + +// Dump contents of PackedValue to stdout for debugging +void script_dump_packed(const PackedValue *val); diff --git a/src/script/common/helper.cpp b/src/script/common/helper.cpp index fbf24e1b7..72de2b14a 100644 --- a/src/script/common/helper.cpp +++ b/src/script/common/helper.cpp @@ -17,35 +17,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +extern "C" { +#include <lauxlib.h> +} + #include "helper.h" #include <cmath> -#include <sstream> #include <irr_v2d.h> #include <irr_v3d.h> +#include "c_converter.h" #include "c_types.h" -#include "c_internal.h" - -// imported from c_converter.cpp with pure C++ style -static inline void check_lua_type(lua_State *L, int index, const char *name, int type) -{ - int t = lua_type(L, index); - if (t != type) { - std::string traceback = script_get_backtrace(L); - throw LuaError(std::string("Invalid ") + (name) + " (expected " + - lua_typename(L, (type)) + " got " + lua_typename(L, t) + - ").\n" + traceback); - } -} - -// imported from c_converter.cpp -#define CHECK_POS_COORD(name) \ - check_lua_type(L, -1, "position coordinate '" name "'", LUA_TNUMBER) -#define CHECK_POS_TAB(index) check_lua_type(L, index, "position", LUA_TTABLE) - -bool LuaHelper::isNaN(lua_State *L, int idx) -{ - return lua_type(L, idx) == LUA_TNUMBER && std::isnan(lua_tonumber(L, idx)); -} /* * Read template functions @@ -59,74 +40,41 @@ bool LuaHelper::readParam(lua_State *L, int index) template <> s16 LuaHelper::readParam(lua_State *L, int index) { - return lua_tonumber(L, index); + return luaL_checkinteger(L, index); } template <> int LuaHelper::readParam(lua_State *L, int index) { - return luaL_checkint(L, index); + return luaL_checkinteger(L, index); } template <> float LuaHelper::readParam(lua_State *L, int index) { - if (isNaN(L, index)) - throw LuaError("NaN value is not allowed."); + lua_Number v = luaL_checknumber(L, index); + if (std::isnan(v) && std::isinf(v)) + throw LuaError("Invalid float value (NaN or infinity)"); - return (float)luaL_checknumber(L, index); + return static_cast<float>(v); } template <> v2s16 LuaHelper::readParam(lua_State *L, int index) { - v2s16 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<s16>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<s16>(L, -1); - lua_pop(L, 1); - return p; + return read_v2s16(L, index); } template <> v2f LuaHelper::readParam(lua_State *L, int index) { - v2f p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<float>(L, -1); - lua_pop(L, 1); - return p; + return check_v2f(L, index); } template <> v3f LuaHelper::readParam(lua_State *L, int index) { - v3f p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - CHECK_POS_COORD("x"); - p.X = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - CHECK_POS_COORD("y"); - p.Y = readParam<float>(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "z"); - CHECK_POS_COORD("z"); - p.Z = readParam<float>(L, -1); - lua_pop(L, 1); - return p; + return check_v3f(L, index); } template <> diff --git a/src/script/common/helper.h b/src/script/common/helper.h index 6491e73cf..fc462b6ef 100644 --- a/src/script/common/helper.h +++ b/src/script/common/helper.h @@ -21,14 +21,11 @@ with this program; if not, write to the Free Software Foundation, Inc., extern "C" { #include <lua.h> -#include <lauxlib.h> } class LuaHelper { protected: - static bool isNaN(lua_State *L, int idx); - /** * Read a value using a template type T from Lua State L and index * diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index dacdcd75a..42a794ceb 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdlib> extern "C" { -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> } #include "server.h" @@ -32,6 +32,7 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "common/c_packer.h" #include "lua_api/l_base.h" /******************************************************************************/ @@ -76,19 +77,34 @@ void AsyncEngine::initialize(unsigned int numEngines) { initDone = true; - for (unsigned int i = 0; i < numEngines; i++) { - AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, - std::string("AsyncWorker-") + itos(i)); - workerThreads.push_back(toAdd); - toAdd->start(); + if (numEngines == 0) { + // Leave one core for the main thread and one for whatever else + autoscaleMaxWorkers = Thread::getNumberOfProcessors(); + if (autoscaleMaxWorkers >= 2) + autoscaleMaxWorkers -= 2; + infostream << "AsyncEngine: using at most " << autoscaleMaxWorkers + << " threads with automatic scaling" << std::endl; + + addWorkerThread(); + } else { + for (unsigned int i = 0; i < numEngines; i++) + addWorkerThread(); } } +void AsyncEngine::addWorkerThread() +{ + AsyncWorkerThread *toAdd = new AsyncWorkerThread(this, + std::string("AsyncWorker-") + itos(workerThreads.size())); + workerThreads.push_back(toAdd); + toAdd->start(); +} + /******************************************************************************/ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, const std::string &mod_origin) { - jobQueueMutex.lock(); + MutexAutoLock autolock(jobQueueMutex); u32 jobId = jobIdCounter++; jobQueue.emplace_back(); @@ -99,7 +115,23 @@ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); + return jobId; +} + +u32 AsyncEngine::queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin) +{ + MutexAutoLock autolock(jobQueueMutex); + u32 jobId = jobIdCounter++; + + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params_ext.reset(params); + to_add.mod_origin = mod_origin; + + jobQueueCounter.post(); return jobId; } @@ -132,6 +164,12 @@ void AsyncEngine::putJobResult(LuaJobInfo &&result) /******************************************************************************/ void AsyncEngine::step(lua_State *L) { + stepJobResults(L); + stepAutoscale(); +} + +void AsyncEngine::stepJobResults(lua_State *L) +{ int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); @@ -148,7 +186,10 @@ void AsyncEngine::step(lua_State *L) luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushinteger(L, j.id); - lua_pushlstring(L, j.result.data(), j.result.size()); + if (j.result_ext) + script_unpack(L, j.result_ext.get()); + else + lua_pushlstring(L, j.result.data(), j.result.size()); // Call handler const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); @@ -161,12 +202,71 @@ void AsyncEngine::step(lua_State *L) lua_pop(L, 2); // Pop core and error handler } +void AsyncEngine::stepAutoscale() +{ + if (workerThreads.size() >= autoscaleMaxWorkers) + return; + + MutexAutoLock autolock(jobQueueMutex); + + // 2) If the timer elapsed, check again + if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) { + autoscaleTimer = 0; + // Determine overlap with previous snapshot + unsigned int n = 0; + for (const auto &it : jobQueue) + n += autoscaleSeenJobs.count(it.id); + autoscaleSeenJobs.clear(); + infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl; + // Start this many new threads + while (workerThreads.size() < autoscaleMaxWorkers && n > 0) { + addWorkerThread(); + n--; + } + return; + } + + // 1) Check if there's anything in the queue + if (!autoscaleTimer && !jobQueue.empty()) { + // Take a snapshot of all jobs we have seen + for (const auto &it : jobQueue) + autoscaleSeenJobs.emplace(it.id); + // and set a timer for 1 second + autoscaleTimer = porting::getTimeMs() + 1000; + } +} + /******************************************************************************/ -void AsyncEngine::prepareEnvironment(lua_State* L, int top) +bool AsyncEngine::prepareEnvironment(lua_State* L, int top) { for (StateInitializer &stateInitializer : stateInitializers) { stateInitializer(L, top); } + + auto *script = ModApiBase::getScriptApiBase(L); + try { + script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + } catch (const ModError &e) { + errorstream << "Execution of async base environment failed: " + << e.what() << std::endl; + FATAL_ERROR("Execution of async base environment failed"); + } + + // Load per mod stuff + if (server) { + const auto &list = server->m_async_init_files; + try { + for (auto &it : list) + script->loadMod(it.second, it.first); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside async environment." << std::endl; + server->setAsyncFatalError(e.what()); + return false; + } + } + + return true; } /******************************************************************************/ @@ -178,15 +278,25 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, { lua_State *L = getStack(); + if (jobDispatcher->server) { + setGameDef(jobDispatcher->server); + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + } + // Prepare job lua environment lua_getglobal(L, "core"); int top = lua_gettop(L); // Push builtin initialization type - lua_pushstring(L, "async"); + lua_pushstring(L, jobDispatcher->server ? "async_game" : "async"); lua_setglobal(L, "INIT"); - jobDispatcher->prepareEnvironment(L, top); + if (!jobDispatcher->prepareEnvironment(L, top)) { + // can't throw from here so we're stuck with this + isErrored = true; + } } /******************************************************************************/ @@ -198,19 +308,20 @@ AsyncWorkerThread::~AsyncWorkerThread() /******************************************************************************/ void* AsyncWorkerThread::run() { - lua_State *L = getStack(); + if (isErrored) + return nullptr; - try { - loadMod(getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua", - BUILTIN_MOD_NAME); - } catch (const ModError &e) { - errorstream << "Execution of async base environment failed: " - << e.what() << std::endl; - FATAL_ERROR("Execution of async base environment failed"); - } + lua_State *L = getStack(); int error_handler = PUSH_ERROR_HANDLER(L); + auto report_error = [this] (const ModError &e) { + if (jobDispatcher->server) + jobDispatcher->server->setAsyncFatalError(e.what()); + else + errorstream << e.what() << std::endl; + }; + lua_getglobal(L, "core"); if (lua_isnil(L, -1)) { FATAL_ERROR("Unable to find core within async environment!"); @@ -223,6 +334,8 @@ void* AsyncWorkerThread::run() if (!jobDispatcher->getJob(&j) || stopRequested()) continue; + const bool use_ext = !!j.params_ext; + lua_getfield(L, -1, "job_processor"); if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); @@ -232,7 +345,10 @@ void* AsyncWorkerThread::run() errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; lua_pushnil(L); } - lua_pushlstring(L, j.params.data(), j.params.size()); + if (use_ext) + script_unpack(L, j.params_ext.get()); + else + lua_pushlstring(L, j.params.data(), j.params.size()); // Call it setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); @@ -241,19 +357,28 @@ void* AsyncWorkerThread::run() try { scriptError(result, "<async>"); } catch (const ModError &e) { - errorstream << e.what() << std::endl; + report_error(e); } } else { // Fetch result - size_t length; - const char *retval = lua_tolstring(L, -1, &length); - j.result.assign(retval, length); + if (use_ext) { + try { + j.result_ext.reset(script_pack(L, -1)); + } catch (const ModError &e) { + report_error(e); + result = LUA_ERRERR; + } + } else { + size_t length; + const char *retval = lua_tolstring(L, -1, &length); + j.result.assign(retval, length); + } } lua_pop(L, 1); // Pop retval // Put job result - if (!j.result.empty()) + if (result == 0) jobDispatcher->putJobResult(std::move(j)); } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 697cb0221..1e34e40ea 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <deque> +#include <unordered_set> +#include <memory> +#include <lua.h> #include "threading/semaphore.h" #include "threading/thread.h" -#include "lua.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" +#include "cpp_api/s_security.h" // Forward declarations class AsyncEngine; @@ -42,8 +46,12 @@ struct LuaJobInfo std::string function; // Parameter to be passed to function (serialized) std::string params; + // Alternative parameters + std::unique_ptr<PackedValue> params_ext; // Result of function call (serialized) std::string result; + // Alternative result + std::unique_ptr<PackedValue> result_ext; // Name of the mod who invoked this call std::string mod_origin; // JobID used to identify a job and match it to callback @@ -51,7 +59,8 @@ struct LuaJobInfo }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { +class AsyncWorkerThread : public Thread, + virtual public ScriptApiBase, public ScriptApiSecurity { friend class AsyncEngine; public: virtual ~AsyncWorkerThread(); @@ -63,6 +72,7 @@ protected: private: AsyncEngine *jobDispatcher = nullptr; + bool isErrored = false; }; // Asynchornous thread and job management @@ -71,6 +81,7 @@ class AsyncEngine { typedef void (*StateInitializer)(lua_State *L, int top); public: AsyncEngine() = default; + AsyncEngine(Server *server) : server(server) {}; ~AsyncEngine(); /** @@ -81,7 +92,7 @@ public: /** * Create async engine tasks and lock function registration - * @param numEngines Number of async threads to be started + * @param numEngines Number of worker threads, 0 for automatic scaling */ void initialize(unsigned int numEngines); @@ -95,8 +106,16 @@ public: const std::string &mod_origin = ""); /** + * Queue an async job + * @param func Serialized lua function + * @param params Serialized parameters (takes ownership!) + * @return ID of queued job + */ + u32 queueAsyncJob(std::string &&func, PackedValue *params, + const std::string &mod_origin = ""); + + /** * Engine step to process finished jobs - * the engine step is one way to pass events back, PushFinishedJobs another * @param L The Lua stack */ void step(lua_State *L); @@ -117,18 +136,43 @@ protected: void putJobResult(LuaJobInfo &&result); /** + * Start an additional worker thread + */ + void addWorkerThread(); + + /** + * Process finished jobs callbacks + */ + void stepJobResults(lua_State *L); + + /** + * Handle automatic scaling of worker threads + */ + void stepAutoscale(); + + /** * Initialize environment with current registred functions * this function adds all functions registred by registerFunction to the * passed lua stack * @param L Lua stack to initialize * @param top Stack position + * @return false if a mod error ocurred */ - void prepareEnvironment(lua_State* L, int top); + bool prepareEnvironment(lua_State* L, int top); private: // Variable locking the engine against further modification bool initDone = false; + // Maximum number of worker threads for automatic scaling + // 0 if disabled + unsigned int autoscaleMaxWorkers = 0; + u64 autoscaleTimer = 0; + std::unordered_set<u32> autoscaleSeenJobs; + + // Only set for the server async environment (duh) + Server *server = nullptr; + // Internal store for registred state initializers std::vector<StateInitializer> stateInitializers; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index f7b8a5102..595c9e540 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -121,6 +121,14 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_newtable(m_luastack); lua_setglobal(m_luastack, "core"); + // vector.metatable is stored in the registry for quick access from C++. + lua_newtable(m_luastack); + lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_newtable(m_luastack); + lua_rawgeti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE); + lua_setfield(m_luastack, -2, "metatable"); + lua_setglobal(m_luastack, "vector"); + if (m_type == ScriptingType::Client) lua_pushstring(m_luastack, "/"); else diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index c889fffa0..b02a0c7be 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -281,15 +281,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_inventory_open"); - std::vector<const InventoryList*> lists = inventory->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inventory, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inventory); try { runCallbacks(1, RUN_CALLBACKS_MODE_OR); diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index 06337b9e8..852a27ddc 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -103,7 +103,7 @@ void ScriptApiEntity::luaentity_Activate(u16 id, lua_pop(L, 2); // Pop object and error handler } -void ScriptApiEntity::luaentity_Deactivate(u16 id) +void ScriptApiEntity::luaentity_Deactivate(u16 id, bool removal) { SCRIPTAPI_PRECHECKHEADER @@ -120,9 +120,9 @@ void ScriptApiEntity::luaentity_Deactivate(u16 id) if (!lua_isnil(L, -1)) { luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); - + lua_pushboolean(L, removal); setOriginFromTable(object); - PCALL_RES(lua_pcall(L, 1, 0, error_handler)); + PCALL_RES(lua_pcall(L, 2, 0, error_handler)); } else { lua_pop(L, 1); } @@ -244,7 +244,7 @@ bool ScriptApiEntity::luaentity_Punch(u16 id, { SCRIPTAPI_PRECHECKHEADER - //infostream<<"scriptapi_luaentity_step: id="<<id<<std::endl; + assert(puncher); int error_handler = PUSH_ERROR_HANDLER(L); @@ -294,7 +294,10 @@ bool ScriptApiEntity::luaentity_run_simple_callback(u16 id, } luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self - objectrefGetOrCreate(L, sao); // killer reference + if (sao) + objectrefGetOrCreate(L, sao); // sao reference + else + lua_pushnil(L); setOriginFromTable(object); PCALL_RES(lua_pcall(L, 2, 1, error_handler)); diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h index 7658ae922..13f3e9aa3 100644 --- a/src/script/cpp_api/s_entity.h +++ b/src/script/cpp_api/s_entity.h @@ -33,7 +33,7 @@ public: bool luaentity_Add(u16 id, const char *name); void luaentity_Activate(u16 id, const std::string &staticdata, u32 dtime_s); - void luaentity_Deactivate(u16 id); + void luaentity_Deactivate(u16 id, bool removal); void luaentity_Remove(u16 id); std::string luaentity_GetStaticdata(u16 id); void luaentity_GetProperties(u16 id, diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 874c37b6e..af68f689f 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -140,10 +140,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) bool simple_catch_up = true; getboolfield(L, current_abm, "catch_up", simple_catch_up); - + s16 min_y = INT16_MIN; getintfield(L, current_abm, "min_y", min_y); - + s16 max_y = INT16_MAX; getintfield(L, current_abm, "max_y", max_y); diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index a6c5114b2..316b19926 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -98,6 +98,7 @@ void ScriptApiSecurity::initializeSecurity() "type", "unpack", "_VERSION", + "vector", "xpcall", }; static const char *whitelist_tables[] = { @@ -127,8 +128,6 @@ void ScriptApiSecurity::initializeSecurity() "gethook", "traceback", "getinfo", - "getmetatable", - "setmetatable", "upvalueid", "sethook", "debug", @@ -254,6 +253,10 @@ void ScriptApiSecurity::initializeSecurity() lua_pushnil(L); lua_setfield(L, old_globals, "core"); + // 'vector' as well. + lua_pushnil(L); + lua_setfield(L, old_globals, "vector"); + lua_pop(L, 1); // Pop globals_backup @@ -296,6 +299,7 @@ void ScriptApiSecurity::initializeSecurityClient() "type", "unpack", "_VERSION", + "vector", "xpcall", // Completely safe libraries "coroutine", @@ -413,6 +417,12 @@ void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread) bool ScriptApiSecurity::isSecure(lua_State *L) { +#ifndef SERVER + auto script = ModApiBase::getScriptApiBase(L); + // CSM keeps no globals backup but is always secure + if (script->getType() == ScriptingType::Client) + return true; +#endif lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); bool secure = !lua_isnil(L, -1); lua_pop(L, 1); diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 45724e604..ec2656c4a 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -27,26 +27,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include <fstream> -static inline void get_data_and_border_flags(lua_State *L, u8 start_i, - bool *borders, bool *data) +static inline void get_data_and_corner_flags(lua_State *L, u8 start_i, + bool *corners, bool *data) { if (!lua_isboolean(L, start_i)) return; - *borders = lua_toboolean(L, start_i); + *corners = lua_toboolean(L, start_i); if (!lua_isboolean(L, start_i + 1)) return; *data = lua_toboolean(L, start_i + 1); } static void push_area(lua_State *L, const Area *a, - bool include_borders, bool include_data) + bool include_corners, bool include_data) { - if (!include_borders && !include_data) { + if (!include_corners && !include_data) { lua_pushboolean(L, true); return; } lua_newtable(L); - if (include_borders) { + if (include_corners) { push_v3s16(L, a->minedge); lua_setfield(L, -2, "min"); push_v3s16(L, a->maxedge); @@ -59,13 +59,13 @@ static void push_area(lua_State *L, const Area *a, } static inline void push_areas(lua_State *L, const std::vector<Area *> &areas, - bool borders, bool data) + bool corners, bool data) { lua_newtable(L); size_t cnt = areas.size(); for (size_t i = 0; i < cnt; i++) { lua_pushnumber(L, areas[i]->id); - push_area(L, areas[i], borders, data); + push_area(L, areas[i], corners, data); lua_settable(L, -3); } } @@ -94,7 +94,7 @@ int LuaAreaStore::gc_object(lua_State *L) return 0; } -// get_area(id, include_borders, include_data) +// get_area(id, include_corners, include_data) int LuaAreaStore::l_get_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -104,9 +104,9 @@ int LuaAreaStore::l_get_area(lua_State *L) u32 id = luaL_checknumber(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); const Area *res; @@ -114,12 +114,12 @@ int LuaAreaStore::l_get_area(lua_State *L) if (!res) return 0; - push_area(L, res, include_borders, include_data); + push_area(L, res, include_corners, include_data); return 1; } -// get_areas_for_pos(pos, include_borders, include_data) +// get_areas_for_pos(pos, include_corners, include_data) int LuaAreaStore::l_get_areas_for_pos(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -129,19 +129,19 @@ int LuaAreaStore::l_get_areas_for_pos(lua_State *L) v3s16 pos = check_v3s16(L, 2); - bool include_borders = true; + bool include_corners = true; bool include_data = false; - get_data_and_border_flags(L, 3, &include_borders, &include_data); + get_data_and_corner_flags(L, 3, &include_corners, &include_data); std::vector<Area *> res; ast->getAreasForPos(&res, pos); - push_areas(L, res, include_borders, include_data); + push_areas(L, res, include_corners, include_data); return 1; } -// get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data) +// get_areas_in_area(corner1, corner2, accept_overlap, include_corners, include_data) int LuaAreaStore::l_get_areas_in_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -149,25 +149,26 @@ int LuaAreaStore::l_get_areas_in_area(lua_State *L) LuaAreaStore *o = checkobject(L, 1); AreaStore *ast = o->as; - v3s16 minedge = check_v3s16(L, 2); - v3s16 maxedge = check_v3s16(L, 3); + v3s16 minp = check_v3s16(L, 2); + v3s16 maxp = check_v3s16(L, 3); + sortBoxVerticies(minp, maxp); - bool include_borders = true; + bool include_corners = true; bool include_data = false; bool accept_overlap = false; if (lua_isboolean(L, 4)) { accept_overlap = readParam<bool>(L, 4); - get_data_and_border_flags(L, 5, &include_borders, &include_data); + get_data_and_corner_flags(L, 5, &include_corners, &include_data); } std::vector<Area *> res; - ast->getAreasInArea(&res, minedge, maxedge, accept_overlap); - push_areas(L, res, include_borders, include_data); + ast->getAreasInArea(&res, minp, maxp, accept_overlap); + push_areas(L, res, include_corners, include_data); return 1; } -// insert_area(edge1, edge2, data, id) +// insert_area(corner1, corner2, data, id) int LuaAreaStore::l_insert_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index aaced7cd0..05ac53cbb 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -268,30 +268,32 @@ int ModApiClient::l_sound_play(lua_State *L) SimpleSoundSpec spec; read_soundspec(L, 1, spec); + SoundLocation type = SoundLocation::Local; float gain = 1.0f; - float pitch = 1.0f; - bool looped = false; - s32 handle; + v3f position; if (lua_istable(L, 2)) { getfloatfield(L, 2, "gain", gain); - getfloatfield(L, 2, "pitch", pitch); - getboolfield(L, 2, "loop", looped); + getfloatfield(L, 2, "pitch", spec.pitch); + getboolfield(L, 2, "loop", spec.loop); lua_getfield(L, 2, "pos"); if (!lua_isnil(L, -1)) { - v3f pos = read_v3f(L, -1) * BS; + position = read_v3f(L, -1) * BS; + type = SoundLocation::Position; lua_pop(L, 1); - handle = sound->playSoundAt( - spec.name, looped, gain * spec.gain, pos, pitch); - lua_pushinteger(L, handle); - return 1; } } - handle = sound->playSound(spec.name, looped, gain * spec.gain, spec.fade, pitch); - lua_pushinteger(L, handle); + spec.gain *= gain; + s32 handle; + if (type == SoundLocation::Local) + handle = sound->playSound(spec); + else + handle = sound->playSoundAt(spec, position); + + lua_pushinteger(L, handle); return 1; } diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 18622ee00..137b210be 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -371,8 +371,9 @@ int ModApiCraft::l_clear_craft(lua_State *L) int ModApiCraft::l_get_craft_result(lua_State *L) { NO_MAP_LOCK_REQUIRED; + IGameDef *gdef = getGameDef(L); - int input_i = 1; + const int input_i = 1; std::string method_s = getstringfield_default(L, input_i, "method", "normal"); enum CraftMethod method = (CraftMethod)getenumfield(L, input_i, "method", es_CraftMethod, CRAFT_METHOD_NORMAL); @@ -382,10 +383,9 @@ int ModApiCraft::l_get_craft_result(lua_State *L) width = luaL_checkinteger(L, -1); lua_pop(L, 1); lua_getfield(L, input_i, "items"); - std::vector<ItemStack> items = read_items(L, -1,getServer(L)); + std::vector<ItemStack> items = read_items(L, -1, gdef); lua_pop(L, 1); // items - IGameDef *gdef = getServer(L); ICraftDefManager *cdef = gdef->cdef(); CraftInput input(method, width, items); CraftOutput output; @@ -465,13 +465,13 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, const std::vector<CraftDefinition*> &recipes, const CraftOutput &output) { - lua_createtable(L, recipes.size(), 0); - if (recipes.empty()) { lua_pushnil(L); return; } + lua_createtable(L, recipes.size(), 0); + std::vector<CraftDefinition*>::const_iterator it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); @@ -487,10 +487,9 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server, 1); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef, 1); lua_createtable(L, 1, 0); @@ -500,7 +499,7 @@ int ModApiCraft::l_get_craft_recipe(lua_State *L) setintfield(L, -1, "width", 0); return 1; } - push_craft_recipe(L, server, recipes[0], output); + push_craft_recipe(L, gdef, recipes[0], output); return 1; } @@ -510,12 +509,11 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string item = luaL_checkstring(L, 1); - Server *server = getServer(L); + IGameDef *gdef = getGameDef(L); CraftOutput output(item, 0); - std::vector<CraftDefinition*> recipes = server->cdef() - ->getCraftRecipes(output, server); + auto recipes = gdef->cdef()->getCraftRecipes(output, gdef); - push_craft_recipes(L, server, recipes, output); + push_craft_recipes(L, gdef, recipes, output); return 1; } @@ -527,3 +525,11 @@ void ModApiCraft::Initialize(lua_State *L, int top) API_FCT(register_craft); API_FCT(clear_craft); } + +void ModApiCraft::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + API_FCT(get_all_craft_recipes); + API_FCT(get_craft_recipe); + API_FCT(get_craft_result); +} diff --git a/src/script/lua_api/l_craft.h b/src/script/lua_api/l_craft.h index 9002b23ef..5234af56f 100644 --- a/src/script/lua_api/l_craft.h +++ b/src/script/lua_api/l_craft.h @@ -45,4 +45,5 @@ private: public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 18ee3a521..b26c89e7d 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -640,7 +640,7 @@ int ModApiEnvMod::l_add_entity(lua_State *L) v3f pos = checkFloatPos(L, 1); const char *name = luaL_checkstring(L, 2); - const char *staticdata = luaL_optstring(L, 3, ""); + std::string staticdata = readParam<std::string>(L, 3, ""); ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, staticdata); int objectid = env->addActiveObject(obj); @@ -757,7 +757,7 @@ int ModApiEnvMod::l_get_objects_in_area(lua_State *L) { GET_ENV_PTR; ScriptApiBase *script = getScriptApiBase(L); - + v3f minp = read_v3f(L, 1) * BS; v3f maxp = read_v3f(L, 2) * BS; aabb3f box(minp, maxp); @@ -1219,7 +1219,8 @@ int ModApiEnvMod::l_emerge_area(lua_State *L) sortBoxVerticies(bpmin, bpmax); size_t num_blocks = VoxelArea(bpmin, bpmax).getVolume(); - assert(num_blocks != 0); + if (num_blocks == 0) + return 0; if (lua_isfunction(L, 3)) { callback = LuaEmergeAreaCallback; @@ -1386,7 +1387,7 @@ int ModApiEnvMod::l_transforming_liquid_add(lua_State *L) GET_ENV_PTR; v3s16 p0 = read_v3s16(L, 1); - env->getMap().transforming_liquid_add(p0); + env->getServerMap().transforming_liquid_add(p0); return 1; } @@ -1409,7 +1410,7 @@ int ModApiEnvMod::l_compare_block_status(lua_State *L) v3s16 nodepos = check_v3s16(L, 1); std::string condition_s = luaL_checkstring(L, 2); auto status = env->getBlockStatus(getNodeBlockPos(nodepos)); - + int condition_i = -1; if (!string_to_enum(es_BlockStatusType, condition_i, condition_s)) return 0; // Unsupported diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 67c76faae..a7d406d2a 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -114,7 +114,7 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); - + // get_objects_in_area(pos, minp, maxp) static int l_get_objects_in_area(lua_State *L); diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 672e535ca..de73ff42a 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -69,7 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // Retrieve Environment pointer as `env` (no map lock) #define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \ - Environment *env = (Environment *)getEnv(L); \ + Environment *env = getEnv(L); \ if (env == NULL) \ return 0 diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index b0a4ee194..175047e58 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -214,11 +214,16 @@ int InvRef::l_get_list(lua_State *L) InvRef *ref = checkobject(L, 1); const char *listname = luaL_checkstring(L, 2); Inventory *inv = getinv(L, ref); - if(inv){ - push_inventory_list(L, inv, listname); - } else { + if (!inv) { lua_pushnil(L); + return 1; } + InventoryList *invlist = inv->getList(listname); + if (!invlist) { + lua_pushnil(L); + return 1; + } + push_inventory_list(L, *invlist); return 1; } @@ -242,7 +247,7 @@ int InvRef::l_set_list(lua_State *L) return 0; } -// get_lists(self) -> list of InventoryLists +// get_lists(self) -> table that maps listnames to InventoryLists int InvRef::l_get_lists(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -251,15 +256,7 @@ int InvRef::l_get_lists(lua_State *L) if (!inv) { return 0; } - std::vector<const InventoryList*> lists = inv->getLists(); - std::vector<const InventoryList*>::iterator iter = lists.begin(); - lua_createtable(L, 0, lists.size()); - for (; iter != lists.end(); iter++) { - const char* name = (*iter)->getName().c_str(); - lua_pushstring(L, name); - push_inventory_list(L, inv, name); - lua_rawset(L, -3); - } + push_inventory_lists(L, *inv); return 1; } diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index 794d8a6e5..13d046d00 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "itemdef.h" #include "nodedef.h" #include "server.h" @@ -342,7 +343,7 @@ int LuaItemStack::l_get_tool_capabilities(lua_State *L) } // add_wear(self, amount) -> true/false -// The range for "amount" is [0,65535]. Wear is only added if the item +// The range for "amount" is [0,65536]. Wear is only added if the item // is a tool. Adding wear might destroy the item. // Returns true if the item is (or was) a tool. int LuaItemStack::l_add_wear(lua_State *L) @@ -356,6 +357,25 @@ int LuaItemStack::l_add_wear(lua_State *L) return 1; } +// add_wear_by_uses(self, max_uses) -> true/false +// The range for "max_uses" is [0,65536]. +// Adds wear to the item in such a way that, if +// only this function is called to add wear, the item +// will be destroyed exactly after `max_uses` times of calling it. +// No-op if `max_uses` is 0 or item is not a tool. +// Returns true if the item is (or was) a tool. +int LuaItemStack::l_add_wear_by_uses(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + u32 max_uses = readParam<int>(L, 2); + u32 add_wear = calculateResultWear(max_uses, item.wear); + bool result = item.addWear(add_wear, getGameDef(L)->idef()); + lua_pushboolean(L, result); + return 1; +} + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack int LuaItemStack::l_add_item(lua_State *L) @@ -441,6 +461,7 @@ int LuaItemStack::create_object(lua_State *L) lua_setmetatable(L, -2); return 1; } + // Not callable from Lua int LuaItemStack::create(lua_State *L, const ItemStack &item) { @@ -457,6 +478,20 @@ LuaItemStack *LuaItemStack::checkobject(lua_State *L, int narg) return *(LuaItemStack **)luaL_checkudata(L, narg, className); } +void *LuaItemStack::packIn(lua_State *L, int idx) +{ + LuaItemStack *o = checkobject(L, idx); + return new ItemStack(o->getItem()); +} + +void LuaItemStack::packOut(lua_State *L, void *ptr) +{ + ItemStack *stack = reinterpret_cast<ItemStack*>(ptr); + if (L) + create(L, *stack); + delete stack; +} + void LuaItemStack::Register(lua_State *L) { lua_newtable(L); @@ -488,6 +523,8 @@ void LuaItemStack::Register(lua_State *L) // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaItemStack::className[] = "ItemStack"; @@ -514,6 +551,7 @@ const luaL_Reg LuaItemStack::methods[] = { luamethod(LuaItemStack, get_definition), luamethod(LuaItemStack, get_tool_capabilities), luamethod(LuaItemStack, add_wear), + luamethod(LuaItemStack, add_wear_by_uses), luamethod(LuaItemStack, add_item), luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, take_item), @@ -576,6 +614,9 @@ int ModApiItemMod::l_register_item_raw(lua_State *L) // be done if (f.name == "ignore") return 0; + // This would break everything + if (f.name.empty()) + throw LuaError("Cannot register node with empty name"); content_t id = ndef->set(f.name, f); @@ -632,8 +673,8 @@ int ModApiItemMod::l_get_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); - const IItemDefManager *idef = getGameDef(L)->getItemDefManager(); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const IItemDefManager *idef = getGameDef(L)->idef(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); // If this is called at mod load time, NodeDefManager isn't aware of // aliases yet, so we need to handle them manually @@ -658,7 +699,7 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L) NO_MAP_LOCK_REQUIRED; content_t c = luaL_checkint(L, 1); - const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); const char *name = ndef->get(c).name.c_str(); lua_pushstring(L, name); @@ -673,3 +714,10 @@ void ModApiItemMod::Initialize(lua_State *L, int top) API_FCT(get_content_id); API_FCT(get_name_from_content_id); } + +void ModApiItemMod::InitializeAsync(lua_State *L, int top) +{ + // all read-only functions + 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 16878c101..a392555d2 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -108,11 +108,20 @@ private: static int l_get_tool_capabilities(lua_State *L); // add_wear(self, amount) -> true/false - // The range for "amount" is [0,65535]. Wear is only added if the item + // The range for "amount" is [0,65536]. Wear is only added if the item // is a tool. Adding wear might destroy the item. // Returns true if the item is (or was) a tool. static int l_add_wear(lua_State *L); + // add_wear_by_uses(self, max_uses) -> true/false + // The range for "max_uses" is [0,65536]. + // Adds wear to the item in such a way that, if + // only this function is called to add wear, the item + // will be destroyed exactly after `max_uses` times of calling it. + // No-op if `max_uses` is 0 or item is not a tool. + // Returns true if the item is (or was) a tool. + static int l_add_wear_by_uses(lua_State *L); + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack static int l_add_item(lua_State *L); @@ -141,8 +150,11 @@ public: // Not callable from Lua static int create(lua_State *L, const ItemStack &item); static LuaItemStack* checkobject(lua_State *L, int narg); - static void Register(lua_State *L); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + + static void Register(lua_State *L); }; class ModApiItemMod : public ModApiBase { @@ -152,6 +164,8 @@ private: 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); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 736ad022f..cf4a057e1 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #include "client/renderingengine.h" #include "network/networkprotocol.h" - +#include "content/mod_configuration.h" /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, std::string name) @@ -139,6 +139,14 @@ int ModApiMainMenu::l_start(lua_State *L) data->password = getTextData(L,"password"); data->address = getTextData(L,"address"); data->port = getTextData(L,"port"); + + const auto val = getTextData(L, "allow_login_or_register"); + if (val == "login") + data->allow_login_or_register = ELoginRegister::Login; + else if (val == "register") + data->allow_login_or_register = ELoginRegister::Register; + else + data->allow_login_or_register = ELoginRegister::Any; } data->serverdescription = getTextData(L,"serverdescription"); data->servername = getTextData(L,"servername"); @@ -304,7 +312,11 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_settable(L, top_lvl2); lua_pushstring(L, "name"); - lua_pushstring(L, game.name.c_str()); + lua_pushstring(L, game.title.c_str()); + lua_settable(L, top_lvl2); + + lua_pushstring(L, "title"); + lua_pushstring(L, game.title.c_str()); lua_settable(L, top_lvl2); lua_pushstring(L, "author"); @@ -323,9 +335,9 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_newtable(L); int table2 = lua_gettop(L); int internal_index = 1; - for (const std::string &addon_mods_path : game.addon_mods_paths) { + for (const auto &addon_mods_path : game.addon_mods_paths) { lua_pushnumber(L, internal_index); - lua_pushstring(L, addon_mods_path.c_str()); + lua_pushstring(L, addon_mods_path.second.c_str()); lua_settable(L, table2); internal_index++; } @@ -356,6 +368,11 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) lua_pushstring(L, spec.author.c_str()); lua_setfield(L, -2, "author"); + if (!spec.title.empty()) { + lua_pushstring(L, spec.title.c_str()); + lua_setfield(L, -2, "title"); + } + lua_pushinteger(L, spec.release); lua_setfield(L, -2, "release"); @@ -393,6 +410,100 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_check_mod_configuration(lua_State *L) +{ + std::string worldpath = luaL_checkstring(L, 1); + + ModConfiguration modmgr; + + // Add all game mods + SubgameSpec gamespec = findWorldSubgame(worldpath); + modmgr.addGameMods(gamespec); + modmgr.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); + + // Add user-configured mods + std::vector<ModSpec> modSpecs; + + luaL_checktype(L, 2, LUA_TTABLE); + + lua_pushnil(L); + while (lua_next(L, 2)) { + // Ignore non-string keys + if (lua_type(L, -2) != LUA_TSTRING) { + throw LuaError( + "Unexpected non-string key in table passed to " + "core.check_mod_configuration"); + } + + std::string modpath = luaL_checkstring(L, -1); + lua_pop(L, 1); + std::string virtual_path = lua_tostring(L, -1); + + modSpecs.emplace_back(); + ModSpec &spec = modSpecs.back(); + spec.name = fs::GetFilenameFromPath(modpath.c_str()); + spec.path = modpath; + spec.virtual_path = virtual_path; + if (!parseModContents(spec)) { + throw LuaError("Not a mod!"); + } + } + + modmgr.addMods(modSpecs); + try { + modmgr.checkConflictsAndDeps(); + } catch (const ModError &err) { + errorstream << err.what() << std::endl; + + lua_newtable(L); + + lua_pushboolean(L, false); + lua_setfield(L, -2, "is_consistent"); + + lua_newtable(L); + lua_setfield(L, -2, "unsatisfied_mods"); + + lua_newtable(L); + lua_setfield(L, -2, "satisfied_mods"); + + lua_pushstring(L, err.what()); + lua_setfield(L, -2, "error_message"); + return 1; + } + + + lua_newtable(L); + + lua_pushboolean(L, modmgr.isConsistent()); + lua_setfield(L, -2, "is_consistent"); + + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + for (const auto &spec : modmgr.getUnsatisfiedMods()) { + lua_pushnumber(L, index); + push_mod_spec(L, spec, true); + lua_settable(L, top); + index++; + } + + lua_setfield(L, -2, "unsatisfied_mods"); + + lua_newtable(L); + top = lua_gettop(L); + index = 1; + for (const auto &spec : modmgr.getMods()) { + lua_pushnumber(L, index); + push_mod_spec(L, spec, false); + lua_settable(L, top); + index++; + } + lua_setfield(L, -2, "satisfied_mods"); + + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_show_keys_menu(lua_State *L) { GUIEngine* engine = getGuiEngine(L); @@ -533,14 +644,14 @@ int ModApiMainMenu::l_get_modpath(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_modpaths(lua_State *L) { - int index = 1; lua_newtable(L); + ModApiMainMenu::l_get_modpath(L); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, "mods"); + for (const std::string &component : getEnvModPaths()) { - index++; lua_pushstring(L, component.c_str()); - lua_rawseti(L, -2, index); + lua_setfield(L, -2, fs::AbsolutePath(component).c_str()); } return 1; } @@ -860,6 +971,19 @@ int ModApiMainMenu::l_open_dir(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_share_file(lua_State *L) +{ +#ifdef __ANDROID__ + std::string path = luaL_checkstring(L, 1); + porting::shareFileAndroid(path); + lua_pushboolean(L, true); +#else + lua_pushboolean(L, false); +#endif + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_do_async_callback(lua_State *L) { MainMenuScripting *script = getScriptApi<MainMenuScripting>(L); @@ -891,6 +1015,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_worlds); API_FCT(get_games); API_FCT(get_content_info); + API_FCT(check_mod_configuration); API_FCT(start); API_FCT(close); API_FCT(show_keys_menu); @@ -924,6 +1049,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_max_supp_proto); API_FCT(open_url); API_FCT(open_dir); + API_FCT(share_file); API_FCT(do_async_callback); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 781185425..9dc40c7f4 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -82,6 +82,8 @@ private: static int l_get_content_info(lua_State *L); + static int l_check_mod_configuration(lua_State *L); + //gui static int l_show_keys_menu(lua_State *L); @@ -152,6 +154,8 @@ private: static int l_open_dir(lua_State *L); + static int l_share_file(lua_State *L); + // async static int l_do_async_callback(lua_State *L); diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 34760157d..bdc4844c0 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -40,7 +40,7 @@ NodeMetaRef* NodeMetaRef::checkobject(lua_State *L, int narg) Metadata* NodeMetaRef::getmeta(bool auto_create) { if (m_is_local) - return m_meta; + return m_local_meta; NodeMetadata *meta = m_env->getMap().getNodeMetadata(m_p); if (meta == NULL && auto_create) { @@ -62,9 +62,14 @@ void NodeMetaRef::clearMeta() void NodeMetaRef::reportMetadataChange(const std::string *name) { SANITY_CHECK(!m_is_local); - // NOTE: This same code is in rollback_interface.cpp // Inform other things that the metadata has changed - NodeMetadata *meta = dynamic_cast<NodeMetadata*>(m_meta); + NodeMetadata *meta = dynamic_cast<NodeMetadata*>(getmeta(false)); + + // If the metadata is now empty, get rid of it + if (meta && meta->empty()) { + clearMeta(); + meta = nullptr; + } MapEditEvent event; event.type = MEET_BLOCK_NODE_METADATA_CHANGED; @@ -127,18 +132,14 @@ void NodeMetaRef::handleToTable(lua_State *L, Metadata *_meta) // fields MetaDataRef::handleToTable(L, _meta); - NodeMetadata *meta = (NodeMetadata*) _meta; + NodeMetadata *meta = (NodeMetadata *) _meta; // inventory - lua_newtable(L); Inventory *inv = meta->getInventory(); if (inv) { - std::vector<const InventoryList *> lists = inv->getLists(); - for(std::vector<const InventoryList *>::const_iterator - i = lists.begin(); i != lists.end(); ++i) { - push_inventory_list(L, inv, (*i)->getName().c_str()); - lua_setfield(L, -2, (*i)->getName().c_str()); - } + push_inventory_lists(L, *inv); + } else { + lua_newtable(L); } lua_setfield(L, -2, "inventory"); } @@ -178,8 +179,8 @@ NodeMetaRef::NodeMetaRef(v3s16 p, ServerEnvironment *env): } NodeMetaRef::NodeMetaRef(Metadata *meta): - m_meta(meta), - m_is_local(true) + m_is_local(true), + m_local_meta(meta) { } diff --git a/src/script/lua_api/l_nodemeta.h b/src/script/lua_api/l_nodemeta.h index fdc1766ed..265ece3d0 100644 --- a/src/script/lua_api/l_nodemeta.h +++ b/src/script/lua_api/l_nodemeta.h @@ -33,10 +33,12 @@ class NodeMetadata; class NodeMetaRef : public MetaDataRef { private: + bool m_is_local = false; + // Set for server metadata v3s16 m_p; ServerEnvironment *m_env = nullptr; - Metadata *m_meta = nullptr; - bool m_is_local = false; + // Set for client metadata + Metadata *m_local_meta = nullptr; static const char className[]; static const luaL_Reg methodsServer[]; diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index f43ba837a..5561eaebf 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "log.h" #include "porting.h" #include "util/numeric.h" @@ -30,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., LuaPerlinNoise */ -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : +LuaPerlinNoise::LuaPerlinNoise(const NoiseParams *params) : np(*params) { } @@ -101,6 +102,25 @@ LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) } +void *LuaPerlinNoise::packIn(lua_State *L, int idx) +{ + LuaPerlinNoise *o = checkobject(L, idx); + return new NoiseParams(o->np); +} + +void LuaPerlinNoise::packOut(lua_State *L, void *ptr) +{ + NoiseParams *np = reinterpret_cast<NoiseParams*>(ptr); + if (L) { + LuaPerlinNoise *o = new LuaPerlinNoise(np); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete np; +} + + void LuaPerlinNoise::Register(lua_State *L) { lua_newtable(L); @@ -126,6 +146,8 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } @@ -141,12 +163,10 @@ luaL_Reg LuaPerlinNoise::methods[] = { LuaPerlinNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, s32 seed, v3s16 size) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) { - m_is3d = size.Z > 1; - np = *params; try { - noise = new Noise(&np, seed, size.X, size.Y, size.Z); + noise = new Noise(np, seed, size.X, size.Y, size.Z); } catch (InvalidNoiseParamsException &e) { throw LuaError(e.what()); } @@ -217,7 +237,7 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -248,7 +268,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) v3f p = check_v3f(L, 2); bool use_buffer = lua_istable(L, 3); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -289,7 +309,7 @@ int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) LuaPerlinNoiseMap *o = checkobject(L, 1); v3f p = check_v3f(L, 2); - if (!o->m_is3d) + if (!o->is3D()) return 0; Noise *n = o->noise; @@ -359,6 +379,35 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) } +struct NoiseMapParams { + NoiseParams np; + s32 seed; + v3s16 size; +}; + +void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) +{ + LuaPerlinNoiseMap *o = checkobject(L, idx); + NoiseMapParams *ret = new NoiseMapParams(); + ret->np = o->noise->np; + ret->seed = o->noise->seed; + ret->size = v3s16(o->noise->sx, o->noise->sy, o->noise->sz); + return ret; +} + +void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) +{ + NoiseMapParams *p = reinterpret_cast<NoiseMapParams*>(ptr); + if (L) { + LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&p->np, p->seed, p->size); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + } + delete p; +} + + void LuaPerlinNoiseMap::Register(lua_State *L) { lua_newtable(L); @@ -384,6 +433,8 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 9f50dfd3f..5d34a479b 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -30,6 +30,7 @@ class LuaPerlinNoise : public ModApiBase { private: NoiseParams np; + static const char className[]; static luaL_Reg methods[]; @@ -42,7 +43,7 @@ private: static int l_get_3d(lua_State *L); public: - LuaPerlinNoise(NoiseParams *params); + LuaPerlinNoise(const NoiseParams *params); ~LuaPerlinNoise() = default; // LuaPerlinNoise(seed, octaves, persistence, scale) @@ -51,6 +52,9 @@ public: static LuaPerlinNoise *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; @@ -59,9 +63,8 @@ public: */ class LuaPerlinNoiseMap : public ModApiBase { - NoiseParams np; Noise *noise; - bool m_is3d; + static const char className[]; static luaL_Reg methods[]; @@ -80,16 +83,20 @@ class LuaPerlinNoiseMap : public ModApiBase static int l_get_map_slice(lua_State *L); public: - LuaPerlinNoiseMap(NoiseParams *np, s32 seed, v3s16 size); - + LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); ~LuaPerlinNoiseMap(); + inline bool is3D() const { return noise->sz > 1; } + // LuaPerlinNoiseMap(np, size) // Creates an LuaPerlinNoiseMap and leaves it on top of stack static int create_object(lua_State *L); static LuaPerlinNoiseMap *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 407b48db0..6bd07a4c1 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -420,8 +420,7 @@ int ObjectRef::l_set_local_animation(lua_State *L) float frame_speed = readParam<float>(L, 6, 30.0f); getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed); - lua_pushboolean(L, true); - return 1; + return 0; } // get_local_animation(self) @@ -464,8 +463,7 @@ int ObjectRef::l_set_eye_offset(lua_State *L) offset_third.Y = rangelim(offset_third.Y,-10,15); //1.5*BS getServer(L)->setPlayerEyeOffset(player, offset_first, offset_third); - lua_pushboolean(L, true); - return 1; + return 0; } // get_eye_offset(self) @@ -737,8 +735,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) prop->validate(); sao->notifyObjectPropertiesModified(); - lua_pushboolean(L, true); - return 1; + return 0; } // get_nametag_attributes(self) @@ -1116,7 +1113,7 @@ int ObjectRef::l_set_look_vertical(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // set_look_horizontal(self, radians) @@ -1131,7 +1128,7 @@ int ObjectRef::l_set_look_horizontal(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // DEPRECATED @@ -1151,7 +1148,7 @@ int ObjectRef::l_set_look_pitch(lua_State *L) float pitch = readParam<float>(L, 2) * core::RADTODEG; playersao->setLookPitchAndSend(pitch); - return 1; + return 0; } // DEPRECATED @@ -1171,7 +1168,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L) float yaw = readParam<float>(L, 2) * core::RADTODEG; playersao->setPlayerYawAndSend(yaw); - return 1; + return 0; } // set_fov(self, degrees, is_multiplier, transition_time) @@ -1310,8 +1307,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) player->inventory_formspec = formspec; getServer(L)->reportInventoryFormspecModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_inventory_formspec(self) -> formspec @@ -1342,8 +1338,7 @@ int ObjectRef::l_set_formspec_prepend(lua_State *L) player->formspec_prepend = formspec; getServer(L)->reportFormspecPrependModified(player->getName()); - lua_pushboolean(L, true); - return 1; + return 0; } // get_formspec_prepend(self) @@ -1603,8 +1598,7 @@ int ObjectRef::l_hud_set_flags(lua_State *L) if (!getServer(L)->hudSetFlags(player, flags, mask)) return 0; - lua_pushboolean(L, true); - return 1; + return 0; } // hud_get_flags(self) @@ -1617,20 +1611,11 @@ int ObjectRef::l_hud_get_flags(lua_State *L) return 0; lua_newtable(L); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE); - lua_setfield(L, -2, "hotbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_HEALTHBAR_VISIBLE); - lua_setfield(L, -2, "healthbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE); - lua_setfield(L, -2, "crosshair"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE); - lua_setfield(L, -2, "wielditem"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE); - lua_setfield(L, -2, "breathbar"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); - lua_setfield(L, -2, "minimap"); - lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE); - lua_setfield(L, -2, "minimap_radar"); + const EnumString *esp = es_HudBuiltinElement; + for (int i = 0; esp[i].str; i++) { + lua_pushboolean(L, (player->hud_flags & esp[i].num) != 0); + lua_setfield(L, -2, esp[i].str); + } return 1; } @@ -1870,11 +1855,37 @@ int ObjectRef::l_set_sky(lua_State *L) } getServer(L)->setSky(player, sky_params); - lua_pushboolean(L, true); - return 1; + return 0; } -// get_sky(self) +static void push_sky_color(lua_State *L, const SkyboxParams ¶ms) +{ + lua_newtable(L); + if (params.type == "regular") { + push_ARGB8(L, params.sky_color.day_sky); + lua_setfield(L, -2, "day_sky"); + push_ARGB8(L, params.sky_color.day_horizon); + lua_setfield(L, -2, "day_horizon"); + push_ARGB8(L, params.sky_color.dawn_sky); + lua_setfield(L, -2, "dawn_sky"); + push_ARGB8(L, params.sky_color.dawn_horizon); + lua_setfield(L, -2, "dawn_horizon"); + push_ARGB8(L, params.sky_color.night_sky); + lua_setfield(L, -2, "night_sky"); + push_ARGB8(L, params.sky_color.night_horizon); + lua_setfield(L, -2, "night_horizon"); + push_ARGB8(L, params.sky_color.indoors); + lua_setfield(L, -2, "indoors"); + } + push_ARGB8(L, params.fog_sun_tint); + lua_setfield(L, -2, "fog_sun_tint"); + push_ARGB8(L, params.fog_moon_tint); + lua_setfield(L, -2, "fog_moon_tint"); + lua_pushstring(L, params.fog_tint_type.c_str()); + lua_setfield(L, -2, "fog_tint_type"); +} + +// get_sky(self, as_table) int ObjectRef::l_get_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1883,10 +1894,30 @@ int ObjectRef::l_get_sky(lua_State *L) if (player == nullptr) return 0; - SkyboxParams skybox_params = player->getSkyParams(); + const SkyboxParams &skybox_params = player->getSkyParams(); + // handle the deprecated version + if (!readParam<bool>(L, 2, false)) { + log_deprecated(L, "Deprecated call to get_sky, please check lua_api.txt"); + + push_ARGB8(L, skybox_params.bgcolor); + lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + + lua_newtable(L); + s16 i = 1; + for (const std::string &texture : skybox_params.textures) { + lua_pushlstring(L, texture.c_str(), texture.size()); + lua_rawseti(L, -2, i++); + } + lua_pushboolean(L, skybox_params.clouds); + return 4; + } + + lua_newtable(L); push_ARGB8(L, skybox_params.bgcolor); + lua_setfield(L, -2, "base_color"); lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); + lua_setfield(L, -2, "type"); lua_newtable(L); s16 i = 1; @@ -1894,44 +1925,30 @@ int ObjectRef::l_get_sky(lua_State *L) lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } + lua_setfield(L, -2, "textures"); lua_pushboolean(L, skybox_params.clouds); - return 4; + lua_setfield(L, -2, "clouds"); + + push_sky_color(L, skybox_params); + lua_setfield(L, -2, "sky_color"); + return 1; } +// DEPRECATED // get_sky_color(self) int ObjectRef::l_get_sky_color(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + log_deprecated(L, "Deprecated call to get_sky_color, use get_sky instead"); + ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); if (player == nullptr) return 0; const SkyboxParams &skybox_params = player->getSkyParams(); - - lua_newtable(L); - if (skybox_params.type == "regular") { - push_ARGB8(L, skybox_params.sky_color.day_sky); - lua_setfield(L, -2, "day_sky"); - push_ARGB8(L, skybox_params.sky_color.day_horizon); - lua_setfield(L, -2, "day_horizon"); - push_ARGB8(L, skybox_params.sky_color.dawn_sky); - lua_setfield(L, -2, "dawn_sky"); - push_ARGB8(L, skybox_params.sky_color.dawn_horizon); - lua_setfield(L, -2, "dawn_horizon"); - push_ARGB8(L, skybox_params.sky_color.night_sky); - lua_setfield(L, -2, "night_sky"); - push_ARGB8(L, skybox_params.sky_color.night_horizon); - lua_setfield(L, -2, "night_horizon"); - push_ARGB8(L, skybox_params.sky_color.indoors); - lua_setfield(L, -2, "indoors"); - } - push_ARGB8(L, skybox_params.fog_sun_tint); - lua_setfield(L, -2, "fog_sun_tint"); - push_ARGB8(L, skybox_params.fog_moon_tint); - lua_setfield(L, -2, "fog_moon_tint"); - lua_pushstring(L, skybox_params.fog_tint_type.c_str()); - lua_setfield(L, -2, "fog_tint_type"); + push_sky_color(L, skybox_params); return 1; } @@ -1960,8 +1977,7 @@ int ObjectRef::l_set_sun(lua_State *L) } getServer(L)->setSun(player, sun_params); - lua_pushboolean(L, true); - return 1; + return 0; } //get_sun(self) @@ -2014,8 +2030,7 @@ int ObjectRef::l_set_moon(lua_State *L) } getServer(L)->setMoon(player, moon_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_moon(self) @@ -2069,9 +2084,11 @@ int ObjectRef::l_set_stars(lua_State *L) "scale", star_params.scale); } + star_params.day_opacity = getfloatfield_default(L, 2, + "day_opacity", star_params.day_opacity); + getServer(L)->setStars(player, star_params); - lua_pushboolean(L, true); - return 1; + return 0; } // get_stars(self) @@ -2094,6 +2111,8 @@ int ObjectRef::l_get_stars(lua_State *L) lua_setfield(L, -2, "star_color"); lua_pushnumber(L, star_params.scale); lua_setfield(L, -2, "scale"); + lua_pushnumber(L, star_params.day_opacity); + lua_setfield(L, -2, "day_opacity"); return 1; } @@ -2138,8 +2157,7 @@ int ObjectRef::l_set_clouds(lua_State *L) } getServer(L)->setClouds(player, cloud_params); - lua_pushboolean(L, true); - return 1; + return 0; } int ObjectRef::l_get_clouds(lua_State *L) @@ -2193,8 +2211,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) } getServer(L)->overrideDayNightRatio(player, do_override, ratio); - lua_pushboolean(L, true); - return 1; + return 0; } // get_day_night_ratio(self) @@ -2271,6 +2288,61 @@ int ObjectRef::l_set_minimap_modes(lua_State *L) return 0; } +// set_lighting(self, lighting) +int ObjectRef::l_set_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + Lighting lighting = player->getLighting(); + lua_getfield(L, 2, "shadows"); + if (lua_istable(L, -1)) { + lighting.shadow_intensity = getfloatfield_default(L, -1, "intensity", lighting.shadow_intensity); + } + lua_pop(L, -1); + + getServer(L)->setLighting(player, lighting); + return 0; +} + +// get_lighting(self) +int ObjectRef::l_get_lighting(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + const Lighting &lighting = player->getLighting(); + + lua_newtable(L); // result + lua_newtable(L); // "shadows" + lua_pushnumber(L, lighting.shadow_intensity); + lua_setfield(L, -2, "intensity"); + lua_setfield(L, -2, "shadows"); + return 1; +} + +// respawn(self) +int ObjectRef::l_respawn(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + getServer(L)->RespawnPlayer(player->getPeerId()); + lua_pushboolean(L, true); + return 1; +} + + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) {} @@ -2424,5 +2496,9 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, get_eye_offset), luamethod(ObjectRef, send_mapblock), luamethod(ObjectRef, set_minimap_modes), + luamethod(ObjectRef, set_lighting), + luamethod(ObjectRef, get_lighting), + luamethod(ObjectRef, respawn), + {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index db3a3a7cf..b36bab492 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -316,9 +316,10 @@ private: // set_sky(self, sky_parameters) static int l_set_sky(lua_State *L); - // get_sky(self) + // get_sky(self, as_table) static int l_get_sky(lua_State *L); + // DEPRECATED // get_sky_color(self) static int l_get_sky_color(lua_State* L); @@ -375,4 +376,13 @@ private: // set_minimap_modes(self, modes, wanted_mode) static int l_set_minimap_modes(lua_State *L); + + // set_lighting(self, lighting) + static int l_set_lighting(lua_State *L); + + // get_lighting(self) + static int l_get_lighting(lua_State *L); + + // respawn(self) + static int l_respawn(lua_State *L); }; diff --git a/src/script/lua_api/l_particleparams.h b/src/script/lua_api/l_particleparams.h new file mode 100644 index 000000000..03f11c07f --- /dev/null +++ b/src/script/lua_api/l_particleparams.h @@ -0,0 +1,282 @@ +/* +Minetest +Copyright (C) 2021 velartrill, Lexi Hale <lexi@hale.su> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include "lua_api/l_particles.h" +#include "lua_api/l_object.h" +#include "lua_api/l_internal.h" +#include "common/c_converter.h" +#include "common/c_content.h" +#include "server.h" +#include "particles.h" + +namespace LuaParticleParams +{ + using namespace ParticleParamTypes; + + template<typename T> + inline void readNumericLuaValue(lua_State* L, T& ret) + { + if (lua_isnil(L,-1)) + return; + + if (std::is_integral<T>()) + ret = lua_tointeger(L, -1); + else + ret = lua_tonumber(L, -1); + } + + template <typename T, size_t N> + inline void readNumericLuaValue(lua_State* L, Parameter<T,N>& ret) + { + readNumericLuaValue<T>(L, ret.val); + } + + // these are unfortunately necessary as C++ intentionally disallows function template + // specialization and there's no way to make template overloads reliably resolve correctly + inline void readLuaValue(lua_State* L, f32Parameter& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, f32& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, u16& ret) { readNumericLuaValue(L, ret); } + inline void readLuaValue(lua_State* L, u8& ret) { readNumericLuaValue(L, ret); } + + inline void readLuaValue(lua_State* L, v3fParameter& ret) + { + if (lua_isnil(L, -1)) + return; + + if (lua_isnumber(L, -1)) { // shortcut for uniform vectors + auto n = lua_tonumber(L, -1); + ret = v3fParameter(n,n,n); + } else { + ret = (v3fParameter)check_v3f(L, -1); + } + } + + inline void readLuaValue(lua_State* L, v2fParameter& ret) + { + if (lua_isnil(L, -1)) + return; + + if (lua_isnumber(L, -1)) { // shortcut for uniform vectors + auto n = lua_tonumber(L, -1); + ret = v2fParameter(n,n); + } else { + ret = (v2fParameter)check_v2f(L, -1); + } + } + + inline void readLuaValue(lua_State* L, TweenStyle& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)TweenStyle::fwd, "fwd"}, + {(int)TweenStyle::rev, "rev"}, + {(int)TweenStyle::pulse, "pulse"}, + {(int)TweenStyle::flicker, "flicker"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)TweenStyle::fwd; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("tween style must be one of ('fwd', 'rev', 'pulse', 'flicker')"); + } + ret = (TweenStyle)v; + } + + inline void readLuaValue(lua_State* L, AttractorKind& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)AttractorKind::none, "none"}, + {(int)AttractorKind::point, "point"}, + {(int)AttractorKind::line, "line"}, + {(int)AttractorKind::plane, "plane"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)AttractorKind::none; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("attractor kind must be one of ('none', 'point', 'line', 'plane')"); + } + ret = (AttractorKind)v; + } + + inline void readLuaValue(lua_State* L, BlendMode& ret) + { + if (lua_isnil(L, -1)) + return; + + static const EnumString opts[] = { + {(int)BlendMode::alpha, "alpha"}, + {(int)BlendMode::add, "add"}, + {(int)BlendMode::sub, "sub"}, + {(int)BlendMode::screen, "screen"}, + {0, nullptr}, + }; + + luaL_checktype(L, -1, LUA_TSTRING); + int v = (int)BlendMode::alpha; + if (!string_to_enum(opts, v, lua_tostring(L, -1))) { + throw LuaError("blend mode must be one of ('alpha', 'add', 'sub', 'screen')"); + } + ret = (BlendMode)v; + } + + template <typename T> void + readLuaValue(lua_State* L, RangedParameter<T>& field) + { + if (lua_isnil(L,-1)) + return; + if (!lua_istable(L,-1)) // is this is just a literal value? + goto set_uniform; + + lua_getfield(L, -1, "min"); + // handle convenience syntax for non-range values + if (lua_isnil(L,-1)) { + lua_pop(L, 1); + goto set_uniform; + } + readLuaValue(L,field.min); + lua_pop(L, 1); + + lua_getfield(L, -1, "max"); + readLuaValue(L,field.max); + lua_pop(L, 1); + + lua_getfield(L, -1, "bias"); + if (!lua_isnil(L,-1)) + readLuaValue(L,field.bias); + lua_pop(L, 1); + return; + + set_uniform: + readLuaValue(L, field.min); + readLuaValue(L, field.max); + } + + template <typename T> void + readLegacyValue(lua_State* L, const char* name, T& field) {} + + template <typename T> void + readLegacyValue(lua_State* L, const char* name, RangedParameter<T>& field) + { + int tbl = lua_gettop(L); + lua_pushliteral(L, "min"); + lua_pushstring(L, name); + lua_concat(L, 2); + lua_gettable(L, tbl); + if (!lua_isnil(L, -1)) { + readLuaValue(L, field.min); + } + lua_settop(L, tbl); + + lua_pushliteral(L, "max"); + lua_pushstring(L, name); + lua_concat(L, 2); + lua_gettable(L, tbl); + if (!lua_isnil(L, -1)) { + readLuaValue(L, field.max); + } + lua_settop(L, tbl); + } + + template <typename T> void + readTweenTable(lua_State* L, const char* name, TweenedParameter<T>& field) + { + int tbl = lua_gettop(L); + + lua_pushstring(L, name); + lua_pushliteral(L, "_tween"); + lua_concat(L, 2); + lua_gettable(L, tbl); + if(lua_istable(L, -1)) { + int tween = lua_gettop(L); + // get the starting value + lua_pushinteger(L, 1), lua_gettable(L, tween); + readLuaValue(L, field.start); + lua_pop(L, 1); + + // get the final value -- use len instead of 2 so that this + // gracefully degrades if keyframe support is later added + lua_pushinteger(L, (lua_Integer)lua_objlen(L, -1)), lua_gettable(L, tween); + readLuaValue(L, field.end); + lua_pop(L, 1); + + // get the effect settings + lua_getfield(L, -1, "style"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.style); + lua_pop(L, 1); + + lua_getfield(L, -1, "reps"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.reps); + lua_pop(L, 1); + + lua_getfield(L, -1, "start"); + if (!lua_isnil(L,-1)) + readLuaValue(L, field.beginning); + lua_pop(L, 1); + + goto done; + } else { + lua_pop(L,1); + } + // the table is not present; check for nonanimated values + + lua_getfield(L, tbl, name); + if(!lua_isnil(L, -1)) { + readLuaValue(L, field.start); + lua_settop(L, tbl); + goto set_uniform; + } else { + lua_pop(L,1); + } + + // the goto did not trigger, so this table is not present either + // check for pre-5.6.0 legacy values + readLegacyValue(L, name, field.start); + + set_uniform: + field.end = field.start; + done: + lua_settop(L, tbl); // clean up after ourselves + } + + inline u16 readAttachmentID(lua_State* L, const char* name) + { + u16 id = 0; + lua_getfield(L, -1, name); + if (!lua_isnil(L, -1)) { + ObjectRef *ref = ObjectRef::checkobject(L, -1); + if (auto obj = ObjectRef::getobject(ref)) + id = obj->getId(); + } + lua_pop(L, 1); + return id; + } + + void readTexValue(lua_State* L, ServerParticleTexture& tex); +} diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index a51c4fe20..586c7dc73 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -20,30 +20,50 @@ 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 "lua_api/l_particleparams.h" #include "common/c_converter.h" #include "common/c_content.h" #include "server.h" #include "particles.h" -// add_particle({pos=, velocity=, acceleration=, expirationtime=, -// size=, collisiondetection=, collision_removal=, object_collision=, -// vertical=, texture=, player=}) -// pos/velocity/acceleration = {x=num, y=num, z=num} -// expirationtime = num (seconds) -// size = num -// collisiondetection = bool -// collision_removal = bool -// object_collision = bool -// vertical = bool -// texture = e.g."default_wood.png" -// animation = TileAnimation definition -// glow = num +void LuaParticleParams::readTexValue(lua_State* L, ServerParticleTexture& tex) +{ + StackUnroller unroll(L); + + tex.animated = false; + if (lua_isstring(L, -1)) { + tex.string = lua_tostring(L, -1); + return; + } + + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "name"); + tex.string = luaL_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, -1, "animation"); + if (! lua_isnil(L, -1)) { + tex.animated = true; + tex.animation = read_animation_definition(L, -1); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "blend"); + LuaParticleParams::readLuaValue(L, tex.blendmode); + lua_pop(L, 1); + + LuaParticleParams::readTweenTable(L, "alpha", tex.alpha); + LuaParticleParams::readTweenTable(L, "scale", tex.scale); + +} + +// add_particle({...}) int ModApiParticles::l_add_particle(lua_State *L) { NO_MAP_LOCK_REQUIRED; // Get parameters - struct ParticleParameters p; + ParticleParameters p; std::string playername; if (lua_gettop(L) > 1) // deprecated @@ -56,7 +76,7 @@ int ModApiParticles::l_add_particle(lua_State *L) p.expirationtime = luaL_checknumber(L, 4); p.size = luaL_checknumber(L, 5); p.collisiondetection = readParam<bool>(L, 6); - p.texture = luaL_checkstring(L, 7); + p.texture.string = luaL_checkstring(L, 7); if (lua_gettop(L) == 8) // only spawn for a single player playername = luaL_checkstring(L, 8); } @@ -108,7 +128,12 @@ int ModApiParticles::l_add_particle(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); - p.texture = getstringfield_default(L, 1, "texture", p.texture); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.glow = getintfield_default(L, 1, "glow", p.glow); lua_getfield(L, 1, "node"); @@ -119,34 +144,26 @@ int ModApiParticles::l_add_particle(lua_State *L) p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile); playername = getstringfield_default(L, 1, "playername", ""); + + lua_getfield(L, 1, "drag"); + if (lua_istable(L, -1)) + p.drag = check_v3f(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "jitter"); + LuaParticleParams::readLuaValue(L, p.jitter); + lua_pop(L, 1); + + lua_getfield(L, 1, "bounce"); + LuaParticleParams::readLuaValue(L, p.bounce); + lua_pop(L, 1); } getServer(L)->spawnParticle(playername, p); return 1; } -// add_particlespawner({amount=, time=, -// minpos=, maxpos=, -// minvel=, maxvel=, -// minacc=, maxacc=, -// minexptime=, maxexptime=, -// minsize=, maxsize=, -// collisiondetection=, -// collision_removal=, -// object_collision=, -// vertical=, -// texture=, -// player=}) -// minpos/maxpos/minvel/maxvel/minacc/maxacc = {x=num, y=num, z=num} -// minexptime/maxexptime = num (seconds) -// minsize/maxsize = num -// collisiondetection = bool -// collision_removal = bool -// object_collision = bool -// vertical = bool -// texture = e.g."default_wood.png" -// animation = TileAnimation definition -// glow = num +// add_particlespawner({...}) int ModApiParticles::l_add_particlespawner(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -156,24 +173,31 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) ServerActiveObject *attached = NULL; std::string playername; + using namespace ParticleParamTypes; if (lua_gettop(L) > 1) //deprecated { log_deprecated(L, "Deprecated add_particlespawner call with " "individual parameters instead of definition"); p.amount = luaL_checknumber(L, 1); p.time = luaL_checknumber(L, 2); - p.minpos = check_v3f(L, 3); - p.maxpos = check_v3f(L, 4); - p.minvel = check_v3f(L, 5); - p.maxvel = check_v3f(L, 6); - p.minacc = check_v3f(L, 7); - p.maxacc = check_v3f(L, 8); - p.minexptime = luaL_checknumber(L, 9); - p.maxexptime = luaL_checknumber(L, 10); - p.minsize = luaL_checknumber(L, 11); - p.maxsize = luaL_checknumber(L, 12); + auto minpos = check_v3f(L, 3); + auto maxpos = check_v3f(L, 4); + auto minvel = check_v3f(L, 5); + auto maxvel = check_v3f(L, 6); + auto minacc = check_v3f(L, 7); + auto maxacc = check_v3f(L, 8); + auto minexptime = luaL_checknumber(L, 9); + auto maxexptime = luaL_checknumber(L, 10); + auto minsize = luaL_checknumber(L, 11); + auto maxsize = luaL_checknumber(L, 12); + p.pos = v3fRange(minpos, maxpos); + p.vel = v3fRange(minvel, maxvel); + p.acc = v3fRange(minacc, maxacc); + p.exptime = f32Range(minexptime, maxexptime); + p.size = f32Range(minsize, maxsize); + p.collisiondetection = readParam<bool>(L, 13); - p.texture = luaL_checkstring(L, 14); + p.texture.string = luaL_checkstring(L, 14); if (lua_gettop(L) == 15) // only spawn for a single player playername = luaL_checkstring(L, 15); } @@ -182,40 +206,46 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) p.amount = getintfield_default(L, 1, "amount", p.amount); p.time = getfloatfield_default(L, 1, "time", p.time); - lua_getfield(L, 1, "minpos"); - if (lua_istable(L, -1)) - p.minpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxpos"); - if (lua_istable(L, -1)) - p.maxpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minvel"); - if (lua_istable(L, -1)) - p.minvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxvel"); - if (lua_istable(L, -1)) - p.maxvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minacc"); - if (lua_istable(L, -1)) - p.minacc = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxacc"); - if (lua_istable(L, -1)) - p.maxacc = check_v3f(L, -1); - lua_pop(L, 1); + // set default values + p.exptime = 1; + p.size = 1; + + // read spawner parameters from the table + LuaParticleParams::readTweenTable(L, "pos", p.pos); + LuaParticleParams::readTweenTable(L, "vel", p.vel); + LuaParticleParams::readTweenTable(L, "acc", p.acc); + LuaParticleParams::readTweenTable(L, "size", p.size); + LuaParticleParams::readTweenTable(L, "exptime", p.exptime); + LuaParticleParams::readTweenTable(L, "drag", p.drag); + LuaParticleParams::readTweenTable(L, "jitter", p.jitter); + LuaParticleParams::readTweenTable(L, "bounce", p.bounce); + lua_getfield(L, 1, "attract"); + if (!lua_isnil(L, -1)) { + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "kind"); + LuaParticleParams::readLuaValue(L, p.attractor_kind); + lua_pop(L,1); + + lua_getfield(L, -1, "die_on_contact"); + if (!lua_isnil(L, -1)) + p.attractor_kill = readParam<bool>(L, -1); + lua_pop(L,1); + + if (p.attractor_kind != AttractorKind::none) { + LuaParticleParams::readTweenTable(L, "strength", p.attract); + LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin); + p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached"); + if (p.attractor_kind != AttractorKind::point) { + LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction); + p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached"); + } + } + } else { + p.attractor_kind = AttractorKind::none; + } + lua_pop(L,1); + LuaParticleParams::readTweenTable(L, "radius", p.radius); - p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime); - p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); - p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); - p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); p.collisiondetection = getboolfield_default(L, 1, "collisiondetection", p.collisiondetection); p.collision_removal = getboolfield_default(L, 1, @@ -234,11 +264,29 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) attached = ObjectRef::getobject(ref); } + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); - p.texture = getstringfield_default(L, 1, "texture", p.texture); playername = getstringfield_default(L, 1, "playername", ""); p.glow = getintfield_default(L, 1, "glow", p.glow); + lua_getfield(L, 1, "texpool"); + if (lua_istable(L, -1)) { + size_t tl = lua_objlen(L, -1); + p.texpool.reserve(tl); + for (size_t i = 0; i < tl; ++i) { + lua_pushinteger(L, i+1), lua_gettable(L, -2); + p.texpool.emplace_back(); + LuaParticleParams::readTexValue(L, p.texpool.back()); + lua_pop(L,1); + } + } + lua_pop(L, 1); + lua_getfield(L, 1, "node"); if (lua_istable(L, -1)) p.node = readnode(L, -1, getGameDef(L)->ndef()); diff --git a/src/script/lua_api/l_particles_local.cpp b/src/script/lua_api/l_particles_local.cpp index cc68b13a5..62cbab8e9 100644 --- a/src/script/lua_api/l_particles_local.cpp +++ b/src/script/lua_api/l_particles_local.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "lua_api/l_internal.h" #include "lua_api/l_object.h" +#include "lua_api/l_particleparams.h" #include "client/particles.h" #include "client/client.h" #include "client/clientevent.h" @@ -49,6 +50,19 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.acc = check_v3f(L, -1); lua_pop(L, 1); + lua_getfield(L, 1, "drag"); + if (lua_istable(L, -1)) + p.drag = check_v3f(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 1, "jitter"); + LuaParticleParams::readLuaValue(L, p.jitter); + lua_pop(L, 1); + + lua_getfield(L, 1, "bounce"); + LuaParticleParams::readLuaValue(L, p.bounce); + lua_pop(L, 1); + p.expirationtime = getfloatfield_default(L, 1, "expirationtime", p.expirationtime); p.size = getfloatfield_default(L, 1, "size", p.size); @@ -64,7 +78,11 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); - p.texture = getstringfield_default(L, 1, "texture", p.texture); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L,p.texture); + } + lua_pop(L, 1); p.glow = getintfield_default(L, 1, "glow", p.glow); lua_getfield(L, 1, "node"); @@ -88,44 +106,50 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) // Get parameters ParticleSpawnerParameters p; - p.amount = getintfield_default(L, 1, "amount", p.amount); p.time = getfloatfield_default(L, 1, "time", p.time); - lua_getfield(L, 1, "minpos"); - if (lua_istable(L, -1)) - p.minpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxpos"); - if (lua_istable(L, -1)) - p.maxpos = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minvel"); - if (lua_istable(L, -1)) - p.minvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "maxvel"); - if (lua_istable(L, -1)) - p.maxvel = check_v3f(L, -1); - lua_pop(L, 1); - - lua_getfield(L, 1, "minacc"); - if (lua_istable(L, -1)) - p.minacc = check_v3f(L, -1); - lua_pop(L, 1); + // set default values + p.exptime = 1; + p.size = 1; + + // read spawner parameters from the table + using namespace ParticleParamTypes; + LuaParticleParams::readTweenTable(L, "pos", p.pos); + LuaParticleParams::readTweenTable(L, "vel", p.vel); + LuaParticleParams::readTweenTable(L, "acc", p.acc); + LuaParticleParams::readTweenTable(L, "size", p.size); + LuaParticleParams::readTweenTable(L, "exptime", p.exptime); + LuaParticleParams::readTweenTable(L, "drag", p.drag); + LuaParticleParams::readTweenTable(L, "jitter", p.jitter); + LuaParticleParams::readTweenTable(L, "bounce", p.bounce); + lua_getfield(L, 1, "attract"); + if (!lua_isnil(L, -1)) { + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "kind"); + LuaParticleParams::readLuaValue(L, p.attractor_kind); + lua_pop(L,1); + + lua_getfield(L, -1, "die_on_contact"); + if (!lua_isnil(L, -1)) + p.attractor_kill = readParam<bool>(L, -1); + lua_pop(L,1); + + if (p.attractor_kind != AttractorKind::none) { + LuaParticleParams::readTweenTable(L, "strength", p.attract); + LuaParticleParams::readTweenTable(L, "origin", p.attractor_origin); + p.attractor_attachment = LuaParticleParams::readAttachmentID(L, "origin_attached"); + if (p.attractor_kind != AttractorKind::point) { + LuaParticleParams::readTweenTable(L, "direction", p.attractor_direction); + p.attractor_direction_attachment = LuaParticleParams::readAttachmentID(L, "direction_attached"); + } + } + } else { + p.attractor_kind = AttractorKind::none; + } + lua_pop(L,1); + LuaParticleParams::readTweenTable(L, "radius", p.radius); - lua_getfield(L, 1, "maxacc"); - if (lua_istable(L, -1)) - p.maxacc = check_v3f(L, -1); - lua_pop(L, 1); - - p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime); - p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime); - p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize); - p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize); p.collisiondetection = getboolfield_default(L, 1, "collisiondetection", p.collisiondetection); p.collision_removal = getboolfield_default(L, 1, @@ -137,10 +161,28 @@ int ModApiParticlesLocal::l_add_particlespawner(lua_State *L) p.animation = read_animation_definition(L, -1); lua_pop(L, 1); + lua_getfield(L, 1, "texture"); + if (!lua_isnil(L, -1)) { + LuaParticleParams::readTexValue(L, p.texture); + } + lua_pop(L, 1); + p.vertical = getboolfield_default(L, 1, "vertical", p.vertical); - p.texture = getstringfield_default(L, 1, "texture", p.texture); p.glow = getintfield_default(L, 1, "glow", p.glow); + lua_getfield(L, 1, "texpool"); + if (lua_istable(L, -1)) { + size_t tl = lua_objlen(L, -1); + p.texpool.reserve(tl); + for (size_t i = 0; i < tl; ++i) { + lua_pushinteger(L, i+1), lua_gettable(L, -2); + p.texpool.emplace_back(); + LuaParticleParams::readTexValue(L, p.texpool.back()); + lua_pop(L,1); + } + } + lua_pop(L, 1); + lua_getfield(L, 1, "node"); if (lua_istable(L, -1)) p.node = readnode(L, -1, getGameDef(L)->ndef()); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 88ab5e16b..a5daae346 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "common/c_packer.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" #include "scripting_server.h" @@ -61,11 +62,8 @@ int ModApiServer::l_get_server_uptime(lua_State *L) int ModApiServer::l_get_server_max_lag(lua_State *L) { NO_MAP_LOCK_REQUIRED; - ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L)); - if (!s_env) - lua_pushnil(L); - else - lua_pushnumber(L, s_env->getMaxLagEstimate()); + GET_ENV_PTR; + lua_pushnumber(L, env->getMaxLagEstimate()); return 1; } @@ -325,12 +323,15 @@ int ModApiServer::l_disconnect_player(lua_State *L) else message.append("Disconnected."); - RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name); - if (player == NULL) { + Server *server = getServer(L); + + RemotePlayer *player = server->getEnv().getPlayer(name); + if (!player) { lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message)); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); lua_pushboolean(L, true); return 1; } @@ -392,12 +393,11 @@ int ModApiServer::l_get_modpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; std::string modname = luaL_checkstring(L, 1); - const ModSpec *mod = getServer(L)->getModSpec(modname); - if (!mod) { + const ModSpec *mod = getGameDef(L)->getModSpec(modname); + if (!mod) lua_pushnil(L); - return 1; - } - lua_pushstring(L, mod->path.c_str()); + else + lua_pushstring(L, mod->path.c_str()); return 1; } @@ -409,13 +409,14 @@ int ModApiServer::l_get_modnames(lua_State *L) // Get a list of mods std::vector<std::string> modlist; - getServer(L)->getModNames(modlist); + for (auto &it : getGameDef(L)->getMods()) + modlist.emplace_back(it.name); std::sort(modlist.begin(), modlist.end()); // Package them up for Lua lua_createtable(L, modlist.size(), 0); - std::vector<std::string>::iterator iter = modlist.begin(); + auto iter = modlist.begin(); for (u16 i = 0; iter != modlist.end(); ++iter) { lua_pushstring(L, iter->c_str()); lua_rawseti(L, -2, ++i); @@ -427,8 +428,8 @@ int ModApiServer::l_get_modnames(lua_State *L) int ModApiServer::l_get_worldpath(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string worldpath = getServer(L)->getWorldPath(); - lua_pushstring(L, worldpath.c_str()); + const Server *srv = getServer(L); + lua_pushstring(L, srv->getWorldPath().c_str()); return 1; } @@ -436,16 +437,15 @@ int ModApiServer::l_get_worldpath(lua_State *L) int ModApiServer::l_sound_play(lua_State *L) { NO_MAP_LOCK_REQUIRED; - SimpleSoundSpec spec; - read_soundspec(L, 1, spec); - ServerSoundParams params; + ServerPlayingSound params; + read_soundspec(L, 1, params.spec); read_server_sound_params(L, 2, params); bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3); if (ephemeral) { - getServer(L)->playSound(spec, params, true); + getServer(L)->playSound(params, true); lua_pushnil(L); } else { - s32 handle = getServer(L)->playSound(spec, params); + s32 handle = getServer(L)->playSound(params); lua_pushinteger(L, handle); } return 1; @@ -510,7 +510,8 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) int ModApiServer::l_is_singleplayer(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_pushboolean(L, getServer(L)->isSingleplayer()); + const Server *srv = getServer(L); + lua_pushboolean(L, srv->isSingleplayer()); return 1; } @@ -525,6 +526,76 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } +// do_async_callback(func, params, mod_origin) +int ModApiServer::l_do_async_callback(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ServerScripting *script = getScriptApi<ServerScripting>(L); + + luaL_checktype(L, 1, LUA_TFUNCTION); + luaL_checktype(L, 2, LUA_TTABLE); + luaL_checktype(L, 3, LUA_TSTRING); + + call_string_dump(L, 1); + size_t func_length; + const char *serialized_func_raw = lua_tolstring(L, -1, &func_length); + + PackedValue *param = script_pack(L, 2); + + std::string mod_origin = readParam<std::string>(L, 3); + + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + param, mod_origin); + + lua_settop(L, 0); + lua_pushinteger(L, jobId); + return 1; +} + +// register_async_dofile(path) +int ModApiServer::l_register_async_dofile(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam<std::string>(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + // Find currently running mod name (only at init time) + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return 0; + std::string modname = readParam<std::string>(L, -1); + + getServer(L)->m_async_init_files.emplace_back(modname, path); + lua_pushboolean(L, true); + return 1; +} + +// serialize_roundtrip(value) +// Meant for unit testing the packer from Lua +int ModApiServer::l_serialize_roundtrip(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + int top = lua_gettop(L); + auto *pv = script_pack(L, 1); + if (top != lua_gettop(L)) + throw LuaError("stack values leaked"); + +#ifndef NDEBUG + script_dump_packed(pv); +#endif + + top = lua_gettop(L); + script_unpack(L, pv); + delete pv; + if (top + 1 != lua_gettop(L)) + throw LuaError("stack values leaked"); + + return 1; +} + void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); @@ -558,4 +629,18 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(remove_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); + + API_FCT(do_async_callback); + API_FCT(register_async_dofile); + API_FCT(serialize_roundtrip); +} + +void ModApiServer::InitializeAsync(lua_State *L, int top) +{ + API_FCT(get_worldpath); + API_FCT(is_singleplayer); + + API_FCT(get_current_modname); + API_FCT(get_modpath); + API_FCT(get_modnames); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index f05c0b7c9..a4f38c34e 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -106,6 +106,16 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); + // do_async_callback(func, params, mod_origin) + static int l_do_async_callback(lua_State *L); + + // register_async_dofile(path) + static int l_register_async_dofile(lua_State *L); + + // serialize_roundtrip(obj) + static int l_serialize_roundtrip(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeAsync(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 14398dda2..3f3fda56e 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -27,9 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" -/* This protects: - * 'secure.*' settings from being set - * some mapgen settings from being set +/* This protects the following from being set: + * 'secure.*' settings + * some security-relevant settings + * (better solution pending) + * some mapgen settings * (not security-criticial, just to avoid messing up user configs) */ #define CHECK_SETTING_SECURITY(L, name) \ @@ -41,7 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., static inline int checkSettingSecurity(lua_State* L, const std::string &name) { if (ScriptApiSecurity::isSecure(L) && name.compare(0, 7, "secure.") == 0) - throw LuaError("Attempt to set secure setting."); + throw LuaError("Attempted to set secure setting."); bool is_mainmenu = false; #ifndef SERVER @@ -54,6 +56,17 @@ static inline int checkSettingSecurity(lua_State* L, const std::string &name) return -1; } + const char *disallowed[] = { + "main_menu_script", "shader_path", "texture_path", "screenshot_path", + "serverlist_file", "serverlist_url", "map-dir", "contentdb_url", + }; + if (!is_mainmenu) { + for (const char *name2 : disallowed) { + if (name == name2) + throw LuaError("Attempted to set disallowed setting."); + } + } + return 0; } diff --git a/src/script/lua_api/l_sound.cpp b/src/script/lua_api/l_sound.cpp index b86eda53e..934b4a07e 100644 --- a/src/script/lua_api/l_sound.cpp +++ b/src/script/lua_api/l_sound.cpp @@ -28,9 +28,9 @@ int ModApiSound::l_sound_play(lua_State *L) { SimpleSoundSpec spec; read_soundspec(L, 1, spec); - bool looped = readParam<bool>(L, 2); + spec.loop = readParam<bool>(L, 2); - s32 handle = getGuiEngine(L)->playSound(spec, looped); + s32 handle = getGuiEngine(L)->playSound(spec); lua_pushinteger(L, handle); diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index b8f4347a8..b6c53e353 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -25,12 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., int ModApiStorage::l_get_mod_storage(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - if (!lua_isstring(L, -1)) { - return 0; - } - - std::string mod_name = readParam<std::string>(L, -1); + // Note that this is wrapped in Lua, see builtin/common/mod_storage.lua + std::string mod_name = readParam<std::string>(L, 1); ModMetadata *store = nullptr; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index b04f26fda..f602aed99 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -159,6 +159,17 @@ int ModApiUtil::l_write_json(lua_State *L) return 1; } +// get_tool_wear_after_use(uses[, initial_wear]) +int ModApiUtil::l_get_tool_wear_after_use(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + u32 uses = readParam<int>(L, 1); + u16 initial_wear = readParam<int>(L, 2, 0); + u16 wear = calculateResultWear(uses, initial_wear); + lua_pushnumber(L, wear); + return 1; +} + // get_dig_params(groups, tool_capabilities[, wear]) int ModApiUtil::l_get_dig_params(lua_State *L) { @@ -469,6 +480,8 @@ int ModApiUtil::l_get_version(lua_State *L) lua_setfield(L, table, "hash"); } + lua_pushboolean(L, DEVELOPMENT_BUILD); + lua_setfield(L, table, "is_dev"); return 1; } @@ -586,6 +599,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(parse_json); API_FCT(write_json); + API_FCT(get_tool_wear_after_use); API_FCT(get_dig_params); API_FCT(get_hit_params); @@ -647,6 +661,9 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(sha1); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + + LuaSettings::create(L, g_settings, g_settings_path); + lua_setfield(L, top, "settings"); } void ModApiUtil::InitializeAsync(lua_State *L, int top) @@ -671,6 +688,9 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(cpdir); API_FCT(mvdir); API_FCT(get_dir_list); + API_FCT(safe_file_write); + + API_FCT(request_insecure_environment); API_FCT(encode_base64); API_FCT(decode_base64); @@ -680,6 +700,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(encode_png); + API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index fcf8a1057..ec86c6632 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -50,6 +50,9 @@ private: // write_json(data[, styled]) static int l_write_json(lua_State *L); + // get_tool_wear_after_use(uses[, initial_wear]) + static int l_get_tool_wear_after_use(lua_State *L); + // get_dig_params(groups, tool_capabilities[, wear]) static int l_get_dig_params(lua_State *L); @@ -129,6 +132,4 @@ public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - - static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index e040e545b..6187a47db 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -17,11 +17,12 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ - +#include <map> #include "lua_api/l_vmanip.h" #include "lua_api/l_internal.h" #include "common/c_content.h" #include "common/c_converter.h" +#include "common/c_packer.h" #include "emerge.h" #include "environment.h" #include "map.h" @@ -45,6 +46,8 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; + if (vm->isOrphan()) + return 0; v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); @@ -112,23 +115,23 @@ int LuaVoxelManip::l_write_to_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam<bool>(L, 2); + GET_ENV_PTR; ServerMap *map = &(env->getServerMap()); + + std::map<v3s16, MapBlock*> modified_blocks; if (o->is_mapgen_vm || !update_light) { - o->vm->blitBackAll(&(o->modified_blocks)); + o->vm->blitBackAll(&modified_blocks); } else { - voxalgo::blit_back_with_light(map, o->vm, - &(o->modified_blocks)); + voxalgo::blit_back_with_light(map, o->vm, &modified_blocks); } MapEditEvent event; event.type = MEET_OTHER; - for (const auto &modified_block : o->modified_blocks) - event.modified_blocks.insert(modified_block.first); - + for (const auto &it : modified_blocks) + event.modified_blocks.insert(it.first); map->dispatchEvent(event); - o->modified_blocks.clear(); return 0; } @@ -166,7 +169,7 @@ int LuaVoxelManip::l_update_liquids(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); - Map *map = &(env->getMap()); + ServerMap *map = &(env->getServerMap()); const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); MMVManip *vm = o->vm; @@ -429,6 +432,34 @@ LuaVoxelManip *LuaVoxelManip::checkobject(lua_State *L, int narg) return *(LuaVoxelManip **)ud; // unbox pointer } +void *LuaVoxelManip::packIn(lua_State *L, int idx) +{ + LuaVoxelManip *o = checkobject(L, idx); + + if (o->is_mapgen_vm) + throw LuaError("nope"); + return o->vm->clone(); +} + +void LuaVoxelManip::packOut(lua_State *L, void *ptr) +{ + MMVManip *vm = reinterpret_cast<MMVManip*>(ptr); + if (!L) { + delete vm; + return; + } + + // Associate vmanip with map if the Lua env has one + Environment *env = getEnv(L); + if (env) + vm->reparent(&(env->getMap())); + + LuaVoxelManip *o = new LuaVoxelManip(vm, false); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); +} + void LuaVoxelManip::Register(lua_State *L) { lua_newtable(L); @@ -455,6 +486,8 @@ void LuaVoxelManip::Register(lua_State *L) // Can be created from Lua (VoxelManip()) lua_register(L, className, create_object); + + script_register_packer(L, className, packIn, packOut); } const char LuaVoxelManip::className[] = "VoxelManip"; diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 15ab9eef8..005133335 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include <map> #include "irr_v3d.h" #include "lua_api/l_base.h" @@ -33,7 +32,6 @@ class MMVManip; class LuaVoxelManip : public ModApiBase { private: - std::map<v3s16, MapBlock *> modified_blocks; bool is_mapgen_vm = false; static const char className[]; @@ -77,5 +75,8 @@ public: static LuaVoxelManip *checkobject(lua_State *L, int narg); + static void *packIn(lua_State *L, int idx); + static void packOut(lua_State *L, void *ptr); + static void Register(lua_State *L); }; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index 6643a9509..377205379 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_nodemeta.h" #include "lua_api/l_localplayer.h" #include "lua_api/l_camera.h" +#include "lua_api/l_settings.h" ClientScripting::ClientScripting(Client *client): ScriptApiBase(ScriptingType::Client) @@ -73,6 +74,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) LuaLocalPlayer::Register(L); LuaCamera::Register(L); ModChannelRef::Register(L); + LuaSettings::Register(L); ModApiUtil::InitializeClient(L, top); ModApiClient::Initialize(L, top); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 85411ded4..b462141b0 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -47,11 +47,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_storage.h" extern "C" { -#include "lualib.h" +#include <lualib.h> } ServerScripting::ServerScripting(Server* server): - ScriptApiBase(ScriptingType::Server) + ScriptApiBase(ScriptingType::Server), + asyncEngine(server) { setGameDef(server); @@ -88,6 +89,48 @@ ServerScripting::ServerScripting(Server* server): infostream << "SCRIPTAPI: Initialized game modules" << std::endl; } +void ServerScripting::initAsync() +{ + // Save globals to transfer + { + lua_State *L = getStack(); + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "get_globals_to_transfer"); + lua_call(L, 0, 1); + auto *data = script_pack(L, -1); + assert(!data->contains_userdata); + getServer()->m_async_globals_data.reset(data); + lua_pushnil(L); + lua_setfield(L, -3, "get_globals_to_transfer"); // unset function too + lua_pop(L, 2); // pop 'core', return value + } + + infostream << "SCRIPTAPI: Initializing async engine" << std::endl; + asyncEngine.registerStateInitializer(InitializeAsync); + asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiItemMod::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync); + // not added: ModApiMapgen is a minefield for thread safety + // not added: ModApiHttp async api can't really work together with our jobs + // not added: ModApiStorage is probably not thread safe(?) + + asyncEngine.initialize(0); +} + +void ServerScripting::stepAsync() +{ + asyncEngine.step(getStack()); +} + +u32 ServerScripting::queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin) +{ + return asyncEngine.queueAsyncJob(std::move(serialized_func), + param, mod_origin); +} + void ServerScripting::InitializeModApi(lua_State *L, int top) { // Register reference classes (userdata) @@ -125,3 +168,24 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); } + +void ServerScripting::InitializeAsync(lua_State *L, int top) +{ + // classes + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // globals data + lua_getglobal(L, "core"); + luaL_checktype(L, -1, LUA_TTABLE); + auto *data = ModApiBase::getServer(L)->m_async_globals_data.get(); + script_unpack(L, data); + lua_setfield(L, -2, "transferred_globals"); + lua_pop(L, 1); // pop 'core' +} diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index bf06ab197..9803397c5 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -27,6 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" #include "cpp_api/s_security.h" +#include "cpp_api/s_async.h" + +struct PackedValue; /*****************************************************************************/ /* Scripting <-> Server Game Interface */ @@ -48,6 +51,20 @@ public: // use ScriptApiBase::loadMod() to load mods + // Initialize async engine, call this AFTER loading all mods + void initAsync(); + + // Global step handler to collect async results + void stepAsync(); + + // Pass job to async threads + u32 queueAsync(std::string &&serialized_func, + PackedValue *param, const std::string &mod_origin); + private: void InitializeModApi(lua_State *L, int top); + + static void InitializeAsync(lua_State *L, int top); + + AsyncEngine asyncEngine; }; diff --git a/src/serialization.cpp b/src/serialization.cpp index d3009bc83..dc34dd7b9 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <zstd.h> /* report a zlib or i/o error */ -void zerr(int ret) +static void zerr(int ret) { dstream<<"zerr: "; switch (ret) { @@ -52,6 +52,17 @@ void zerr(int ret) } } +// Make sure that z is deleted in case of exception +template <int (*F)(z_stream*)> +class ZlibAutoDeleter { +public: + ZlibAutoDeleter(z_stream *ptr) : ptr_(ptr) {} + ~ZlibAutoDeleter() { F(ptr_); } + +private: + z_stream *ptr_; +}; + void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level) { z_stream z; @@ -68,6 +79,8 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level) if(ret != Z_OK) throw SerializationError("compressZlib: deflateInit failed"); + ZlibAutoDeleter<deflateEnd> deleter(&z); + // Point zlib to our input buffer z.next_in = (Bytef*)&data[0]; z.avail_in = data_size; @@ -91,8 +104,6 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level) if(status == Z_STREAM_END) break; } - - deflateEnd(&z); } void compressZlib(const std::string &data, std::ostream &os, int level) @@ -108,7 +119,6 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) char output_buffer[bufsize]; int status = 0; int ret; - int bytes_read = 0; int bytes_written = 0; int input_buffer_len = 0; @@ -120,9 +130,9 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) if(ret != Z_OK) throw SerializationError("dcompressZlib: inflateInit failed"); - z.avail_in = 0; + ZlibAutoDeleter<inflateEnd> deleter(&z); - //dstream<<"initial fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; + z.avail_in = 0; for(;;) { @@ -147,19 +157,13 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) is.read(input_buffer, bufsize); input_buffer_len = is.gcount(); z.avail_in = input_buffer_len; - //dstream<<"read fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; } if(z.avail_in == 0) { - //dstream<<"z.avail_in == 0"<<std::endl; break; } - //dstream<<"1 z.avail_in="<<z.avail_in<<std::endl; status = inflate(&z, Z_NO_FLUSH); - //dstream<<"2 z.avail_in="<<z.avail_in<<std::endl; - bytes_read += is.gcount() - z.avail_in; - //dstream<<"bytes_read="<<bytes_read<<std::endl; if(status == Z_NEED_DICT || status == Z_DATA_ERROR || status == Z_MEM_ERROR) @@ -168,16 +172,11 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) throw SerializationError("decompressZlib: inflate failed"); } int count = output_size - z.avail_out; - //dstream<<"count="<<count<<std::endl; if(count) os.write(output_buffer, count); bytes_written += count; if(status == Z_STREAM_END) { - //dstream<<"Z_STREAM_END"<<std::endl; - - //dstream<<"z.avail_in="<<z.avail_in<<std::endl; - //dstream<<"fail="<<is.fail()<<" bad="<<is.bad()<<std::endl; // Unget all the data that inflate didn't take is.clear(); // Just in case EOF is set for(u32 i=0; i < z.avail_in; i++) @@ -194,8 +193,6 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) break; } } - - inflateEnd(&z); } struct ZSTD_Deleter { diff --git a/src/server.cpp b/src/server.cpp index b3fb037b5..93767da9d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -136,24 +136,31 @@ void *ServerThread::run() return nullptr; } -v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const +v3f ServerPlayingSound::getPos(ServerEnvironment *env, bool *pos_exists) const { - if(pos_exists) *pos_exists = false; - switch(type){ - case SSP_LOCAL: + if (pos_exists) + *pos_exists = false; + + switch (type ){ + case SoundLocation::Local: return v3f(0,0,0); - case SSP_POSITIONAL: - if(pos_exists) *pos_exists = true; + case SoundLocation::Position: + if (pos_exists) + *pos_exists = true; return pos; - case SSP_OBJECT: { - if(object == 0) - return v3f(0,0,0); - ServerActiveObject *sao = env->getActiveObject(object); - if(!sao) - return v3f(0,0,0); - if(pos_exists) *pos_exists = true; - return sao->getBasePosition(); } + case SoundLocation::Object: + { + if (object == 0) + return v3f(0,0,0); + ServerActiveObject *sao = env->getActiveObject(object); + if (!sao) + return v3f(0,0,0); + if (pos_exists) + *pos_exists = true; + return sao->getBasePosition(); + } } + return v3f(0,0,0); } @@ -254,7 +261,7 @@ Server::Server( #if USE_PROMETHEUS m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend()); #else - m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend()); + m_metrics_backend = std::make_unique<MetricsBackend>(); #endif m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)"); @@ -268,9 +275,15 @@ Server::Server( "minetest_core_latency", "Latency value (in seconds)"); - m_aom_buffer_counter = m_metrics_backend->addCounter( - "minetest_core_aom_generated_count", - "Number of active object messages generated"); + + const std::string aom_types[] = {"reliable", "unreliable"}; + for (u32 i = 0; i < ARRLEN(aom_types); i++) { + std::string help_str("Number of active object messages generated ("); + help_str.append(aom_types[i]).append(")"); + m_aom_buffer_counter[i] = m_metrics_backend->addCounter( + "minetest_core_aom_generated_count", help_str, + {{"type", aom_types[i]}}); + } m_packet_recv_counter = m_metrics_backend->addCounter( "minetest_core_server_packet_recv", @@ -280,6 +293,10 @@ Server::Server( "minetest_core_server_packet_recv_processed", "Valid received packets processed"); + m_map_edit_event_counter = m_metrics_backend->addCounter( + "minetest_core_map_edit_events", + "Number of map edit events"); + m_lag_gauge->set(g_settings->getFloat("dedicated_server_step")); } @@ -396,7 +413,7 @@ void Server::init() } // Create emerge manager - m_emerge = new EmergeManager(this); + m_emerge = new EmergeManager(this, m_metrics_backend.get()); // Create ban manager std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; @@ -406,11 +423,16 @@ void Server::init() m_mod_storage_database = openModStorageDatabase(m_path_world); m_mod_storage_database->beginSave(); - m_modmgr = std::unique_ptr<ServerModManager>(new ServerModManager(m_path_world)); + m_modmgr = std::make_unique<ServerModManager>(m_path_world); std::vector<ModSpec> unsatisfied_mods = m_modmgr->getUnsatisfiedMods(); + // complain about mods with unsatisfied dependencies if (!m_modmgr->isConsistent()) { m_modmgr->printUnsatisfiedModsError(); + + warningstream + << "You have unsatisfied dependencies, loading your world anyway. " + << "This will become a fatal error in the future." << std::endl; } //lock environment @@ -426,10 +448,11 @@ void Server::init() m_script = new ServerScripting(this); // Must be created before mod loading because we have some inventory creation - m_inventory_mgr = std::unique_ptr<ServerInventoryManager>(new ServerInventoryManager()); + m_inventory_mgr = std::make_unique<ServerInventoryManager>(); m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME); + m_gamespec.checkAndLog(); m_modmgr->loadMods(m_script); // Read Textures and calculate sha1 sums @@ -461,7 +484,8 @@ void Server::init() // Initialize Environment m_startup_server_map = nullptr; // Ownership moved to ServerEnvironment - m_env = new ServerEnvironment(servermap, m_script, this, m_path_world); + m_env = new ServerEnvironment(servermap, m_script, this, + m_path_world, m_metrics_backend.get()); m_inventory_mgr->setEnv(m_env); m_clients.setEnv(m_env); @@ -480,6 +504,9 @@ void Server::init() // Give environment reference to scripting api m_script->initializeEnvironment(m_env); + // Do this after regular script init is done + m_script->initAsync(); + // Register us to receive map edit events servermap->addEventReceiver(this); @@ -521,7 +548,7 @@ void Server::start() actionstream << "World at [" << m_path_world << "]" << std::endl; actionstream << "Server for gameid=\"" << m_gamespec.id << "\" listening on "; - m_bind_addr.print(&actionstream); + m_bind_addr.print(actionstream); actionstream << "." << std::endl; } @@ -619,6 +646,7 @@ void Server::AsyncRunStep(bool initial_step) max_lag = dtime; } m_env->reportMaxLagEstimate(max_lag); + // Step environment m_env->step(dtime); } @@ -630,8 +658,8 @@ void Server::AsyncRunStep(bool initial_step) // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("server_unload_unused_data_timeout"), - U32_MAX); + std::max(g_settings->getFloat("server_unload_unused_data_timeout"), 0.0f), + -1); } /* @@ -665,7 +693,7 @@ void Server::AsyncRunStep(bool initial_step) ScopeProfiler sp(g_profiler, "Server: liquid transform"); std::map<v3s16, MapBlock*> modified_blocks; - m_env->getMap().transformLiquids(modified_blocks, m_env); + m_env->getServerMap().transformLiquids(modified_blocks, m_env); /* Set the modified blocks unsent for all the clients @@ -723,28 +751,29 @@ void Server::AsyncRunStep(bool initial_step) //infostream<<"Server: Checking added and deleted active objects"<<std::endl; MutexAutoLock envlock(m_env_mutex); - m_clients.lock(); - const RemoteClientMap &clients = m_clients.getClientList(); - ScopeProfiler sp(g_profiler, "Server: update objects within range"); + { + ClientInterface::AutoLock clientlock(m_clients); + const RemoteClientMap &clients = m_clients.getClientList(); + ScopeProfiler sp(g_profiler, "Server: update objects within range"); - m_player_gauge->set(clients.size()); - for (const auto &client_it : clients) { - RemoteClient *client = client_it.second; + m_player_gauge->set(clients.size()); + for (const auto &client_it : clients) { + RemoteClient *client = client_it.second; - if (client->getState() < CS_DefinitionsSent) - continue; + if (client->getState() < CS_DefinitionsSent) + continue; - // This can happen if the client times out somehow - if (!m_env->getPlayer(client->peer_id)) - continue; + // This can happen if the client times out somehow + if (!m_env->getPlayer(client->peer_id)) + continue; - PlayerSAO *playersao = getPlayerSAO(client->peer_id); - if (!playersao) - continue; + PlayerSAO *playersao = getPlayerSAO(client->peer_id); + if (!playersao) + continue; - SendActiveObjectRemoveAdd(client, playersao); + SendActiveObjectRemoveAdd(client, playersao); + } } - m_clients.unlock(); // Write changes to the mod storage m_mod_storage_save_timer -= dtime; @@ -768,10 +797,14 @@ void Server::AsyncRunStep(bool initial_step) // Get active object messages from environment ActiveObjectMessage aom(0); - u32 aom_count = 0; + u32 count_reliable = 0, count_unreliable = 0; for(;;) { if (!m_env->getActiveObjectMessage(&aom)) break; + if (aom.reliable) + count_reliable++; + else + count_unreliable++; std::vector<ActiveObjectMessage>* message_list = nullptr; auto n = buffered_messages.find(aom.id); @@ -782,68 +815,69 @@ void Server::AsyncRunStep(bool initial_step) message_list = n->second; } message_list->push_back(std::move(aom)); - aom_count++; } - m_aom_buffer_counter->increment(aom_count); - - m_clients.lock(); - const RemoteClientMap &clients = m_clients.getClientList(); - // Route data to every client - std::string reliable_data, unreliable_data; - for (const auto &client_it : clients) { - reliable_data.clear(); - unreliable_data.clear(); - RemoteClient *client = client_it.second; - PlayerSAO *player = getPlayerSAO(client->peer_id); - // Go through all objects in message buffer - for (const auto &buffered_message : buffered_messages) { - // If object does not exist or is not known by client, skip it - u16 id = buffered_message.first; - ServerActiveObject *sao = m_env->getActiveObject(id); - if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) - continue; + m_aom_buffer_counter[0]->increment(count_reliable); + m_aom_buffer_counter[1]->increment(count_unreliable); - // Get message list of object - std::vector<ActiveObjectMessage>* list = buffered_message.second; - // Go through every message - for (const ActiveObjectMessage &aom : *list) { - // Send position updates to players who do not see the attachment - if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { - if (sao->getId() == player->getId()) - continue; - - // Do not send position updates for attached players - // as long the parent is known to the client - ServerActiveObject *parent = sao->getParent(); - if (parent && client->m_known_objects.find(parent->getId()) != - client->m_known_objects.end()) - continue; - } + { + ClientInterface::AutoLock clientlock(m_clients); + const RemoteClientMap &clients = m_clients.getClientList(); + // Route data to every client + std::string reliable_data, unreliable_data; + for (const auto &client_it : clients) { + reliable_data.clear(); + unreliable_data.clear(); + RemoteClient *client = client_it.second; + PlayerSAO *player = getPlayerSAO(client->peer_id); + // Go through all objects in message buffer + for (const auto &buffered_message : buffered_messages) { + // If object does not exist or is not known by client, skip it + u16 id = buffered_message.first; + ServerActiveObject *sao = m_env->getActiveObject(id); + if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end()) + continue; - // Add full new data to appropriate buffer - std::string &buffer = aom.reliable ? reliable_data : unreliable_data; - char idbuf[2]; - writeU16((u8*) idbuf, aom.id); - // u16 id - // std::string data - buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString16(aom.datastring)); + // Get message list of object + std::vector<ActiveObjectMessage>* list = buffered_message.second; + // Go through every message + for (const ActiveObjectMessage &aom : *list) { + // Send position updates to players who do not see the attachment + if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) { + if (sao->getId() == player->getId()) + continue; + + // Do not send position updates for attached players + // as long the parent is known to the client + ServerActiveObject *parent = sao->getParent(); + if (parent && client->m_known_objects.find(parent->getId()) != + client->m_known_objects.end()) + continue; + } + + // Add full new data to appropriate buffer + std::string &buffer = aom.reliable ? reliable_data : unreliable_data; + char idbuf[2]; + writeU16((u8*) idbuf, aom.id); + // u16 id + // std::string data + buffer.append(idbuf, sizeof(idbuf)); + buffer.append(serializeString16(aom.datastring)); + } + } + /* + reliable_data and unreliable_data are now ready. + Send them. + */ + if (!reliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, reliable_data); } - } - /* - reliable_data and unreliable_data are now ready. - Send them. - */ - if (!reliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, reliable_data); - } - if (!unreliable_data.empty()) { - SendActiveObjectMessages(client->peer_id, unreliable_data, false); + if (!unreliable_data.empty()) { + SendActiveObjectMessages(client->peer_id, unreliable_data, false); + } } } - m_clients.unlock(); // Clear buffered_messages for (auto &buffered_message : buffered_messages) { @@ -858,20 +892,18 @@ void Server::AsyncRunStep(bool initial_step) // We will be accessing the environment MutexAutoLock lock(m_env_mutex); - // Don't send too many at a time - //u32 count = 0; - - // Single change sending is disabled if queue size is not small + // Single change sending is disabled if queue size is big bool disable_single_change_sending = false; if(m_unsent_map_edit_queue.size() >= 4) disable_single_change_sending = true; - int event_count = m_unsent_map_edit_queue.size(); + const auto event_count = m_unsent_map_edit_queue.size(); + m_map_edit_event_counter->increment(event_count); // We'll log the amount of each Profiler prof; - std::list<v3s16> node_meta_updates; + std::unordered_set<v3s16> node_meta_updates; while (!m_unsent_map_edit_queue.empty()) { MapEditEvent* event = m_unsent_map_edit_queue.front(); @@ -898,9 +930,7 @@ void Server::AsyncRunStep(bool initial_step) case MEET_BLOCK_NODE_METADATA_CHANGED: { prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1); if (!event->is_private_change) { - // Don't send the change yet. Collect them to eliminate dupes. - node_meta_updates.remove(event->p); - node_meta_updates.push_back(event->p); + node_meta_updates.emplace(event->p); } if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx( @@ -953,7 +983,7 @@ void Server::AsyncRunStep(bool initial_step) } // Send all metadata updates - if (node_meta_updates.size()) + if (!node_meta_updates.empty()) sendMetadataChanged(node_meta_updates); } @@ -1036,8 +1066,7 @@ void Server::Receive() } catch (const ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << " what()=" << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." - L"Try reconnecting or updating your client"); + DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); } catch (const con::PeerNotFoundException &e) { // Do nothing } catch (const con::NoIncomingDataException &e) { @@ -1050,18 +1079,14 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) { std::string playername; PlayerSAO *playersao = NULL; - m_clients.lock(); - try { + { + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client) { playername = client->getName(); playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); } - } catch (std::exception &e) { - m_clients.unlock(); - throw; } - m_clients.unlock(); RemotePlayer *player = m_env->getPlayer(playername.c_str()); @@ -1070,15 +1095,13 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); } else { errorstream << "Server: " << playername << ": Failed to emerge player" << std::endl; - DenyAccess_Legacy(peer_id, L"Could not allocate player."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); } - return NULL; + return nullptr; } /* @@ -1096,7 +1119,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) SendInventory(playersao, false); // Send HP - SendPlayerHP(playersao); + SendPlayerHP(playersao, false); // Send death screen if (playersao->isDead()) @@ -1143,18 +1166,16 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + // FIXME: Isn't it a bit excessive to check this for every packet? + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; - // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + << addr_s << "; banned name was " << ban_name << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, + "Your IP is banned. Banned name was " + ban_name); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't @@ -1234,13 +1255,12 @@ void Server::onMapEditEvent(const MapEditEvent &event) void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block) { std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Set the modified blocks unsent for all the clients for (const session_t client_id : clients) { if (RemoteClient *client = m_clients.lockedGetClientNoEx(client_id)) client->SetBlocksNotSent(block); } - m_clients.unlock(); } void Server::peerAdded(con::Peer *peer) @@ -1268,13 +1288,11 @@ bool Server::getClientConInfo(session_t peer_id, con::rtt_stat_type type, float* bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) { - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_Invalid); - if (!client) { - m_clients.unlock(); + if (!client) return false; - } ret.state = client->getState(); ret.addr = client->getAddress(); @@ -1289,8 +1307,6 @@ bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) ret.lang_code = client->getLangCode(); - m_clients.unlock(); - return true; } @@ -1370,7 +1386,7 @@ void Server::SendMovement(session_t peer_id) void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { m_script->player_event(playersao, "health_changed"); - SendPlayerHP(playersao); + SendPlayerHP(playersao, reason.type != PlayerHPChangeReason::SET_HP_MAX); // Send to other clients playersao->sendPunchCommand(); @@ -1379,15 +1395,15 @@ void Server::HandlePlayerHPChange(PlayerSAO *playersao, const PlayerHPChangeReas HandlePlayerDeath(playersao, reason); } -void Server::SendPlayerHP(PlayerSAO *playersao) +void Server::SendPlayerHP(PlayerSAO *playersao, bool effect) { - SendHP(playersao->getPeerID(), playersao->getHP()); + SendHP(playersao->getPeerID(), playersao->getHP(), effect); } -void Server::SendHP(session_t peer_id, u16 hp) +void Server::SendHP(session_t peer_id, u16 hp, bool effect) { - NetworkPacket pkt(TOCLIENT_HP, 1, peer_id); - pkt << hp; + NetworkPacket pkt(TOCLIENT_HP, 3, peer_id); + pkt << hp << effect; Send(&pkt); } @@ -1413,13 +1429,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) -{ - NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); - pkt << reason; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -1596,7 +1605,12 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, if (peer_id == PEER_ID_INEXISTENT) { std::vector<session_t> clients = m_clients.getClientIDs(); - const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS; + const v3f pos = ( + p.pos.start.min.val + + p.pos.start.max.val + + p.pos.end.min.val + + p.pos.end.max.val + ) / 4.0f * BS; const float radius_sq = radius * radius; /* Don't send short-lived spawners to distant players. * This could be replaced with proper tracking at some point. */ @@ -1624,11 +1638,19 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id); - pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel - << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime - << p.minsize << p.maxsize << p.collisiondetection; + pkt << p.amount << p.time; + { // serialize legacy fields + std::ostringstream os(std::ios_base::binary); + p.pos.start.legacySerialize(os); + p.vel.start.legacySerialize(os); + p.acc.start.legacySerialize(os); + p.exptime.start.legacySerialize(os); + p.size.start.legacySerialize(os); + pkt.putRawString(os.str()); + } + pkt << p.collisiondetection; - pkt.putLongString(p.texture); + pkt.putLongString(p.texture.string); pkt << id << p.vertical << p.collision_removal << attached_id; { @@ -1639,6 +1661,51 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, pkt << p.glow << p.object_collision; pkt << p.node.param0 << p.node.param2 << p.node_tile; + { // serialize new fields + // initial bias for older properties + pkt << p.pos.start.bias + << p.vel.start.bias + << p.acc.start.bias + << p.exptime.start.bias + << p.size.start.bias; + + std::ostringstream os(std::ios_base::binary); + + // final tween frames of older properties + p.pos.end.serialize(os); + p.vel.end.serialize(os); + p.acc.end.serialize(os); + p.exptime.end.serialize(os); + p.size.end.serialize(os); + + // properties for legacy texture field + p.texture.serialize(os, protocol_version, true); + + // new properties + p.drag.serialize(os); + p.jitter.serialize(os); + p.bounce.serialize(os); + ParticleParamTypes::serializeParameterValue(os, p.attractor_kind); + if (p.attractor_kind != ParticleParamTypes::AttractorKind::none) { + p.attract.serialize(os); + p.attractor_origin.serialize(os); + writeU16(os, p.attractor_attachment); /* object ID */ + writeU8(os, p.attractor_kill); + if (p.attractor_kind != ParticleParamTypes::AttractorKind::point) { + p.attractor_direction.serialize(os); + writeU16(os, p.attractor_direction_attachment); + } + } + p.radius.serialize(os); + + ParticleParamTypes::serializeParameterValue(os, (u16)p.texpool.size()); + for (const auto& tex : p.texpool) { + tex.serialize(os, protocol_version); + } + + pkt.putRawString(os.str()); + } + Send(&pkt); } @@ -1778,7 +1845,8 @@ void Server::SendSetStars(session_t peer_id, const StarParams ¶ms) NetworkPacket pkt(TOCLIENT_SET_STARS, 0, peer_id); pkt << params.visible << params.count - << params.starcolor << params.scale; + << params.starcolor << params.scale + << params.day_opacity; Send(&pkt); } @@ -1802,6 +1870,16 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, Send(&pkt); } +void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) +{ + NetworkPacket pkt(TOCLIENT_SET_LIGHTING, + 4, peer_id); + + pkt << lighting.shadow_intensity; + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); @@ -2059,14 +2137,13 @@ inline s32 Server::nextSoundId() return ret; } -s32 Server::playSound(const SimpleSoundSpec &spec, - const ServerSoundParams ¶ms, bool ephemeral) +s32 Server::playSound(ServerPlayingSound ¶ms, bool ephemeral) { // Find out initial position of sound bool pos_exists = false; - v3f pos = params.getPos(m_env, &pos_exists); + const v3f pos = params.getPos(m_env, &pos_exists); // If position is not found while it should be, cancel sound - if(pos_exists != (params.type != ServerSoundParams::SSP_LOCAL)) + if(pos_exists != (params.type != SoundLocation::Local)) return -1; // Filter destination clients @@ -2111,101 +2188,68 @@ s32 Server::playSound(const SimpleSoundSpec &spec, if(dst_clients.empty()) return -1; - // Create the sound - s32 id; - ServerPlayingSound *psound = nullptr; - if (ephemeral) { - id = -1; // old clients will still use this, so pick a reserved ID - } else { - id = nextSoundId(); - // The sound will exist as a reference in m_playing_sounds - m_playing_sounds[id] = ServerPlayingSound(); - psound = &m_playing_sounds[id]; - psound->params = params; - psound->spec = spec; - } + // old clients will still use this, so pick a reserved ID (-1) + const s32 id = ephemeral ? -1 : nextSoundId(); - float gain = params.gain * spec.gain; + float gain = params.gain * params.spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); - pkt << id << spec.name << gain + pkt << id << params.spec.name << gain << (u8) params.type << pos << params.object - << params.loop << params.fade << params.pitch + << params.spec.loop << params.spec.fade << params.spec.pitch << ephemeral; bool as_reliable = !ephemeral; - for (const u16 dst_client : dst_clients) { - if (psound) - psound->clients.insert(dst_client); - m_clients.send(dst_client, 0, &pkt, as_reliable); + for (const session_t peer_id : dst_clients) { + if (!ephemeral) + params.clients.insert(peer_id); + m_clients.send(peer_id, 0, &pkt, as_reliable); } + + if (!ephemeral) + m_playing_sounds[id] = std::move(params); return id; } void Server::stopSound(s32 handle) { - // Get sound reference - std::unordered_map<s32, ServerPlayingSound>::iterator i = - m_playing_sounds.find(handle); - if (i == m_playing_sounds.end()) + auto it = m_playing_sounds.find(handle); + if (it == m_playing_sounds.end()) return; - ServerPlayingSound &psound = i->second; + + ServerPlayingSound &psound = it->second; NetworkPacket pkt(TOCLIENT_STOP_SOUND, 4); pkt << handle; - for (std::unordered_set<session_t>::const_iterator si = psound.clients.begin(); - si != psound.clients.end(); ++si) { + for (session_t peer_id : psound.clients) { // Send as reliable - m_clients.send(*si, 0, &pkt, true); + m_clients.send(peer_id, 0, &pkt, true); } + // Remove sound reference - m_playing_sounds.erase(i); + m_playing_sounds.erase(it); } void Server::fadeSound(s32 handle, float step, float gain) { - // Get sound reference - std::unordered_map<s32, ServerPlayingSound>::iterator i = - m_playing_sounds.find(handle); - if (i == m_playing_sounds.end()) + auto it = m_playing_sounds.find(handle); + if (it == m_playing_sounds.end()) return; - ServerPlayingSound &psound = i->second; - psound.params.gain = gain; + ServerPlayingSound &psound = it->second; + psound.gain = gain; // destination gain NetworkPacket pkt(TOCLIENT_FADE_SOUND, 4); pkt << handle << step << gain; - // Backwards compability - bool play_sound = gain > 0; - ServerPlayingSound compat_psound = psound; - compat_psound.clients.clear(); - - NetworkPacket compat_pkt(TOCLIENT_STOP_SOUND, 4); - compat_pkt << handle; - - for (std::unordered_set<u16>::iterator it = psound.clients.begin(); - it != psound.clients.end();) { - if (m_clients.getProtocolVersion(*it) >= 32) { - // Send as reliable - m_clients.send(*it, 0, &pkt, true); - ++it; - } else { - compat_psound.clients.insert(*it); - // Stop old sound - m_clients.send(*it, 0, &compat_pkt, true); - psound.clients.erase(it++); - } + for (session_t peer_id : psound.clients) { + // Send as reliable + m_clients.send(peer_id, 0, &pkt, true); } // Remove sound reference - if (!play_sound || psound.clients.empty()) - m_playing_sounds.erase(i); - - if (play_sound && !compat_psound.clients.empty()) { - // Play new sound volume on older clients - playSound(compat_psound.spec, compat_psound.params); - } + if (gain <= 0 || psound.clients.empty()) + m_playing_sounds.erase(it); } void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players, @@ -2219,7 +2263,7 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players, pkt << p; std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2242,8 +2286,6 @@ void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players, // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_players, @@ -2258,7 +2300,7 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player << (u8) (remove_metadata ? 0 : 1); std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id); @@ -2281,35 +2323,35 @@ void Server::sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_player // Send as reliable m_clients.send(client_id, 0, &pkt, true); } - - m_clients.unlock(); } -void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes) +void Server::sendMetadataChanged(const std::unordered_set<v3s16> &positions, float far_d_nodes) { - float maxd = far_d_nodes * BS; NodeMetadataList meta_updates_list(false); - std::vector<session_t> clients = m_clients.getClientIDs(); + std::ostringstream os(std::ios::binary); - m_clients.lock(); + std::vector<session_t> clients = m_clients.getClientIDs(); + ClientInterface::AutoLock clientlock(m_clients); for (session_t i : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(i); if (!client) continue; - ServerActiveObject *player = m_env->getActiveObject(i); - v3f player_pos = player ? player->getBasePosition() : v3f(); + ServerActiveObject *player = getPlayerSAO(i); + v3s16 player_pos; + if (player) + player_pos = floatToInt(player->getBasePosition(), BS); - for (const v3s16 &pos : meta_updates) { + for (const v3s16 pos : positions) { NodeMetadata *meta = m_env->getMap().getNodeMetadata(pos); if (!meta) continue; v3s16 block_pos = getNodeBlockPos(pos); - if (!client->isBlockSent(block_pos) || (player && - player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) { + if (!client->isBlockSent(block_pos) || + player_pos.getDistanceFrom(pos) > far_d_nodes) { client->SetBlockNotSent(block_pos); continue; } @@ -2321,38 +2363,49 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far continue; // Send the meta changes - std::ostringstream os(std::ios::binary); + os.str(""); meta_updates_list.serialize(os, client->serialization_version, false, true, true); - std::ostringstream oss(std::ios::binary); - compressZlib(os.str(), oss); + std::string raw = os.str(); + os.str(""); + compressZlib(raw, os); - NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0); - pkt.putLongString(oss.str()); - m_clients.send(i, 0, &pkt, true); + NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0, i); + pkt.putLongString(os.str()); + Send(&pkt); meta_updates_list.clear(); } - - m_clients.unlock(); } void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, - u16 net_proto_version) + u16 net_proto_version, SerializedBlockCache *cache) { - /* - Create a packet with the block in the right format - */ thread_local const int net_compression_level = rangelim(g_settings->getS16("map_compression_level_net"), -1, 9); - std::ostringstream os(std::ios_base::binary); - block->serialize(os, ver, false, net_compression_level); - block->serializeNetworkSpecific(os); - std::string s = os.str(); + std::string s, *sptr = nullptr; - NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + s.size(), peer_id); + if (cache) { + auto it = cache->find({block->getPos(), ver}); + if (it != cache->end()) + sptr = &it->second; + } + // Serialize the block in the right format + if (!sptr) { + std::ostringstream os(std::ios_base::binary); + block->serialize(os, ver, false, net_compression_level); + block->serializeNetworkSpecific(os); + s = os.str(); + sptr = &s; + } + + NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + sptr->size(), peer_id); pkt << block->getPos(); - pkt.putRawString(s.c_str(), s.size()); + pkt.putRawString(*sptr); Send(&pkt); + + // Store away in cache + if (cache && sptr == &s) + (*cache)[{block->getPos(), ver}] = std::move(s); } void Server::SendBlocks(float dtime) @@ -2362,14 +2415,14 @@ void Server::SendBlocks(float dtime) std::vector<PrioritySortedBlockTransfer> queue; - u32 total_sending = 0; + u32 total_sending = 0, unique_clients = 0; { ScopeProfiler sp2(g_profiler, "Server::SendBlocks(): Collect list"); std::vector<session_t> clients = m_clients.getClientIDs(); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); for (const session_t client_id : clients) { RemoteClient *client = m_clients.lockedGetClientNoEx(client_id, CS_Active); @@ -2377,9 +2430,10 @@ void Server::SendBlocks(float dtime) continue; total_sending += client->getSendingCount(); + const auto old_count = queue.size(); client->GetNextBlocks(m_env,m_emerge, dtime, queue); + unique_clients += queue.size() > old_count ? 1 : 0; } - m_clients.unlock(); } // Sort. @@ -2387,7 +2441,7 @@ void Server::SendBlocks(float dtime) // Lowest is most important. std::sort(queue.begin(), queue.end()); - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); // Maximal total count calculation // The per-client block sends is halved with the maximal online users @@ -2397,6 +2451,12 @@ void Server::SendBlocks(float dtime) ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients"); Map &map = m_env->getMap(); + SerializedBlockCache cache, *cache_ptr = nullptr; + if (unique_clients > 1) { + // caching is pointless with a single client + cache_ptr = &cache; + } + for (const PrioritySortedBlockTransfer &block_to_send : queue) { if (total_sending >= max_blocks_to_send) break; @@ -2411,12 +2471,11 @@ void Server::SendBlocks(float dtime) continue; SendBlockNoLock(block_to_send.peer_id, block, client->serialization_version, - client->net_proto_version); + client->net_proto_version, cache_ptr); client->SentBlock(block_to_send.pos); total_sending++; } - m_clients.unlock(); } bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) @@ -2425,15 +2484,12 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) if (!block) return false; - m_clients.lock(); + ClientInterface::AutoLock clientlock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Active); - if (!client || client->isBlockSent(blockpos)) { - m_clients.unlock(); + if (!client || client->isBlockSent(blockpos)) return false; - } SendBlockNoLock(peer_id, block, client->serialization_version, client->net_proto_version); - m_clients.unlock(); return true; } @@ -2766,9 +2822,10 @@ void Server::RespawnPlayer(session_t peer_id) << playersao->getPlayer()->getName() << " respawns" << std::endl; - playersao->setHP(playersao->accessObjectProperties()->hp_max, + const auto *prop = playersao->accessObjectProperties(); + playersao->setHP(prop->hp_max, PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); - playersao->setBreath(playersao->accessObjectProperties()->breath_max); + playersao->setBreath(prop->breath_max); bool repositioned = m_script->on_respawnplayer(playersao); if (!repositioned) { @@ -2785,29 +2842,10 @@ void Server::DenySudoAccess(session_t peer_id) } -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - SendAccessDenied(peer_id, reason, str_reason, reconnect); - - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason) -{ - SendAccessDenied(peer_id, reason, custom_reason); - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - -// 13/03/15: remove this function when protocol version 25 will become -// the minimum version for MT users, maybe in 1 year -void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason) + const std::string &custom_reason, bool reconnect) { - SendAccessDenied_Legacy(peer_id, reason); + SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); DisconnectPeer(peer_id); } @@ -2993,8 +3031,8 @@ std::wstring Server::handleChat(const std::string &name, return ws.str(); } case RPLAYER_CHATRESULT_KICK: - DenyAccess_Legacy(player->getPeerId(), - L"You have been kicked due to message flooding."); + DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + "You have been kicked due to message flooding."); return L""; case RPLAYER_CHATRESULT_OK: break; @@ -3111,7 +3149,7 @@ std::string Server::getStatusString() // Version os << "version: " << g_version_string; // Game - os << " | game: " << (m_gamespec.name.empty() ? m_gamespec.id : m_gamespec.name); + os << " | game: " << (m_gamespec.title.empty() ? m_gamespec.id : m_gamespec.title); // Uptime os << " | uptime: " << duration_to_string((int) m_uptime_counter->get()); // Max lag estimate @@ -3290,9 +3328,12 @@ bool Server::hudSetFlags(RemotePlayer *player, u32 flags, u32 mask) if (!player) return false; + u32 new_hud_flags = (player->hud_flags & ~mask) | flags; + if (new_hud_flags == player->hud_flags) // no change + return true; + SendHUDSetFlags(player->getPeerId(), flags, mask); - player->hud_flags &= ~mask; - player->hud_flags |= flags; + player->hud_flags = new_hud_flags; PlayerSAO* playersao = player->getPlayerSAO(); @@ -3401,6 +3442,13 @@ void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override, SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio); } +void Server::setLighting(RemotePlayer *player, const Lighting &lighting) +{ + sanity_check(player); + player->setLighting(lighting); + SendSetLighting(player->getPeerId(), lighting); +} + void Server::notifyPlayers(const std::wstring &msg) { SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg)); @@ -3535,48 +3583,49 @@ bool Server::dynamicAddMedia(std::string filepath, legacy_pkt.putLongString(filedata); std::unordered_set<session_t> delivered, waiting; - m_clients.lock(); - for (auto &pair : m_clients.getClientList()) { - if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) { - /* - If a client is in the DefinitionsSent state it is too late to - transfer the file via sendMediaAnnouncement() but at the same - time the client cannot accept a media push yet. - Short of artificially delaying the joining process there is no - way for the server to resolve this so we (currently) opt not to. - */ - warningstream << "The media \"" << filename << "\" (dynamic) could " - "not be delivered to " << pair.second->getName() - << " due to a race condition." << std::endl; - continue; - } - if (pair.second->getState() < CS_Active) - continue; + { + ClientInterface::AutoLock clientlock(m_clients); + for (auto &pair : m_clients.getClientList()) { + if (pair.second->getState() == CS_DefinitionsSent && !ephemeral) { + /* + If a client is in the DefinitionsSent state it is too late to + transfer the file via sendMediaAnnouncement() but at the same + time the client cannot accept a media push yet. + Short of artificially delaying the joining process there is no + way for the server to resolve this so we (currently) opt not to. + */ + warningstream << "The media \"" << filename << "\" (dynamic) could " + "not be delivered to " << pair.second->getName() + << " due to a race condition." << std::endl; + continue; + } + if (pair.second->getState() < CS_Active) + continue; - const auto proto_ver = pair.second->net_proto_version; - if (proto_ver < 39) - continue; + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) + continue; - const session_t peer_id = pair.second->peer_id; - if (!to_player.empty() && getPlayerName(peer_id) != to_player) - continue; + const session_t peer_id = pair.second->peer_id; + if (!to_player.empty() && getPlayerName(peer_id) != to_player) + continue; - if (proto_ver < 40) { - delivered.emplace(peer_id); - /* - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. In practice this means channels 1 and 0. - */ - m_clients.send(peer_id, 1, &legacy_pkt, true); - m_clients.send(peer_id, 0, &legacy_pkt, true); - } else { - waiting.emplace(peer_id); - Send(peer_id, &pkt); + if (proto_ver < 40) { + delivered.emplace(peer_id); + /* + The network layer only guarantees ordered delivery inside a channel. + Since the very next packet could be one that uses the media, we have + to push the media over ALL channels to ensure it is processed before + it is used. In practice this means channels 1 and 0. + */ + m_clients.send(peer_id, 1, &legacy_pkt, true); + m_clients.send(peer_id, 0, &legacy_pkt, true); + } else { + waiting.emplace(peer_id); + Send(peer_id, &pkt); + } } } - m_clients.unlock(); // Run callback for players that already had the file delivered (legacy-only) for (session_t peer_id : delivered) { @@ -3686,11 +3735,6 @@ const ModSpec *Server::getModSpec(const std::string &modname) const return m_modmgr->getModSpec(modname); } -void Server::getModNames(std::vector<std::string> &modlist) -{ - m_modmgr->getModNames(modlist); -} - std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; @@ -3712,8 +3756,8 @@ v3f Server::findSpawnPos() s32 range = MYMIN(1 + i, range_max); // We're going to try to throw the player to this position v2s16 nodepos2d = v2s16( - -range + (myrand() % (range * 2)), - -range + (myrand() % (range * 2))); + -range + myrand_range(0, range*2), + -range + myrand_range(0, range*2)); // Get spawn level at point s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d); // Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to diff --git a/src/server.h b/src/server.h index 2741b3157..cb7d77067 100644 --- a/src/server.h +++ b/src/server.h @@ -69,9 +69,11 @@ struct SkyboxParams; struct SunParams; struct MoonParams; struct StarParams; +struct Lighting; class ServerThread; class ServerModManager; class ServerInventoryManager; +struct PackedValue; enum ClientDeletionReason { CDR_LEAVE, @@ -94,30 +96,22 @@ struct MediaInfo } }; -struct ServerSoundParams +// Combines the pure sound (SimpleSoundSpec) with positional information +struct ServerPlayingSound { - enum Type { - SSP_LOCAL, - SSP_POSITIONAL, - SSP_OBJECT - } type = SSP_LOCAL; - float gain = 1.0f; - float fade = 0.0f; - float pitch = 1.0f; - bool loop = false; + SoundLocation type = SoundLocation::Local; + + float gain = 1.0f; // for amplification of the base sound float max_hear_distance = 32 * BS; v3f pos; u16 object = 0; - std::string to_player = ""; - std::string exclude_player = ""; + std::string to_player; + std::string exclude_player; v3f getPos(ServerEnvironment *env, bool *pos_exists) const; -}; -struct ServerPlayingSound -{ - ServerSoundParams params; SimpleSoundSpec spec; + std::unordered_set<session_t> clients; // peer ids }; @@ -234,8 +228,7 @@ public: // Returns -1 if failed, sound handle on success // Envlock - s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms, - bool ephemeral=false); + s32 playSound(ServerPlayingSound ¶ms, bool ephemeral=false); void stopSound(s32 handle); void fadeSound(s32 handle, float step, float gain); @@ -291,11 +284,10 @@ public: virtual const std::vector<ModSpec> &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; - void getModNames(std::vector<std::string> &modlist); - std::string getBuiltinLuaPath(); + static std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } - inline bool isSingleplayer() + inline bool isSingleplayer() const { return m_simple_singleplayer_mode; } inline void setAsyncFatalError(const std::string &error) @@ -333,17 +325,18 @@ public: void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); + void setLighting(RemotePlayer *player, const Lighting &lighting); + + void RespawnPlayer(session_t peer_id); + /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); - void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason = "", bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = ""); + const std::string &custom_reason = "", bool reconnect = false); void acceptAuth(session_t peer_id, bool forSudoMode); - void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientInfo &ret); @@ -351,7 +344,7 @@ public: void printToConsoleOnly(const std::string &text); void HandlePlayerHPChange(PlayerSAO *sao, const PlayerHPChangeReason &reason); - void SendPlayerHP(PlayerSAO *sao); + void SendPlayerHP(PlayerSAO *sao, bool effect); void SendPlayerBreath(PlayerSAO *sao); void SendInventory(PlayerSAO *playerSAO, bool incremental); void SendMovePlayer(session_t peer_id); @@ -386,6 +379,12 @@ public: static bool migrateModStorageDatabase(const GameParams &game_params, const Settings &cmd_args); + // Lua files registered for init of async env, pair of modname + path + std::vector<std::pair<std::string, std::string>> m_async_init_files; + + // Data transferred into async envs at init time + std::unique_ptr<PackedValue> m_async_globals_data; + // Bind address Address m_bind_addr; @@ -419,10 +418,19 @@ private: std::unordered_set<session_t> waiting_players; }; + // The standard library does not implement std::hash for pairs so we have this: + struct SBCHash { + size_t operator() (const std::pair<v3s16, u16> &p) const { + return std::hash<v3s16>()(p.first) ^ p.second; + } + }; + + typedef std::unordered_map<std::pair<v3s16, u16>, std::string, SBCHash> SerializedBlockCache; + void init(); void SendMovement(session_t peer_id); - void SendHP(session_t peer_id, u16 hp); + void SendHP(session_t peer_id, u16 hp, bool effect); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason, bool reconnect = false); @@ -459,6 +467,7 @@ private: void SendSetStars(session_t peer_id, const StarParams ¶ms); void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); + void SendSetLighting(session_t peer_id, const Lighting &lighting); void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); @@ -474,11 +483,13 @@ private: std::unordered_set<u16> *far_players = nullptr, float far_d_nodes = 100, bool remove_metadata = true); - void sendMetadataChanged(const std::list<v3s16> &meta_updates, + void sendMetadataChanged(const std::unordered_set<v3s16> &positions, float far_d_nodes = 100); // Environment and Connection must be locked when called - void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version); + // `cache` may only be very short lived! (invalidation not handeled) + void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, + u16 net_proto_version, SerializedBlockCache *cache = nullptr); // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); @@ -511,7 +522,6 @@ private: */ void HandlePlayerDeath(PlayerSAO* sao, const PlayerHPChangeReason &reason); - void RespawnPlayer(session_t peer_id); void DeleteClient(session_t peer_id, ClientDeletionReason reason); void UpdateCrafting(RemotePlayer *player); bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what); @@ -706,11 +716,11 @@ private: MetricCounterPtr m_uptime_counter; MetricGaugePtr m_player_gauge; MetricGaugePtr m_timeofday_gauge; - // current server step lag MetricGaugePtr m_lag_gauge; - MetricCounterPtr m_aom_buffer_counter; + MetricCounterPtr m_aom_buffer_counter[2]; // [0] = rel, [1] = unrel MetricCounterPtr m_packet_recv_counter; MetricCounterPtr m_packet_recv_processed_counter; + MetricCounterPtr m_map_edit_event_counter; }; /* diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 82f6da231..ab4a9e3f2 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -117,13 +117,13 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) } } -void LuaEntitySAO::dispatchScriptDeactivate() +void LuaEntitySAO::dispatchScriptDeactivate(bool removal) { // Ensure that this is in fact a registered entity, // and that it isn't already gone. // The latter also prevents this from ever being called twice. if (m_registered && !isGone()) - m_env->getScriptIface()->luaentity_Deactivate(m_id); + m_env->getScriptIface()->luaentity_Deactivate(m_id, removal); } void LuaEntitySAO::step(float dtime, bool send_recommended) @@ -290,7 +290,7 @@ void LuaEntitySAO::getStaticData(std::string *result) const os<<serializeString32(m_init_state); } writeU16(os, m_hp); - writeV3F1000(os, m_velocity); + writeV3F1000(os, clampToF1000(m_velocity)); // yaw writeF1000(os, m_rotation.Y); @@ -337,19 +337,9 @@ u32 LuaEntitySAO::punch(v3f dir, if (result.did_punch) { setHP((s32)getHP() - result.damage, PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); - - // create message and add to list - sendPunchCommand(); } } - if (getHP() == 0 && !isGone()) { - clearParentAttachment(); - clearChildAttachments(); - m_env->getScriptIface()->luaentity_on_death(m_id, puncher); - markForRemoval(); - } - actionstream << puncher->getDescription() << " (id=" << puncher->getId() << ", hp=" << puncher->getHP() << ") punched " << getDescription() << " (id=" << m_id << ", hp=" << m_hp << @@ -402,6 +392,20 @@ std::string LuaEntitySAO::getDescription() void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { m_hp = rangelim(hp, 0, U16_MAX); + + sendPunchCommand(); + + if (m_hp == 0 && !isGone()) { + clearParentAttachment(); + clearChildAttachments(); + if (m_registered) { + ServerActiveObject *killer = nullptr; + if (reason.type == PlayerHPChangeReason::PLAYER_PUNCH) + killer = reason.object; + m_env->getScriptIface()->luaentity_on_death(m_id, killer); + } + markForRemoval(); + } } u16 LuaEntitySAO::getHP() const diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index 87b664a8b..1dc72b150 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -36,23 +36,30 @@ public: { } ~LuaEntitySAO(); + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; } ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } virtual void addedToEnvironment(u32 dtime_s); void step(float dtime, bool send_recommended); std::string getClientInitializationData(u16 protocol_version); + bool isStaticAllowed() const { return m_prop.static_save; } bool shouldUnload() const { return true; } void getStaticData(std::string *result) const; + u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, ServerActiveObject *puncher = nullptr, float time_from_last_punch = 1000000.0f, u16 initial_wear = 0); + void rightClick(ServerActiveObject *clicker); + void setPos(const v3f &pos); void moveTo(v3f pos, bool continuous); float getMinimumSavedMovement(); + std::string getDescription(); + void setHP(s32 hp, const PlayerHPChangeReason &reason); u16 getHP() const; @@ -73,9 +80,9 @@ public: bool collideWithObjects() const; protected: - void dispatchScriptDeactivate(); - virtual void onMarkedForDeactivation() { dispatchScriptDeactivate(); } - virtual void onMarkedForRemoval() { dispatchScriptDeactivate(); } + void dispatchScriptDeactivate(bool removal); + virtual void onMarkedForDeactivation() { dispatchScriptDeactivate(false); } + virtual void onMarkedForRemoval() { dispatchScriptDeactivate(true); } private: std::string getPropertyPacket(); diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 609d8c346..f302d4240 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_server.h" #include "content/subgames.h" #include "porting.h" -#include "util/metricsbackend.h" /** * Manage server mods @@ -35,18 +34,19 @@ with this program; if not, write to the Free Software Foundation, Inc., * Creates a ServerModManager which targets worldpath * @param worldpath */ -ServerModManager::ServerModManager(const std::string &worldpath) : - ModConfiguration(worldpath) +ServerModManager::ServerModManager(const std::string &worldpath): + configuration() { SubgameSpec gamespec = findWorldSubgame(worldpath); // Add all game mods and all world mods - addModsInPath(gamespec.gamemods_path); - addModsInPath(worldpath + DIR_DELIM + "worldmods"); + configuration.addGameMods(gamespec); + configuration.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); // Load normal mods std::string worldmt = worldpath + DIR_DELIM + "world.mt"; - addModsFromConfig(worldmt, gamespec.addon_mods_paths); + configuration.addModsFromConfig(worldmt, gamespec.addon_mods_paths); + configuration.checkConflictsAndDeps(); } // clang-format off @@ -55,12 +55,13 @@ void ServerModManager::loadMods(ServerScripting *script) { // Print mods infostream << "Server: Loading mods: "; - for (const ModSpec &mod : m_sorted_mods) { + for (const ModSpec &mod : configuration.getMods()) { infostream << mod.name << " "; } + infostream << std::endl; // Load and run "mod" scripts - for (const ModSpec &mod : m_sorted_mods) { + for (const ModSpec &mod : configuration.getMods()) { mod.checkAndLog(); std::string script_path = mod.path + DIR_DELIM + "init.lua"; @@ -77,24 +78,26 @@ void ServerModManager::loadMods(ServerScripting *script) // clang-format on const ModSpec *ServerModManager::getModSpec(const std::string &modname) const { - std::vector<ModSpec>::const_iterator it; - for (it = m_sorted_mods.begin(); it != m_sorted_mods.end(); ++it) { - const ModSpec &mod = *it; + for (const auto &mod : configuration.getMods()) { if (mod.name == modname) return &mod; } - return NULL; + + return nullptr; } void ServerModManager::getModNames(std::vector<std::string> &modlist) const { - for (const ModSpec &spec : m_sorted_mods) + for (const ModSpec &spec : configuration.getMods()) modlist.push_back(spec.name); } void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const { - for (auto it = m_sorted_mods.crbegin(); it != m_sorted_mods.crend(); it++) { + // Iterate mods in reverse load order: Media loading expects higher priority media files first + // and mods loading later should be able to override media of already loaded mods + const auto &mods = configuration.getMods(); + for (auto it = mods.crbegin(); it != mods.crend(); it++) { const ModSpec &spec = *it; fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures"); fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds"); diff --git a/src/server/mods.h b/src/server/mods.h index 8954bbf72..1d1b42d0f 100644 --- a/src/server/mods.h +++ b/src/server/mods.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "content/mods.h" +#include "content/mod_configuration.h" #include <memory> class MetricsBackend; @@ -31,8 +31,10 @@ class ServerScripting; * * All new calls to this class must be tested in test_servermodmanager.cpp */ -class ServerModManager : public ModConfiguration +class ServerModManager { + ModConfiguration configuration; + public: /** * Creates a ServerModManager which targets worldpath @@ -42,6 +44,23 @@ public: void loadMods(ServerScripting *script); const ModSpec *getModSpec(const std::string &modname) const; void getModNames(std::vector<std::string> &modlist) const; + + inline const std::vector<ModSpec> &getMods() const { + return configuration.getMods(); + } + + inline const std::vector<ModSpec> &getUnsatisfiedMods() const { + return configuration.getUnsatisfiedMods(); + } + + inline bool isConsistent() const { + return configuration.isConsistent(); + } + + inline void printUnsatisfiedModsError() const { + return configuration.printUnsatisfiedModsError(); + } + /** * Recursively gets all paths of mod folders that can contain media files. * diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index d076d5783..a58a0397f 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -319,7 +319,7 @@ std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const return os.str(); } -void PlayerSAO::setBasePosition(const v3f &position) +void PlayerSAO::setBasePosition(v3f position) { if (m_player && position != m_base_position) m_player->setDirty(true); @@ -344,7 +344,7 @@ void PlayerSAO::setPos(const v3f &pos) setBasePosition(pos); // Movement caused by this command is always valid - m_last_good_position = pos; + m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; m_env->getGameDef()->SendMovePlayer(m_peer_id); @@ -357,7 +357,7 @@ void PlayerSAO::moveTo(v3f pos, bool continuous) setBasePosition(pos); // Movement caused by this command is always valid - m_last_good_position = pos; + m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; m_env->getGameDef()->SendMovePlayer(m_peer_id); @@ -489,7 +489,7 @@ void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool fr m_hp = hp; m_env->getGameDef()->HandlePlayerHPChange(this, reason); } else if (from_client) - m_env->getGameDef()->SendPlayerHP(this); + m_env->getGameDef()->SendPlayerHP(this, true); } void PlayerSAO::setBreath(const u16 breath, bool send) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 96d8f7189..5f48cae67 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -72,24 +72,24 @@ public: PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, bool is_singleplayer); - ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; } - ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } - std::string getDescription(); + ActiveObjectType getType() const override { return ACTIVEOBJECT_TYPE_PLAYER; } + ActiveObjectType getSendType() const override { return ACTIVEOBJECT_TYPE_GENERIC; } + std::string getDescription() override; /* Active object <-> environment interface */ - void addedToEnvironment(u32 dtime_s); - void removingFromEnvironment(); - bool isStaticAllowed() const { return false; } - bool shouldUnload() const { return false; } - std::string getClientInitializationData(u16 protocol_version); - void getStaticData(std::string *result) const; - void step(float dtime, bool send_recommended); - void setBasePosition(const v3f &position); - void setPos(const v3f &pos); - void moveTo(v3f pos, bool continuous); + void addedToEnvironment(u32 dtime_s) override; + void removingFromEnvironment() override; + bool isStaticAllowed() const override { return false; } + bool shouldUnload() const override { return false; } + std::string getClientInitializationData(u16 protocol_version) override; + void getStaticData(std::string *result) const override; + void step(float dtime, bool send_recommended) override; + void setBasePosition(v3f position); + void setPos(const v3f &pos) override; + void moveTo(v3f pos, bool continuous) override; void setPlayerYaw(const float yaw); // Data should not be sent at player initialization void setPlayerYawAndSend(const float yaw); @@ -110,8 +110,8 @@ public: */ u32 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher, - float time_from_last_punch, u16 initial_wear = 0); - void rightClick(ServerActiveObject *clicker); + float time_from_last_punch, u16 initial_wear = 0) override; + void rightClick(ServerActiveObject *clicker) override; void setHP(s32 hp, const PlayerHPChangeReason &reason) override { return setHP(hp, reason, false); @@ -124,13 +124,13 @@ public: /* Inventory interface */ - Inventory *getInventory() const; - InventoryLocation getInventoryLocation() const; - void setInventoryModified() {} - std::string getWieldList() const { return "main"; } - u16 getWieldIndex() const; - ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const; - bool setWieldedItem(const ItemStack &item); + Inventory *getInventory() const override; + InventoryLocation getInventoryLocation() const override; + void setInventoryModified() override {} + std::string getWieldList() const override { return "main"; } + u16 getWieldIndex() const override; + ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const override; + bool setWieldedItem(const ItemStack &item) override; /* PlayerSAO-specific @@ -171,9 +171,9 @@ public: m_is_singleplayer = is_singleplayer; } - bool getCollisionBox(aabb3f *toset) const; - bool getSelectionBox(aabb3f *toset) const; - bool collideWithObjects() const { return true; } + bool getCollisionBox(aabb3f *toset) const override; + bool getSelectionBox(aabb3f *toset) const override; + bool collideWithObjects() const override { return true; } void finalize(RemotePlayer *player, const std::set<std::string> &privs); @@ -235,6 +235,7 @@ struct PlayerHPChangeReason enum Type : u8 { SET_HP, + SET_HP_MAX, // internal type to allow distinguishing hp reset and damage (for effects) PLAYER_PUNCH, FALL, NODE_DAMAGE, @@ -277,6 +278,7 @@ struct PlayerHPChangeReason { switch (type) { case PlayerHPChangeReason::SET_HP: + case PlayerHPChangeReason::SET_HP_MAX: return "set_hp"; case PlayerHPChangeReason::PLAYER_PUNCH: return "punch"; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index f3711652c..8989fb05f 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -258,23 +258,23 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) v3s16 pos; MapNode n; content_t c; - lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp); + bool pos_valid; // dummy, we know it's valid + auto it = getLBMsIntroducedAfter(stamp); for (; it != m_lbm_lookup.end(); ++it) { // Cache previous version to speedup lookup which has a very high performance // penalty on each call - content_t previous_c{}; - std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr; + content_t previous_c = CONTENT_IGNORE; + const std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr; for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) { - n = block->getNodeNoEx(pos); + n = block->getNodeNoCheck(pos, &pos_valid); c = n.getContent(); // If content_t are not matching perform an LBM lookup if (previous_c != c) { - lbm_list = (std::vector<LoadingBlockModifierDef *> *) - it->second.lookup(c); + lbm_list = it->second.lookup(c); previous_c = c; } @@ -378,10 +378,7 @@ void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players, /* Update m_list */ - m_list.clear(); - for (v3s16 p : newlist) { - m_list.insert(p); - } + m_list = std::move(newlist); } /* @@ -393,7 +390,7 @@ static std::random_device seed; ServerEnvironment::ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, Server *server, - const std::string &path_world): + const std::string &path_world, MetricsBackend *mb): Environment(server), m_map(map), m_script(scriptIface), @@ -460,6 +457,15 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_player_database = openPlayerDatabase(player_backend_name, path_world, conf); m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf); + + m_step_time_counter = mb->addCounter( + "minetest_env_step_time", "Time spent in environment step (in microseconds)"); + + m_active_block_gauge = mb->addGauge( + "minetest_env_active_blocks", "Number of active blocks"); + + m_active_object_gauge = mb->addGauge( + "minetest_env_active_objects", "Number of active objects"); } ServerEnvironment::~ServerEnvironment() @@ -552,10 +558,8 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { - for (RemotePlayer *player : m_players) { - m_server->DenyAccessVerCompliant(player->getPeerId(), - player->protocol_version, reason, str_reason, reconnect); - } + for (RemotePlayer *player : m_players) + m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); } void ServerEnvironment::saveLoadedPlayers(bool force) @@ -619,6 +623,9 @@ PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player, /* Add object to environment */ addActiveObject(playersao); + // Update active blocks asap so objects in those blocks appear on the client + m_force_update_active_blocks = true; + return playersao; } @@ -892,7 +899,7 @@ public: for (ActiveABM &aabm : *m_aabms[c]) { if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y)) continue; - + if (myrand() % aabm.chance != 0) continue; @@ -1285,6 +1292,8 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) void ServerEnvironment::step(float dtime) { ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG); + const auto start_time = porting::getTimeUs(); + /* Step time of day */ stepTimeOfDay(dtime); @@ -1323,13 +1332,16 @@ void ServerEnvironment::step(float dtime) /* Manage active block list */ - if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) { + if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) || + m_force_update_active_blocks) { ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG); + /* Get player block positions */ std::vector<PlayerSAO*> players; - for (RemotePlayer *player: m_players) { + players.reserve(m_players.size()); + for (RemotePlayer *player : m_players) { // Ignore disconnected players if (player->getPeerId() == PEER_ID_INEXISTENT) continue; @@ -1377,14 +1389,21 @@ void ServerEnvironment::step(float dtime) for (const v3s16 &p: blocks_added) { MapBlock *block = m_map->getBlockOrEmerge(p); if (!block) { - m_active_blocks.m_list.erase(p); - m_active_blocks.m_abm_list.erase(p); + // TODO: The blocks removed here will only be picked up again + // on the next cycle. To minimize the latency of objects being + // activated we could remember the blocks pending activating + // and activate them instantly as soon as they're loaded. + m_active_blocks.remove(p); continue; } activateBlock(block); } + + // Some blocks may be removed again by the code above so do this here + m_active_block_gauge->set(m_active_blocks.size()); } + m_force_update_active_blocks = false; /* Mess around in active blocks @@ -1483,6 +1502,8 @@ void ServerEnvironment::step(float dtime) */ m_script->environment_Step(dtime); + m_script->stepAsync(); + /* Step active objects */ @@ -1497,9 +1518,12 @@ void ServerEnvironment::step(float dtime) send_recommended = true; } - auto cb_state = [this, dtime, send_recommended] (ServerActiveObject *obj) { + u32 object_count = 0; + + auto cb_state = [&] (ServerActiveObject *obj) { if (obj->isGone()) return; + object_count++; // Step object obj->step(dtime, send_recommended); @@ -1507,6 +1531,8 @@ void ServerEnvironment::step(float dtime) obj->dumpAOMessagesToQueue(m_active_object_messages); }; m_ao_manager.step(dtime, cb_state); + + m_active_object_gauge->set(object_count); } /* @@ -1548,6 +1574,9 @@ void ServerEnvironment::step(float dtime) // Send outdated detached inventories m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true); + + const auto end_time = porting::getTimeUs(); + m_step_time_counter->increment(end_time - start_time); } ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos) diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 8733c2dd2..00184421e 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "server/activeobjectmgr.h" #include "util/numeric.h" +#include "util/metricsbackend.h" #include <set> #include <random> @@ -167,19 +168,27 @@ public: std::set<v3s16> &blocks_removed, std::set<v3s16> &blocks_added); - bool contains(v3s16 p){ + bool contains(v3s16 p) const { return (m_list.find(p) != m_list.end()); } - void clear(){ + auto size() const { + return m_list.size(); + } + + void clear() { m_list.clear(); } + void remove(v3s16 p) { + m_list.erase(p); + m_abm_list.erase(p); + } + std::set<v3s16> m_list; std::set<v3s16> m_abm_list; + // list of blocks that are always active, not modified by this class std::set<v3s16> m_forceloaded_list; - -private: }; /* @@ -198,7 +207,7 @@ class ServerEnvironment : public Environment { public: ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, - Server *server, const std::string &path_world); + Server *server, const std::string &path_world, MetricsBackend *mb); ~ServerEnvironment(); Map & getMap(); @@ -451,7 +460,8 @@ private: IntervalLimiter m_object_management_interval; // List of active blocks ActiveBlockList m_active_blocks; - IntervalLimiter m_active_blocks_management_interval; + bool m_force_update_active_blocks = false; + IntervalLimiter m_active_blocks_mgmt_interval; IntervalLimiter m_active_block_modifier_interval; IntervalLimiter m_active_blocks_nodemetadata_interval; // Whether the variables below have been read from file yet @@ -487,5 +497,10 @@ private: std::unordered_map<u32, float> m_particle_spawners; std::unordered_map<u32, u16> m_particle_spawner_attachments; + // Environment metrics + MetricCounterPtr m_step_time_counter; + MetricGaugePtr m_active_block_gauge; + MetricGaugePtr m_active_object_gauge; + ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data); }; diff --git a/src/settings.cpp b/src/settings.cpp index 0e44ee0bc..a0225bc2c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "exceptions.h" #include "threading/mutex_auto_lock.h" +#include "util/numeric.h" // rangelim #include "util/strfnd.h" #include <iostream> #include <fstream> @@ -534,6 +535,13 @@ float Settings::getFloat(const std::string &name) const } +float Settings::getFloat(const std::string &name, float min, float max) const +{ + float val = stof(get(name)); + return rangelim(val, min, max); +} + + u64 Settings::getU64(const std::string &name) const { std::string s = get(name); diff --git a/src/settings.h b/src/settings.h index 767d057f9..4b0787343 100644 --- a/src/settings.h +++ b/src/settings.h @@ -164,6 +164,7 @@ public: s32 getS32(const std::string &name) const; u64 getU64(const std::string &name) const; float getFloat(const std::string &name) const; + float getFloat(const std::string &name, float min, float max) const; v2f getV2F(const std::string &name) const; v3f getV3F(const std::string &name) const; u32 getFlagStr(const std::string &name, const FlagDesc *flagdesc, diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index ebb7ba9be..03bb564fb 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -1,34 +1,23 @@ // This file is automatically generated -// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files +// It contains 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/dlg_settings_advanced.lua fake_function() { gettext("Controls"); + gettext("General"); gettext("Build inside player"); gettext("If enabled, you can place blocks at the position (feet + eye level) where you stand.\nThis is helpful when working with nodeboxes in small areas."); - gettext("Flying"); - gettext("Player is able to fly without being affected by gravity.\nThis requires the \"fly\" privilege on the server."); - gettext("Pitch move mode"); - gettext("If enabled, makes move directions relative to the player's pitch when flying or swimming."); - gettext("Fast movement"); - gettext("Fast movement (via the \"Aux1\" key).\nThis requires the \"fast\" privilege on the server."); - 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 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"); gettext("Smooths rotation of camera in cinematic mode. 0 to disable."); - gettext("Invert mouse"); - gettext("Invert vertical mouse movement."); - gettext("Mouse sensitivity"); - gettext("Mouse sensitivity multiplier."); gettext("Aux1 key for climbing/descending"); gettext("If enabled, \"Aux1\" key instead of \"Sneak\" key is used for climbing down and\ndescending."); gettext("Double tap jump for fly"); gettext("Double-tapping the jump key toggles fly mode."); - gettext("Always fly and fast"); + gettext("Always fly fast"); gettext("If disabled, \"Aux1\" key is used to fly fast if both fly and fast mode are\nenabled."); gettext("Place repetition interval"); gettext("The time in seconds it takes between repeated node placements when holding\nthe place button."); @@ -36,179 +25,45 @@ fake_function() { gettext("Automatically jump up single-node obstacles."); gettext("Safe digging and placing"); gettext("Prevent digging and placing from repeating when holding the mouse buttons.\nEnable this when you dig or place too often by accident."); - gettext("Random input"); - gettext("Enable random user input (only used for testing)."); - gettext("Continuous forward"); - gettext("Continuous forward movement, toggled by autoforward key.\nPress the autoforward key again or the backwards movement to disable."); + gettext("Keyboard and Mouse"); + gettext("Invert mouse"); + gettext("Invert vertical mouse movement."); + gettext("Mouse sensitivity"); + gettext("Mouse sensitivity multiplier."); + gettext("Touchscreen"); gettext("Touch screen threshold"); gettext("The length in pixels it takes for touch screen interaction to start."); gettext("Fixed virtual joystick"); gettext("(Android) Fixes the position of virtual joystick.\nIf disabled, virtual joystick will center to first-touch's position."); gettext("Virtual joystick triggers Aux1 button"); gettext("(Android) Use virtual joystick to trigger \"Aux1\" button.\nIf enabled, virtual joystick will also tap \"Aux1\" button when out of main circle."); - gettext("Enable joysticks"); - gettext("Enable joysticks"); - gettext("Joystick ID"); - gettext("The identifier of the joystick to use"); - gettext("Joystick type"); - gettext("The type of joystick"); - gettext("Joystick button repetition interval"); - gettext("The time in seconds it takes between repeated events\nwhen holding down a joystick button combination."); - gettext("Joystick dead zone"); - gettext("The dead zone of the joystick"); - gettext("Joystick frustum sensitivity"); - gettext("The sensitivity of the joystick axes for moving the\nin-game 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"); - gettext("Key for moving the player backward.\nWill also disable autoforward, when active.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Left key"); - gettext("Key for moving the player left.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Right key"); - gettext("Key for moving the player right.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Jump key"); - gettext("Key for jumping.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Sneak key"); - gettext("Key for sneaking.\nAlso used for climbing down and descending in water if aux1_descends is disabled.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Dig key"); - gettext("Key for digging.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Place key"); - gettext("Key for placing.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Inventory key"); - gettext("Key for opening the inventory.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Aux1 key"); - gettext("Key for moving fast in fast mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Chat key"); - gettext("Key for opening the chat window.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Command key"); - gettext("Key for opening the chat window to type commands.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Command key"); - gettext("Key for opening the chat window to type local commands.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Range select key"); - gettext("Key for toggling unlimited view range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Fly key"); - gettext("Key for toggling flying.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Pitch move key"); - gettext("Key for toggling pitch move mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Fast key"); - 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("Hotbar next key"); - gettext("Key for selecting the next item in the hotbar.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar previous key"); - gettext("Key for selecting the previous item in the hotbar.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Mute key"); - gettext("Key for muting the game.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Inc. volume key"); - gettext("Key for increasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Dec. volume key"); - gettext("Key for decreasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Automatic forward key"); - gettext("Key for toggling autoforward.\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"); - gettext("Key for toggling display of minimap.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Screenshot"); - gettext("Key for taking screenshots.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Drop item key"); - gettext("Key for dropping the currently selected item.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("View zoom key"); - gettext("Key to use view zoom when possible.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 1 key"); - gettext("Key for selecting the first hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 2 key"); - gettext("Key for selecting the second hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 3 key"); - gettext("Key for selecting the third hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 4 key"); - gettext("Key for selecting the fourth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 5 key"); - gettext("Key for selecting the fifth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 6 key"); - gettext("Key for selecting the sixth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 7 key"); - gettext("Key for selecting the seventh hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 8 key"); - gettext("Key for selecting the eighth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 9 key"); - gettext("Key for selecting the ninth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 10 key"); - gettext("Key for selecting the tenth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 11 key"); - gettext("Key for selecting the 11th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 12 key"); - gettext("Key for selecting the 12th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 13 key"); - gettext("Key for selecting the 13th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 14 key"); - gettext("Key for selecting the 14th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 15 key"); - gettext("Key for selecting the 15th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 16 key"); - gettext("Key for selecting the 16th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 17 key"); - gettext("Key for selecting the 17th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 18 key"); - gettext("Key for selecting the 18th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 19 key"); - gettext("Key for selecting the 19th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 20 key"); - gettext("Key for selecting the 20th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 21 key"); - gettext("Key for selecting the 21st hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 22 key"); - gettext("Key for selecting the 22nd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 23 key"); - gettext("Key for selecting the 23rd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 24 key"); - gettext("Key for selecting the 24th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 25 key"); - gettext("Key for selecting the 25th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 26 key"); - gettext("Key for selecting the 26th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 27 key"); - gettext("Key for selecting the 27th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 28 key"); - gettext("Key for selecting the 28th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 29 key"); - gettext("Key for selecting the 29th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 30 key"); - gettext("Key for selecting the 30th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 31 key"); - gettext("Key for selecting the 31st hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Hotbar slot 32 key"); - gettext("Key for selecting the 32nd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("HUD toggle key"); - gettext("Key for toggling the display of the HUD.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Chat toggle key"); - gettext("Key for toggling the display of chat.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Large chat console key"); - gettext("Key for toggling the display of the large chat console.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Fog toggle key"); - gettext("Key for toggling the display of fog.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Camera update toggle key"); - 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"); - gettext("Key for toggling the display of the profiler. Used for development.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("Toggle camera mode key"); - gettext("Key for switching between first- and third-person camera.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("View range increase key"); - gettext("Key for increasing the viewing range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); - gettext("View range decrease key"); - gettext("Key for decreasing the viewing range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Graphics and Audio"); gettext("Graphics"); - gettext("In-Game"); - gettext("Basic"); - gettext("Show name tag backgrounds by default"); - gettext("Whether name tag backgrounds should be shown by default.\nMods may still set a background."); - gettext("VBO"); - gettext("Enable vertex buffer objects.\nThis should greatly improve graphics performance."); - gettext("Fog"); - gettext("Whether to fog out the end of the visible area."); + gettext("Screen"); + gettext("Screen width"); + gettext("Width component of the initial window size. Ignored in fullscreen mode."); + gettext("Screen height"); + gettext("Height component of the initial window size. Ignored in fullscreen mode."); + gettext("Autosave screen size"); + gettext("Save window size automatically when modified."); + gettext("Full screen"); + gettext("Fullscreen mode."); + gettext("Pause on lost window focus"); + gettext("Open the pause menu when the window's focus is lost. Does not pause if a formspec is\nopen."); + gettext("FPS"); + gettext("Maximum FPS"); + gettext("If FPS would go higher than this, limit it by sleeping\nto not waste CPU power for no benefit."); + gettext("VSync"); + gettext("Vertical screen synchronization."); + gettext("FPS when unfocused or paused"); + gettext("Maximum FPS when the window is not focused, or when the game is paused."); + gettext("Viewing range"); + gettext("View distance in nodes."); + gettext("Undersampling"); + gettext("Undersampling is similar to using a lower screen resolution, but it applies\nto the game world only, keeping the GUI intact.\nIt should give a significant performance boost at the cost of less detailed image.\nHigher values result in a less detailed image."); + gettext("Graphics Effects"); + gettext("Opaque liquids"); + gettext("Makes all liquids opaque"); gettext("Leaves style"); gettext("Leaves style:\n- Fancy: all faces visible\n- Simple: only outer faces, if defined special_tiles are used\n- Opaque: disable transparency"); gettext("Connect glass"); @@ -217,15 +72,62 @@ fake_function() { gettext("Enable smooth lighting with simple ambient occlusion.\nDisable for speed or for different looks."); gettext("Tradeoffs for performance"); gettext("Enables tradeoffs that reduce CPU load or increase rendering performance\nat the expense of minor visual glitches that do not impact game playability."); + gettext("Digging particles"); + gettext("Adds particles when digging a node."); + gettext("3d"); + gettext("3D mode"); + gettext("3D support.\nCurrently supported:\n- none: no 3d output.\n- anaglyph: cyan/magenta color 3d.\n- interlaced: odd/even line based polarisation screen support.\n- topbottom: split screen top/bottom.\n- sidebyside: split screen side by side.\n- crossview: Cross-eyed 3d\n- pageflip: quadbuffer based 3d.\nNote that the interlaced mode requires shaders to be enabled."); + gettext("3D mode parallax strength"); + gettext("Strength of 3D mode parallax."); + gettext("Bobbing"); + gettext("Arm inertia"); + gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); + gettext("View bobbing factor"); + gettext("Enable view bobbing and amount of view bobbing.\nFor example: 0 for no view bobbing; 1.0 for normal; 2.0 for double."); + gettext("Fall bobbing factor"); + gettext("Multiplier for fall bobbing.\nFor example: 0 for no view bobbing; 1.0 for normal; 2.0 for double."); + gettext("Camera"); + gettext("Near plane"); + gettext("Camera 'near clipping plane' distance in nodes, between 0 and 0.25\nOnly works on GLES platforms. Most users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets."); + gettext("Field of view"); + gettext("Field of view in degrees."); + gettext("Light curve gamma"); + gettext("Alters the light curve by applying 'gamma correction' to it.\nHigher values make middle and lower light levels brighter.\nValue '1.0' leaves the light curve unaltered.\nThis only has significant effect on daylight and artificial\nlight, it has very little effect on natural night light."); + gettext("Ambient occlusion gamma"); + 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("Screenshots"); + gettext("Screenshot folder"); + gettext("Path to save screenshots at. Can be an absolute or relative path.\nThe folder will be created if it doesn't already exist."); + gettext("Screenshot format"); + gettext("Format of screenshots."); + gettext("Screenshot quality"); + gettext("Screenshot quality. Only used for JPEG format.\n1 means worst quality; 100 means best quality.\nUse 0 for default quality."); + gettext("Node and Entity Highlighting"); + gettext("Node highlighting"); + gettext("Method used to highlight selected object."); + gettext("Show entity selection boxes"); + gettext("Show entity selection boxes\nA restart is required after changing this."); + gettext("Selection box color"); + gettext("Selection box border color (R,G,B)."); + gettext("Selection box width"); + gettext("Width of the selection box lines around nodes."); + gettext("Crosshair color"); + gettext("Crosshair color (R,G,B).\nAlso controls the object crosshair color"); + gettext("Crosshair alpha"); + gettext("Crosshair alpha (opaqueness, between 0 and 255).\nThis also applies to the object crosshair."); + gettext("Fog"); + gettext("Fog"); + gettext("Whether to fog out the end of the visible area."); + gettext("Colored fog"); + gettext("Make fog and sky colors depend on daytime (dawn/sunset) and view direction."); + gettext("Fog start"); + gettext("Fraction of the visible distance at which fog starts to be rendered"); + gettext("Clouds"); gettext("Clouds"); gettext("Clouds are a client side effect."); gettext("3D clouds"); gettext("Use 3D cloud look instead of flat."); - gettext("Node highlighting"); - gettext("Method used to highlight selected object."); - gettext("Digging particles"); - gettext("Adds particles when digging a node."); - gettext("Filtering"); + gettext("Filtering and Antialiasing"); gettext("Mipmapping"); gettext("Use mipmapping to scale textures. May slightly increase performance,\nespecially when using a high resolution texture pack.\nGamma correct downscaling is not supported."); gettext("Anisotropic filtering"); @@ -240,17 +142,17 @@ fake_function() { gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. This setting is ONLY applied if\nbilinear/trilinear/anisotropic filtering is enabled.\nThis is also used as the base node texture size for world-aligned\ntexture autoscaling."); gettext("FSAA"); gettext("Use multi-sample antialiasing (MSAA) to smooth out block edges.\nThis algorithm smooths out the 3D viewport while keeping the image sharp,\nbut it doesn't affect the insides of textures\n(which is especially noticeable with transparent textures).\nVisible spaces appear between nodes when shaders are disabled.\nIf set to 0, MSAA is disabled.\nA restart is required after changing this option."); - gettext("Undersampling"); - gettext("Undersampling is similar to using a lower screen resolution, but it applies\nto the game world only, keeping the GUI intact.\nIt should give a significant performance boost at the cost of less detailed image.\nHigher values result in a less detailed image."); gettext("Shaders"); gettext("Shaders"); gettext("Shaders allow advanced visual effects and may increase performance on some video\ncards.\nThis only works with the OpenGL video backend."); - gettext("Shader path"); - gettext("Path to shader directory. If no path is defined, default location will be used."); gettext("Tone Mapping"); gettext("Filmic tone mapping"); gettext("Enables Hable's 'Uncharted 2' filmic tone mapping.\nSimulates the tone curve of photographic film and how this approximates the\nappearance of high dynamic range images. Mid-range contrast is slightly\nenhanced, highlights and shadows are gradually compressed."); gettext("Waving Nodes"); + gettext("Waving leaves"); + gettext("Set to true to enable waving leaves.\nRequires shaders to be enabled."); + gettext("Waving plants"); + gettext("Set to true to enable waving plants.\nRequires shaders to be enabled."); gettext("Waving liquids"); gettext("Set to true to enable waving liquids (like water).\nRequires shaders to be enabled."); gettext("Waving liquids wave height"); @@ -259,15 +161,11 @@ fake_function() { gettext("Length of liquid waves.\nRequires waving liquids to be enabled."); gettext("Waving liquids wave speed"); gettext("How fast liquid waves will move. Higher = faster.\nIf negative, liquid waves will move backwards.\nRequires waving liquids to be enabled."); - gettext("Waving leaves"); - gettext("Set to true to enable waving leaves.\nRequires shaders to be enabled."); - gettext("Waving plants"); - gettext("Set to true to enable waving plants.\nRequires shaders to be enabled."); gettext("Dynamic shadows"); gettext("Dynamic shadows"); gettext("Set to true to enable Shadow Mapping.\nRequires shaders to be enabled."); - gettext("Shadow strength"); - gettext("Set the shadow strength.\nLower value means lighter shadows, higher value means darker shadows."); + gettext("Shadow strength gamma"); + gettext("Set the shadow strength gamma.\nAdjusts the intensity of in-game dynamic shadows.\nLower value means lighter shadows, higher value means darker shadows."); gettext("Shadow map max distance in nodes to render shadows"); gettext("Maximum distance to render shadows."); gettext("Shadow map texture size"); @@ -283,123 +181,26 @@ fake_function() { gettext("Map shadows update frames"); gettext("Spread a complete update of shadow map over given amount of frames.\nHigher values might make shadows laggy, lower values\nwill consume more resources.\nMinimum value: 1; maximum value: 16"); gettext("Soft shadow radius"); - gettext("Set the soft shadow radius size.\nLower values mean sharper shadows, bigger values mean softer shadows.\nMinimum value: 1.0; maximum value: 10.0"); + gettext("Set the soft shadow radius size.\nLower values mean sharper shadows, bigger values mean softer shadows.\nMinimum value: 1.0; maximum value: 15.0"); gettext("Sky Body Orbit Tilt"); gettext("Set the tilt of Sun/Moon orbit in degrees.\nValue of 0 means no tilt / vertical orbit.\nMinimum value: 0.0; maximum value: 60.0"); - gettext("Advanced"); - gettext("Arm inertia"); - gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); - gettext("Maximum FPS"); - gettext("If FPS would go higher than this, limit it by sleeping\nto not waste CPU power for no benefit."); - gettext("FPS when unfocused or paused"); - gettext("Maximum FPS when the window is not focused, or when the game is paused."); - gettext("Pause on lost window focus"); - gettext("Open the pause menu when the window's focus is lost. Does not pause if a formspec is\nopen."); - gettext("Viewing range"); - gettext("View distance in nodes."); - gettext("Near plane"); - gettext("Camera 'near clipping plane' distance in nodes, between 0 and 0.25\nOnly works on GLES platforms. Most users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets."); - gettext("Screen width"); - gettext("Width component of the initial window size. Ignored in fullscreen mode."); - gettext("Screen height"); - gettext("Height component of the initial window size. Ignored in fullscreen mode."); - gettext("Autosave screen size"); - gettext("Save window size automatically when modified."); - gettext("Full screen"); - gettext("Fullscreen mode."); - gettext("VSync"); - gettext("Vertical screen synchronization."); - gettext("Field of view"); - gettext("Field of view in degrees."); - gettext("Light curve gamma"); - gettext("Alters the light curve by applying 'gamma correction' to it.\nHigher values make middle and lower light levels brighter.\nValue '1.0' leaves the light curve unaltered.\nThis only has significant effect on daylight and artificial\nlight, it has very little effect on natural night light."); - gettext("Light curve low gradient"); - gettext("Gradient of light curve at minimum light level.\nControls the contrast of the lowest light levels."); - gettext("Light curve high gradient"); - gettext("Gradient of light curve at maximum light level.\nControls the contrast of the highest light levels."); - gettext("Light curve boost"); - gettext("Strength of light curve boost.\nThe 3 'boost' parameters define a range of the light\ncurve that is boosted in brightness."); - gettext("Light curve boost center"); - gettext("Center of light curve boost range.\nWhere 0.0 is minimum light level, 1.0 is maximum light level."); - gettext("Light curve boost spread"); - gettext("Spread of light curve boost range.\nControls the width of the range to be boosted.\nStandard deviation of the light curve boost Gaussian."); - gettext("Texture path"); - gettext("Path to texture directory. All textures are first searched from here."); - gettext("Video driver"); - gettext("The rendering back-end.\nA restart is required after changing this.\nNote: On Android, stick with OGLES1 if unsure! App may fail to start otherwise.\nOn other platforms, OpenGL is recommended.\nShaders are supported by OpenGL (desktop only) and OGLES2 (experimental)"); - gettext("Cloud radius"); - gettext("Radius of cloud area stated in number of 64 node cloud squares.\nValues larger than 26 will start to produce sharp cutoffs at cloud area corners."); - gettext("View bobbing factor"); - gettext("Enable view bobbing and amount of view bobbing.\nFor example: 0 for no view bobbing; 1.0 for normal; 2.0 for double."); - gettext("Fall bobbing factor"); - gettext("Multiplier for fall bobbing.\nFor example: 0 for no view bobbing; 1.0 for normal; 2.0 for double."); - gettext("3D mode"); - gettext("3D support.\nCurrently supported:\n- none: no 3d output.\n- anaglyph: cyan/magenta color 3d.\n- interlaced: odd/even line based polarisation screen support.\n- topbottom: split screen top/bottom.\n- sidebyside: split screen side by side.\n- crossview: Cross-eyed 3d\n- pageflip: quadbuffer based 3d.\nNote that the interlaced mode requires shaders to be enabled."); - gettext("3D mode parallax strength"); - gettext("Strength of 3D mode parallax."); - gettext("Console height"); - gettext("In-game chat console height, between 0.1 (10%) and 1.0 (100%)."); - gettext("Console color"); - gettext("In-game chat console background color (R,G,B)."); - gettext("Console alpha"); - gettext("In-game chat console background alpha (opaqueness, between 0 and 255)."); + gettext("Audio"); + gettext("Volume"); + gettext("Volume of all sounds.\nRequires the sound system to be enabled."); + gettext("Mute sound"); + gettext("Whether to mute sounds. You can unmute sounds at any time, unless the\nsound system is disabled (enable_sound=false).\nIn-game, you can toggle the mute state with the mute key or by using the\npause menu."); + gettext("User Interfaces"); + gettext("Language"); + gettext("Set the language. Leave empty to use the system language.\nA restart is required after changing this."); + gettext("GUIs"); + gettext("GUI scaling"); + gettext("Scale GUI by a user specified value.\nUse a nearest-neighbor-anti-alias filter to scale the GUI.\nThis will smooth over some of the rough edges, and blend\npixels when scaling down, at the cost of blurring some\nedge pixels when images are scaled by non-integer sizes."); + gettext("Inventory items animations"); + gettext("Enables animation of inventory items."); gettext("Formspec Full-Screen Background Opacity"); gettext("Formspec full-screen background opacity (between 0 and 255)."); gettext("Formspec Full-Screen Background Color"); gettext("Formspec full-screen background color (R,G,B)."); - gettext("Formspec Default Background Opacity"); - gettext("Formspec default background opacity (between 0 and 255)."); - gettext("Formspec Default Background Color"); - gettext("Formspec default background color (R,G,B)."); - gettext("Selection box color"); - gettext("Selection box border color (R,G,B)."); - gettext("Selection box width"); - gettext("Width of the selection box lines around nodes."); - gettext("Crosshair color"); - gettext("Crosshair color (R,G,B).\nAlso controls the object crosshair color"); - gettext("Crosshair alpha"); - gettext("Crosshair alpha (opaqueness, between 0 and 255).\nThis also applies to the object crosshair."); - gettext("Recent Chat Messages"); - gettext("Maximum number of recent chat messages to show"); - gettext("Desynchronize block animation"); - gettext("Whether node texture animations should be desynchronized per mapblock."); - gettext("Maximum hotbar width"); - gettext("Maximum proportion of current window to be used for hotbar.\nUseful if there's something to be displayed right or left of hotbar."); - gettext("HUD scale factor"); - gettext("Modifies the size of the HUD elements."); - gettext("Mesh cache"); - gettext("Enables caching of facedir rotated meshes."); - gettext("Mapblock mesh generation delay"); - gettext("Delay between mesh updates on the client in ms. Increasing this will slow\ndown the rate of mesh updates, thus reducing jitter on slower clients."); - gettext("Mapblock mesh generator's MapBlock cache size in MB"); - gettext("Size of the MapBlock cache of the mesh generator. Increasing this will\nincrease the cache hit %, reducing the data being copied from the main\nthread, thus reducing jitter."); - gettext("Minimap"); - gettext("Enables minimap."); - gettext("Round minimap"); - gettext("Shape of the minimap. Enabled = round, disabled = square."); - gettext("Minimap scan height"); - gettext("True = 256\nFalse = 128\nUsable to make minimap smoother on slower machines."); - gettext("Colored fog"); - gettext("Make fog and sky colors depend on daytime (dawn/sunset) and view direction."); - gettext("Ambient occlusion gamma"); - 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("Opaque liquids"); - gettext("Makes all liquids opaque"); - gettext("World-aligned textures mode"); - gettext("Textures on a node may be aligned either to the node or to the world.\nThe former mode suits better things like machines, furniture, etc., while\nthe latter makes stairs and microblocks fit surroundings better.\nHowever, as this possibility is new, thus may not be used by older servers,\nthis option allows enforcing it for certain node types. Note though that\nthat is considered EXPERIMENTAL and may not work properly."); - gettext("Autoscaling mode"); - gettext("World-aligned textures may be scaled to span several nodes. However,\nthe server may not send the scale you want, especially if you use\na specially-designed texture pack; with this option, the client tries\nto determine the scale automatically basing on the texture size.\nSee also texture_min_size.\nWarning: This option is EXPERIMENTAL!"); - gettext("Show entity selection boxes"); - gettext("Show entity selection boxes\nA restart is required after changing this."); - gettext("Menus"); - gettext("Clouds in menu"); - gettext("Use a cloud animation for the main menu background."); - gettext("GUI scaling"); - gettext("Scale GUI by a user specified value.\nUse a nearest-neighbor-anti-alias filter to scale the GUI.\nThis will smooth over some of the rough edges, and blend\npixels when scaling down, at the cost of blurring some\nedge pixels when images are scaled by non-integer sizes."); 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"); @@ -408,88 +209,49 @@ fake_function() { gettext("Delay showing tooltips, stated in milliseconds."); gettext("Append item name"); gettext("Append item name to tooltip."); - gettext("Font bold by default"); - gettext("Font italic by default"); - gettext("Font shadow"); - gettext("Shadow offset (in pixels) of the default font. If 0, then shadow will not be drawn."); - gettext("Font shadow alpha"); - gettext("Opaqueness (alpha) of the shadow behind the default font, between 0 and 255."); - gettext("Font size"); - gettext("Font size of the default font where 1 unit = 1 pixel at 96 DPI"); - gettext("Font size divisible by"); - gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); - gettext("Regular font path"); - gettext("Path to the default font. Must be a TrueType font.\nThe fallback font will be used if the font cannot be loaded."); - gettext("Bold font path"); - gettext("Italic font path"); - gettext("Bold and italic font path"); - gettext("Monospace font size"); - gettext("Font size of the monospace font where 1 unit = 1 pixel at 96 DPI"); - gettext("Monospace font size divisible by"); - gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); - gettext("Monospace font path"); - gettext("Path to the monospace font. Must be a TrueType font.\nThis font is used for e.g. the console and profiler screen."); - gettext("Bold monospace font path"); - gettext("Italic monospace font path"); - gettext("Bold and italic monospace font path"); - gettext("Fallback font path"); - gettext("Path of the fallback font. Must be a TrueType font.\nThis font will be used for certain languages or if the default font is unavailable."); - gettext("Chat font size"); - gettext("Font size of the recent chat text and chat prompt in point (pt).\nValue 0 will use the default font size."); - gettext("Screenshot folder"); - gettext("Path to save screenshots at. Can be an absolute or relative path.\nThe folder will be created if it doesn't already exist."); - gettext("Screenshot format"); - gettext("Format of screenshots."); - gettext("Screenshot quality"); - gettext("Screenshot quality. Only used for JPEG format.\n1 means worst quality; 100 means best quality.\nUse 0 for default quality."); - gettext("Advanced"); - gettext("DPI"); - gettext("Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens."); - gettext("Display Density Scaling Factor"); - gettext("Adjust the detected display density, used for scaling UI elements."); - gettext("Enable console window"); - gettext("Windows systems only: Start Minetest with the command line window in the background.\nContains the same information as the file debug.txt (default name)."); - gettext("Sound"); - gettext("Sound"); - gettext("Enables the sound system.\nIf disabled, this completely disables all sounds everywhere and the in-game\nsound controls will be non-functional.\nChanging this setting requires a restart."); - gettext("Volume"); - gettext("Volume of all sounds.\nRequires the sound system to be enabled."); - gettext("Mute sound"); - gettext("Whether to mute sounds. You can unmute sounds at any time, unless the\nsound system is disabled (enable_sound=false).\nIn-game, you can toggle the mute state with the mute key or by using the\npause menu."); - gettext("Client"); + gettext("Clouds in menu"); + gettext("Use a cloud animation for the main menu background."); + gettext("HUD"); + gettext("HUD scaling"); + gettext("Modifies the size of the HUD elements."); + gettext("Show name tag backgrounds by default"); + gettext("Whether name tag backgrounds should be shown by default.\nMods may still set a background."); + gettext("Chat"); + gettext("Recent Chat Messages"); + gettext("Maximum number of recent chat messages to show"); + gettext("Console height"); + gettext("In-game chat console height, between 0.1 (10%) and 1.0 (100%)."); + gettext("Console color"); + gettext("In-game chat console background color (R,G,B)."); + gettext("Console alpha"); + gettext("In-game chat console background alpha (opaqueness, between 0 and 255)."); + gettext("Maximum hotbar width"); + gettext("Maximum proportion of current window to be used for hotbar.\nUseful if there's something to be displayed right or left of hotbar."); gettext("Chat weblinks"); gettext("Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output."); gettext("Weblink color"); gettext("Optional override for chat weblink color."); - gettext("Network"); - gettext("Server address"); - gettext("Address to connect to.\nLeave this blank to start a local server.\nNote that the address field in the main menu overrides this setting."); - gettext("Remote port"); - gettext("Port to connect to (UDP).\nNote that the port field in the main menu overrides this setting."); - gettext("Prometheus listener address"); - gettext("Prometheus listener address.\nIf Minetest is compiled with ENABLE_PROMETHEUS option enabled,\nenable metrics listener for Prometheus on that address.\nMetrics can be fetched on http://127.0.0.1:30000/metrics"); + gettext("Chat font size"); + gettext("Font size of the recent chat text and chat prompt in point (pt).\nValue 0 will use the default font size."); + gettext("Content Repository"); + gettext("ContentDB URL"); + gettext("The URL for the content repository"); + gettext("ContentDB Flag Blacklist"); + gettext("Comma-separated list of flags to hide in the content repository.\n\"nonfree\" can be used to hide packages which do not qualify as 'free software',\nas defined by the Free Software Foundation.\nYou can also specify content ratings.\nThese flags are independent from Minetest versions,\nso see a full list at https://content.minetest.net/help/content_flags/"); + gettext("ContentDB Max Concurrent Downloads"); + gettext("Maximum number of concurrent downloads. Downloads exceeding this limit will be queued.\nThis should be lower than curl_parallel_limit."); + gettext("Client and Server"); + gettext("Client"); gettext("Saving map received from server"); gettext("Save the map received by the client on disk."); - gettext("Connect to external media server"); - gettext("Enable usage of remote media server (if provided by server).\nRemote servers offer a significantly faster way to download media (e.g. textures)\nwhen connecting to the server."); - gettext("Client modding"); - gettext("Enable Lua modding support on client.\nThis support is experimental and API can change."); gettext("Serverlist URL"); gettext("URL to the server list displayed in the Multiplayer Tab."); - gettext("Serverlist file"); - gettext("File in client/serverlist/ that contains your favorite servers displayed in the\nMultiplayer Tab."); - gettext("Maximum size of the out chat queue"); - gettext("Maximum size of the out chat queue.\n0 to disable queueing and -1 to make the queue size unlimited."); - gettext("Enable register confirmation"); - gettext("Enable register confirmation when connecting to server.\nIf disabled, new account will be registered automatically."); - gettext("Advanced"); - gettext("Mapblock unload timeout"); - gettext("Timeout for client to remove unused map data from memory."); - gettext("Mapblock limit"); - gettext("Maximum number of mapblocks for client to be kept in memory.\nSet to -1 for unlimited amount."); - gettext("Show debug info"); - gettext("Whether to show the client debug info (has the same effect as hitting F5)."); - gettext("Server / Singleplayer"); + gettext("Enable split login/register"); + gettext("If enabled, account registration is separate from login in the UI.\nIf disabled, new accounts will be registered automatically when logging in."); + gettext("Server"); + gettext("Admin 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."); + gettext("Serverlist and MOTD"); gettext("Server name"); gettext("Name of the server, to be displayed when players join and in the serverlist."); gettext("Server description"); @@ -502,9 +264,13 @@ fake_function() { gettext("Automatically report to the serverlist."); gettext("Serverlist URL"); gettext("Announce to this serverlist."); - gettext("Strip color codes"); - gettext("Remove color codes from incoming chat messages\nUse this to stop players from being able to use color in their messages"); - gettext("Network"); + gettext("Message of the day"); + gettext("Message of the day displayed to players connecting."); + gettext("Maximum users"); + gettext("Maximum number of players that can be connected simultaneously."); + gettext("Static spawnpoint"); + gettext("If this is set, players will always (re)spawn at the given position."); + gettext("Networking"); gettext("Server port"); gettext("Network port to listen (UDP).\nThis value will be overridden when starting from the main menu."); gettext("Bind address"); @@ -515,88 +281,42 @@ fake_function() { gettext("Specifies URL from which client fetches media instead of using UDP.\n$filename should be accessible from $remote_media$filename via cURL\n(obviously, remote_media should end with a slash).\nFiles that are not present will be fetched the usual way."); gettext("IPv6 server"); gettext("Enable/disable running an IPv6 server.\nIgnored if bind_address is set.\nNeeds enable_ipv6 to be enabled."); - gettext("Advanced"); - gettext("Maximum simultaneous block sends per client"); - gettext("Maximum number of blocks that are simultaneously sent per client.\nThe maximum total count is calculated dynamically:\nmax_total = ceil((#clients + max_users) * per_client / 4)"); - 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."); - gettext("Map Compression Level for Network Transfer"); - gettext("Compression level to use when sending mapblocks to the client.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); - gettext("Game"); - gettext("Default game"); - gettext("Default game when creating a new world.\nThis will be overridden when creating a world from the main menu."); - gettext("Message of the day"); - gettext("Message of the day displayed to players connecting."); - gettext("Maximum users"); - gettext("Maximum number of players that can be connected simultaneously."); - gettext("Map directory"); - gettext("World directory (everything in the world is stored here).\nNot needed if starting from the main menu."); - gettext("Item entity TTL"); - gettext("Time in seconds for item entity (dropped items) to live.\nSetting it to -1 disables the feature."); - gettext("Default stack size"); - gettext("Specifies the default stack size of nodes, items and tools.\nNote that mods or games may explicitly set a stack for certain (or all) items."); - gettext("Damage"); - gettext("Enable players getting damage and dying."); - gettext("Creative"); - gettext("Enable creative mode for all players"); - gettext("Fixed map seed"); - gettext("A chosen map seed for a new map, leave empty for random.\nWill be overridden when creating a new world in the main menu."); + gettext("Server Security"); gettext("Default password"); gettext("New users need to input this password."); + gettext("Disallow empty passwords"); + gettext("If enabled, players cannot join without a password or change theirs to an empty password."); gettext("Default privileges"); gettext("The privileges that new users automatically get.\nSee /privs in game for a full list on your server and mod configuration."); gettext("Basic privileges"); gettext("Privileges that players with basic_privs can grant"); - gettext("Unlimited player transfer distance"); - gettext("Whether players are shown to clients without any range limit.\nDeprecated, use the setting player_transfer_distance instead."); - gettext("Player transfer distance"); - gettext("Defines the maximal player transfer distance in blocks (0 = unlimited)."); - gettext("Player versus player"); - gettext("Whether to allow players to damage and kill each other."); - gettext("Mod channels"); - gettext("Enable mod channels support."); - gettext("Static spawnpoint"); - gettext("If this is set, players will always (re)spawn at the given position."); - gettext("Disallow empty passwords"); - gettext("If enabled, new players cannot join with an empty password."); gettext("Disable anticheat"); gettext("If enabled, disable cheat prevention in multiplayer."); gettext("Rollback recording"); gettext("If enabled, actions are recorded for rollback.\nThis option is only read when server starts."); - gettext("Chat message format"); - gettext("Format of player chat messages. The following strings are valid placeholders:\n@name, @message, @timestamp (optional)"); - gettext("Chat command time message threshold"); - gettext("If the execution of a chat command takes longer than this specified time in\nseconds, add the time information to the chat command message"); - gettext("Shutdown message"); - gettext("A message to be displayed to all clients when the server shuts down."); - gettext("Crash message"); - gettext("A message to be displayed to all clients when the server crashes."); - gettext("Ask to reconnect after crash"); - gettext("Whether to ask clients to reconnect after a (Lua) crash.\nSet this to true if your server is set up to restart automatically."); - gettext("Active object send range"); - gettext("From how far clients know about objects, stated in mapblocks (16 nodes).\n\nSetting this larger than active_block_range will also cause the server\nto maintain active objects up to this distance in the direction the\nplayer is looking. (This can avoid mobs suddenly disappearing from view)"); - gettext("Active block range"); - gettext("The radius of the volume of blocks around every player that is subject to the\nactive block stuff, stated in mapblocks (16 nodes).\nIn active blocks objects are loaded and ABMs run.\nThis is also the minimum range in which active objects (mobs) are maintained.\nThis should be configured together with active_object_send_range_blocks."); - gettext("Max block send distance"); - gettext("From how far blocks are sent to clients, stated in mapblocks (16 nodes)."); - gettext("Maximum forceloaded blocks"); - gettext("Maximum number of forceloaded mapblocks."); - gettext("Time send interval"); - gettext("Interval of sending time of day to clients."); - gettext("Time speed"); - gettext("Controls length of day/night cycle.\nExamples:\n72 = 20min, 360 = 4min, 1 = 24hour, 0 = day/night/whatever stays unchanged."); - gettext("World start time"); - gettext("Time of day when a new world is started, in millihours (0-23999)."); - gettext("Map save interval"); - gettext("Interval of saving important changes in the world, stated in seconds."); + gettext("Client-side Modding"); + gettext("Client side modding restrictions"); + gettext("Restricts the access of certain client-side functions on servers.\nCombine the byteflags below to restrict client-side features, or set to 0\nfor no restrictions:\nLOAD_CLIENT_MODS: 1 (disable loading client-provided mods)\nCHAT_MESSAGES: 2 (disable send_chat_message call client-side)\nREAD_ITEMDEFS: 4 (disable get_item_def call client-side)\nREAD_NODEDEFS: 8 (disable get_node_def call client-side)\nLOOKUP_NODES_LIMIT: 16 (limits get_node call client-side to\ncsm_restriction_noderange)\nREAD_PLAYERINFO: 32 (disable get_player_names call client-side)"); + gettext("Client side node lookup range restriction"); + gettext("If the CSM restriction for node range is enabled, get_node calls are limited\nto this distance from the player to the node."); + gettext("Chat"); + gettext("Strip color codes"); + gettext("Remove color codes from incoming chat messages\nUse this to stop players from being able to use color in their messages"); gettext("Chat message max length"); - gettext("Set the maximum character length of a chat message sent by clients."); + gettext("Set the maximum length of a chat message (in characters) sent by clients."); gettext("Chat message count limit"); gettext("Amount of messages a player may send per 10 seconds."); gettext("Chat message kick threshold"); gettext("Kick players who sent more than X messages per 10 seconds."); + gettext("Server Gameplay"); + gettext("Time speed"); + gettext("Controls length of day/night cycle.\nExamples:\n72 = 20min, 360 = 4min, 1 = 24hour, 0 = day/night/whatever stays unchanged."); + gettext("World start time"); + gettext("Time of day when a new world is started, in millihours (0-23999)."); + gettext("Item entity TTL"); + gettext("Time in seconds for item entity (dropped items) to live.\nSetting it to -1 disables the feature."); + gettext("Default stack size"); + gettext("Specifies the default stack size of nodes, items and tools.\nNote that mods or games may explicitly set a stack for certain (or all) items."); gettext("Physics"); gettext("Default acceleration"); gettext("Horizontal and vertical acceleration on ground or when climbing,\nin nodes per second per second."); @@ -615,108 +335,16 @@ fake_function() { gettext("Jumping speed"); gettext("Initial vertical speed when jumping, in nodes per second."); gettext("Liquid fluidity"); - gettext("Decrease this to increase liquid resistance to movement."); + gettext("How much you are slowed down when moving inside a liquid.\nDecrease this to increase liquid resistance to movement."); gettext("Liquid fluidity smoothing"); gettext("Maximum liquid resistance. Controls deceleration when entering liquid at\nhigh speed."); gettext("Liquid sinking"); - gettext("Controls sinking speed in liquid."); + gettext("Controls sinking speed in liquid when idling. Negative values will cause\nyou to rise instead."); gettext("Gravity"); gettext("Acceleration of gravity, in nodes per second per second."); - gettext("Advanced"); - gettext("Deprecated Lua API handling"); - gettext("Handling for deprecated Lua API calls:\n- none: Do not log deprecated calls\n- log: mimic and log backtrace of deprecated call (default).\n- error: abort on usage of deprecated call (suggested for mod 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("Maximum objects per block"); - gettext("Maximum number of statically stored objects in a block."); - gettext("Synchronous SQLite"); - gettext("See https://www.sqlite.org/pragma.html#pragma_synchronous"); - gettext("Map Compression Level for Disk Storage"); - gettext("Compression level to use when saving mapblocks to disk.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); - gettext("Dedicated server step"); - gettext("Length of a server tick and the interval at which objects are generally updated over\nnetwork."); - gettext("Active block management interval"); - gettext("Length of time between active block management cycles"); - gettext("ABM interval"); - gettext("Length of time between Active Block Modifier (ABM) execution cycles"); - gettext("ABM time budget"); - gettext("The time budget allowed for ABMs to execute on each step\n(as a fraction of the ABM Interval)"); - gettext("NodeTimer interval"); - gettext("Length of time between NodeTimer execution cycles"); - gettext("Ignore world errors"); - gettext("If enabled, invalid world data won't cause the server to shut down.\nOnly enable this if you know what you are doing."); - gettext("Liquid loop max"); - gettext("Max liquids processed per step."); - gettext("Liquid queue purge time"); - 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\nclients.\nSmall values potentially improve performance a lot, at the expense of visible\nrendering glitches (some blocks will not be rendered under water and in caves,\nas well as sometimes on land).\nSetting this to a value greater than max_block_send_distance disables this\noptimization.\nStated in mapblocks (16 nodes)."); - gettext("Server side occlusion culling"); - gettext("If enabled the server will perform map block occlusion culling based on\non the eye position of the player. This can reduce the number of blocks\nsent to the client 50-80%. The client will not longer receive most invisible\nso that the utility of noclip mode is reduced."); - gettext("Client side modding restrictions"); - gettext("Restricts the access of certain client-side functions on servers.\nCombine the byteflags below to restrict client-side features, or set to 0\nfor no restrictions:\nLOAD_CLIENT_MODS: 1 (disable loading client-provided mods)\nCHAT_MESSAGES: 2 (disable send_chat_message call client-side)\nREAD_ITEMDEFS: 4 (disable get_item_def call client-side)\nREAD_NODEDEFS: 8 (disable get_node_def call client-side)\nLOOKUP_NODES_LIMIT: 16 (limits get_node call client-side to\ncsm_restriction_noderange)\nREAD_PLAYERINFO: 32 (disable get_player_names call client-side)"); - gettext("Client side node lookup range restriction"); - gettext("If the CSM restriction for node range is enabled, get_node calls are limited\nto this distance from the player to the node."); - gettext("Security"); - gettext("Enable mod security"); - gettext("Prevent mods from doing insecure things like running shell commands."); - 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-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."); - 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("Chat commands"); - gettext("Instrument chat commands 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."); - gettext("Language"); - gettext("Set the language. Leave empty to use the system language.\nA restart is required after changing this."); - gettext("Debug log level"); - gettext("Level of logging to be written to debug.txt:\n- <nothing> (no logging)\n- none (messages with no level)\n- error\n- warning\n- action\n- info\n- verbose"); - gettext("Debug log file size threshold"); - gettext("If the file size of debug.txt exceeds the number of megabytes specified in\nthis setting when it is opened, the file is moved to debug.txt.1,\ndeleting an older debug.txt.1 if it exists.\ndebug.txt is only moved if this setting is positive."); - gettext("Chat log level"); - gettext("Minimal level of logging to be written to chat."); - gettext("IPv6"); - gettext("Enable IPv6 support (for both client and server).\nRequired for IPv6 connections to work at all."); - gettext("Advanced"); - gettext("cURL interactive timeout"); - gettext("Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds."); - gettext("cURL parallel limit"); - gettext("Limits number of parallel HTTP requests. Affects:\n- Media fetch if server uses remote_media setting.\n- Serverlist download and server announcement.\n- Downloads performed by main menu (e.g. mod manager).\nOnly has an effect if compiled with cURL."); - gettext("cURL file download timeout"); - gettext("Maximum time a file download (e.g. a mod download) may take, stated in milliseconds."); - gettext("Main menu script"); - gettext("Replaces the default main menu with a custom one."); - gettext("Engine profiling data print interval"); - gettext("Print the engine's profiling data in regular intervals (in seconds).\n0 = disable. Useful for developers."); gettext("Mapgen"); + gettext("Fixed map seed"); + gettext("A chosen map seed for a new map, leave empty for random.\nWill be overridden when creating a new world in the main menu."); gettext("Mapgen name"); gettext("Name of map generator to be used when creating a new world.\nCreating a world in the main menu will override this.\nCurrent mapgens in a highly unstable state:\n- The optional floatlands of v7 (disabled by default)."); gettext("Water level"); @@ -727,7 +355,7 @@ fake_function() { gettext("Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).\nOnly mapchunks completely within the mapgen limit are generated.\nValue is stored per-world."); gettext("Mapgen flags"); gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand jungle grass, in all other mapgens this flag controls all decorations."); - gettext("Biome API temperature and humidity noise parameters"); + gettext("Biome API noise parameters"); gettext("Heat noise"); gettext("Temperature variation for biomes."); gettext("Heat blend noise"); @@ -1111,6 +739,195 @@ fake_function() { gettext("Dungeon noise"); gettext("3D noise that determines number of dungeons per mapchunk."); gettext("Advanced"); + gettext("Developer Options"); + gettext("Client modding"); + gettext("Enable Lua modding support on client.\nThis support is experimental and API can change."); + gettext("Main menu script"); + gettext("Replaces the default main menu with a custom one."); + gettext("Mod Security"); + gettext("Enable mod security"); + gettext("Prevent mods from doing insecure things like running shell commands."); + 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-separated list of mods that are allowed to access HTTP APIs, which\nallow them to upload and download data to/from the internet."); + gettext("Debugging"); + gettext("Debug log level"); + gettext("Level of logging to be written to debug.txt:\n- <nothing> (no logging)\n- none (messages with no level)\n- error\n- warning\n- action\n- info\n- verbose\n- trace"); + gettext("Debug log file size threshold"); + gettext("If the file size of debug.txt exceeds the number of megabytes specified in\nthis setting when it is opened, the file is moved to debug.txt.1,\ndeleting an older debug.txt.1 if it exists.\ndebug.txt is only moved if this setting is positive."); + gettext("Chat log level"); + gettext("Minimal level of logging to be written to chat."); + gettext("Deprecated Lua API handling"); + gettext("Handling for deprecated Lua API calls:\n- none: Do not log deprecated calls\n- log: mimic and log backtrace of deprecated call (default).\n- error: abort on usage of deprecated call (suggested for mod developers)."); + gettext("Random input"); + gettext("Enable random user input (only used for testing)."); + gettext("Mod channels"); + gettext("Enable mod channels support."); + gettext("Mod Profiler"); + 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."); + 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("Chat commands"); + gettext("Instrument chat commands on registration."); + gettext("Global callbacks"); + gettext("Instrument global callback functions on registration.\n(anything you pass to a minetest.register_*() function)"); + 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("Engine profiler"); + gettext("Engine profiling data print interval"); + gettext("Print the engine's profiling data in regular intervals (in seconds).\n0 = disable. Useful for developers."); + gettext("Advanced"); + gettext("IPv6"); + gettext("Enable IPv6 support (for both client and server).\nRequired for IPv6 connections to work at all."); + gettext("Ignore world errors"); + gettext("If enabled, invalid world data won't cause the server to shut down.\nOnly enable this if you know what you are doing."); + gettext("Graphics"); + gettext("Shader path"); + gettext("Path to shader directory. If no path is defined, default location will be used."); + gettext("Video driver"); + gettext("The rendering back-end.\nA restart is required after changing this.\nNote: On Android, stick with OGLES1 if unsure! App may fail to start otherwise.\nOn other platforms, OpenGL is recommended.\nShaders are supported by OpenGL (desktop only) and OGLES2 (experimental)"); + gettext("Transparency Sorting Distance"); + gettext("Distance in nodes at which transparency depth sorting is enabled\nUse this to limit the performance impact of transparency depth sorting"); + gettext("VBO"); + gettext("Enable vertex buffer objects.\nThis should greatly improve graphics performance."); + gettext("Cloud radius"); + gettext("Radius of cloud area stated in number of 64 node cloud squares.\nValues larger than 26 will start to produce sharp cutoffs at cloud area corners."); + gettext("Desynchronize block animation"); + gettext("Whether node texture animations should be desynchronized per mapblock."); + gettext("Mesh cache"); + gettext("Enables caching of facedir rotated meshes."); + gettext("Mapblock mesh generation delay"); + gettext("Delay between mesh updates on the client in ms. Increasing this will slow\ndown the rate of mesh updates, thus reducing jitter on slower clients."); + gettext("Mapblock mesh generator's MapBlock cache size in MB"); + gettext("Size of the MapBlock cache of the mesh generator. Increasing this will\nincrease the cache hit %, reducing the data being copied from the main\nthread, thus reducing jitter."); + gettext("Minimap scan height"); + gettext("True = 256\nFalse = 128\nUsable to make minimap smoother on slower machines."); + gettext("World-aligned textures mode"); + gettext("Textures on a node may be aligned either to the node or to the world.\nThe former mode suits better things like machines, furniture, etc., while\nthe latter makes stairs and microblocks fit surroundings better.\nHowever, as this possibility is new, thus may not be used by older servers,\nthis option allows enforcing it for certain node types. Note though that\nthat is considered EXPERIMENTAL and may not work properly."); + gettext("Autoscaling mode"); + gettext("World-aligned textures may be scaled to span several nodes. However,\nthe server may not send the scale you want, especially if you use\na specially-designed texture pack; with this option, the client tries\nto determine the scale automatically basing on the texture size.\nSee also texture_min_size.\nWarning: This option is EXPERIMENTAL!"); + gettext("Font"); + gettext("Font bold by default"); + gettext("Font italic by default"); + gettext("Font shadow"); + gettext("Shadow offset (in pixels) of the default font. If 0, then shadow will not be drawn."); + gettext("Font shadow alpha"); + gettext("Opaqueness (alpha) of the shadow behind the default font, between 0 and 255."); + gettext("Font size"); + gettext("Font size of the default font where 1 unit = 1 pixel at 96 DPI"); + gettext("Font size divisible by"); + gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); + gettext("Regular font path"); + gettext("Path to the default font. Must be a TrueType font.\nThe fallback font will be used if the font cannot be loaded."); + gettext("Bold font path"); + gettext("Italic font path"); + gettext("Bold and italic font path"); + gettext("Monospace font size"); + gettext("Font size of the monospace font where 1 unit = 1 pixel at 96 DPI"); + gettext("Monospace font size divisible by"); + gettext("For pixel-style fonts that do not scale well, this ensures that font sizes used\nwith this font will always be divisible by this value, in pixels. For instance,\na pixel font 16 pixels tall should have this set to 16, so it will only ever be\nsized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32."); + gettext("Monospace font path"); + gettext("Path to the monospace font. Must be a TrueType font.\nThis font is used for e.g. the console and profiler screen."); + gettext("Bold monospace font path"); + gettext("Italic monospace font path"); + gettext("Bold and italic monospace font path"); + gettext("Fallback font path"); + gettext("Path of the fallback font. Must be a TrueType font.\nThis font will be used for certain languages or if the default font is unavailable."); + gettext("Lighting"); + gettext("Light curve low gradient"); + gettext("Gradient of light curve at minimum light level.\nControls the contrast of the lowest light levels."); + gettext("Light curve high gradient"); + gettext("Gradient of light curve at maximum light level.\nControls the contrast of the highest light levels."); + gettext("Light curve boost"); + gettext("Strength of light curve boost.\nThe 3 'boost' parameters define a range of the light\ncurve that is boosted in brightness."); + gettext("Light curve boost center"); + gettext("Center of light curve boost range.\nWhere 0.0 is minimum light level, 1.0 is maximum light level."); + gettext("Light curve boost spread"); + gettext("Spread of light curve boost range.\nControls the width of the range to be boosted.\nStandard deviation of the light curve boost Gaussian."); + gettext("Networking"); + gettext("Prometheus listener address"); + gettext("Prometheus listener address.\nIf Minetest is compiled with ENABLE_PROMETHEUS option enabled,\nenable metrics listener for Prometheus on that address.\nMetrics can be fetched on http://127.0.0.1:30000/metrics"); + gettext("Maximum size of the out chat queue"); + gettext("Maximum size of the out chat queue.\n0 to disable queueing and -1 to make the queue size unlimited."); + gettext("Mapblock unload timeout"); + gettext("Timeout for client to remove unused map data from memory, in seconds."); + gettext("Mapblock limit"); + gettext("Maximum number of mapblocks for client to be kept in memory.\nSet to -1 for unlimited amount."); + gettext("Show debug info"); + gettext("Whether to show the client debug info (has the same effect as hitting F5)."); + gettext("Maximum simultaneous block sends per client"); + gettext("Maximum number of blocks that are simultaneously sent per client.\nThe maximum total count is calculated dynamically:\nmax_total = ceil((#clients + max_users) * per_client / 4)"); + 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."); + gettext("Map Compression Level for Network Transfer"); + gettext("Compression level to use when sending mapblocks to the client.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); + gettext("Server"); + gettext("Chat message format"); + gettext("Format of player chat messages. The following strings are valid placeholders:\n@name, @message, @timestamp (optional)"); + gettext("Chat command time message threshold"); + gettext("If the execution of a chat command takes longer than this specified time in\nseconds, add the time information to the chat command message"); + gettext("Shutdown message"); + gettext("A message to be displayed to all clients when the server shuts down."); + gettext("Crash message"); + gettext("A message to be displayed to all clients when the server crashes."); + gettext("Ask to reconnect after crash"); + gettext("Whether to ask clients to reconnect after a (Lua) crash.\nSet this to true if your server is set up to restart automatically."); + gettext("Server/Env Performance"); + gettext("Dedicated server step"); + gettext("Length of a server tick and the interval at which objects are generally updated over\nnetwork, stated in seconds."); + gettext("Unlimited player transfer distance"); + gettext("Whether players are shown to clients without any range limit.\nDeprecated, use the setting player_transfer_distance instead."); + gettext("Player transfer distance"); + gettext("Defines the maximal player transfer distance in blocks (0 = unlimited)."); + gettext("Active object send range"); + gettext("From how far clients know about objects, stated in mapblocks (16 nodes).\n\nSetting this larger than active_block_range will also cause the server\nto maintain active objects up to this distance in the direction the\nplayer is looking. (This can avoid mobs suddenly disappearing from view)"); + gettext("Active block range"); + gettext("The radius of the volume of blocks around every player that is subject to the\nactive block stuff, stated in mapblocks (16 nodes).\nIn active blocks objects are loaded and ABMs run.\nThis is also the minimum range in which active objects (mobs) are maintained.\nThis should be configured together with active_object_send_range_blocks."); + gettext("Max block send distance"); + gettext("From how far blocks are sent to clients, stated in mapblocks (16 nodes)."); + gettext("Maximum forceloaded blocks"); + gettext("Maximum number of forceloaded mapblocks."); + gettext("Time send interval"); + gettext("Interval of sending time of day to clients, stated in seconds."); + gettext("Map save interval"); + gettext("Interval of saving important changes in the world, stated in seconds."); + gettext("Unload unused server data"); + gettext("How long the server will wait before unloading unused mapblocks, stated in seconds.\nHigher value is smoother, but will use more RAM."); + gettext("Maximum objects per block"); + gettext("Maximum number of statically stored objects in a block."); + gettext("Active block management interval"); + gettext("Length of time between active block management cycles, stated in seconds."); + gettext("ABM interval"); + gettext("Length of time between Active Block Modifier (ABM) execution cycles, stated in seconds."); + gettext("ABM time budget"); + gettext("The time budget allowed for ABMs to execute on each step\n(as a fraction of the ABM Interval)"); + gettext("NodeTimer interval"); + gettext("Length of time between NodeTimer execution cycles, stated in seconds."); + gettext("Liquid loop max"); + gettext("Max liquids processed per step."); + gettext("Liquid queue purge time"); + 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\nclients.\nSmall values potentially improve performance a lot, at the expense of visible\nrendering glitches (some blocks will not be rendered under water and in caves,\nas well as sometimes on land).\nSetting this to a value greater than max_block_send_distance disables this\noptimization.\nStated in mapblocks (16 nodes)."); + gettext("Server side occlusion culling"); + gettext("If enabled the server will perform map block occlusion culling based on\non the eye position of the player. This can reduce the number of blocks\nsent to the client 50-80%. The client will not longer receive most invisible\nso that the utility of noclip mode is reduced."); + gettext("Mapgen"); gettext("Chunk size"); gettext("Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes).\nWARNING!: There is no benefit, and there are several dangers, in\nincreasing this value above 5.\nReducing this value increases cave and dungeon density.\nAltering this value is for special usage, leaving it unchanged is\nrecommended."); gettext("Mapgen debug"); @@ -1123,11 +940,222 @@ fake_function() { gettext("Maximum number of blocks to be queued that are to be generated.\nThis limit is enforced per player."); gettext("Number of emerge threads"); gettext("Number of emerge threads to use.\nValue 0:\n- Automatic selection. The number of emerge threads will be\n- 'number of processors - 2', with a lower limit of 1.\nAny other value:\n- Specifies the number of emerge threads, with a lower limit of 1.\nWARNING: Increasing the number of emerge threads increases engine mapgen\nspeed, but this may harm game performance by interfering with other\nprocesses, especially in singleplayer and/or when running Lua code in\n'on_generated'. For many users the optimum setting may be '1'."); - gettext("Online Content Repository"); - gettext("ContentDB URL"); - gettext("The URL for the content repository"); - gettext("ContentDB Flag Blacklist"); - gettext("Comma-separated list of flags to hide in the content repository.\n\"nonfree\" can be used to hide packages which do not qualify as 'free software',\nas defined by the Free Software Foundation.\nYou can also specify content ratings.\nThese flags are independent from Minetest versions,\nso see a full list at https://content.minetest.net/help/content_flags/"); - gettext("ContentDB Max Concurrent Downloads"); - gettext("Maximum number of concurrent downloads. Downloads exceeding this limit will be queued.\nThis should be lower than curl_parallel_limit."); + gettext("cURL"); + gettext("cURL interactive timeout"); + gettext("Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds."); + gettext("cURL parallel limit"); + gettext("Limits number of parallel HTTP requests. Affects:\n- Media fetch if server uses remote_media setting.\n- Serverlist download and server announcement.\n- Downloads performed by main menu (e.g. mod manager).\nOnly has an effect if compiled with cURL."); + gettext("cURL file download timeout"); + gettext("Maximum time a file download (e.g. a mod download) may take, stated in milliseconds."); + gettext("Misc"); + gettext("DPI"); + gettext("Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens."); + gettext("Display Density Scaling Factor"); + gettext("Adjust the detected display density, used for scaling UI elements."); + gettext("Enable console window"); + gettext("Windows systems only: Start Minetest with the command line window in the background.\nContains the same information as the file debug.txt (default name)."); + 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("Map directory"); + gettext("World directory (everything in the world is stored here).\nNot needed if starting from the main menu."); + gettext("Synchronous SQLite"); + gettext("See https://www.sqlite.org/pragma.html#pragma_synchronous"); + gettext("Map Compression Level for Disk Storage"); + gettext("Compression level to use when saving mapblocks to disk.\n-1 - use default compression level\n0 - least compression, fastest\n9 - best compression, slowest"); + gettext("Connect to external media server"); + gettext("Enable usage of remote media server (if provided by server).\nRemote servers offer a significantly faster way to download media (e.g. textures)\nwhen connecting to the server."); + gettext("Serverlist file"); + gettext("File in client/serverlist/ that contains your favorite servers displayed in the\nMultiplayer Tab."); + gettext("Gamepads"); + gettext("Enable joysticks"); + gettext("Enable joysticks. Requires a restart to take effect"); + gettext("Joystick ID"); + gettext("The identifier of the joystick to use"); + gettext("Joystick type"); + gettext("The type of joystick"); + gettext("Joystick button repetition interval"); + gettext("The time in seconds it takes between repeated events\nwhen holding down a joystick button combination."); + gettext("Joystick dead zone"); + gettext("The dead zone of the joystick"); + gettext("Joystick frustum sensitivity"); + gettext("The sensitivity of the joystick axes for moving the\nin-game view frustum around."); + gettext("Temporary Settings"); + gettext("Texture path"); + gettext("Path to texture directory. All textures are first searched from here."); + gettext("Minimap"); + gettext("Enables minimap."); + gettext("Round minimap"); + gettext("Shape of the minimap. Enabled = round, disabled = square."); + gettext("Server address"); + gettext("Address to connect to.\nLeave this blank to start a local server.\nNote that the address field in the main menu overrides this setting."); + gettext("Remote port"); + gettext("Port to connect to (UDP).\nNote that the port field in the main menu overrides this setting."); + gettext("Default game"); + gettext("Default game when creating a new world.\nThis will be overridden when creating a world from the main menu."); + gettext("Damage"); + gettext("Enable players getting damage and dying."); + gettext("Creative"); + gettext("Enable creative mode for all players"); + gettext("Player versus player"); + gettext("Whether to allow players to damage and kill each other."); + gettext("Flying"); + gettext("Player is able to fly without being affected by gravity.\nThis requires the \"fly\" privilege on the server."); + gettext("Pitch move mode"); + gettext("If enabled, makes move directions relative to the player's pitch when flying or swimming."); + gettext("Fast movement"); + gettext("Fast movement (via the \"Aux1\" key).\nThis requires the \"fast\" privilege on the server."); + 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("Continuous forward"); + gettext("Continuous forward movement, toggled by autoforward key.\nPress the autoforward key again or the backwards movement to disable."); + gettext("Formspec Default Background Opacity"); + gettext("Formspec default background opacity (between 0 and 255)."); + gettext("Formspec Default Background Color"); + gettext("Formspec default background color (R,G,B)."); + gettext("Show technical names"); + gettext("Whether to show technical names.\nAffects mods and texture packs in the Content and Select Mods menus, as well as\nsetting names in All Settings.\nControlled by the checkbox in the \"All settings\" menu."); + gettext("Sound"); + gettext("Enables the sound system.\nIf disabled, this completely disables all sounds everywhere and the in-game\nsound controls will be non-functional.\nChanging this setting requires a restart."); + gettext("Forward key"); + gettext("Key for moving the player forward.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Backward key"); + gettext("Key for moving the player backward.\nWill also disable autoforward, when active.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Left key"); + gettext("Key for moving the player left.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Right key"); + gettext("Key for moving the player right.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Jump key"); + gettext("Key for jumping.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Sneak key"); + gettext("Key for sneaking.\nAlso used for climbing down and descending in water if aux1_descends is disabled.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Dig key"); + gettext("Key for digging.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Place key"); + gettext("Key for placing.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Inventory key"); + gettext("Key for opening the inventory.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Aux1 key"); + gettext("Key for moving fast in fast mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Chat key"); + gettext("Key for opening the chat window.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Command key"); + gettext("Key for opening the chat window to type commands.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Command key"); + gettext("Key for opening the chat window to type local commands.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Range select key"); + gettext("Key for toggling unlimited view range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Fly key"); + gettext("Key for toggling flying.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Pitch move key"); + gettext("Key for toggling pitch move mode.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Fast key"); + 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("Hotbar next key"); + gettext("Key for selecting the next item in the hotbar.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar previous key"); + gettext("Key for selecting the previous item in the hotbar.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Mute key"); + gettext("Key for muting the game.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Inc. volume key"); + gettext("Key for increasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Dec. volume key"); + gettext("Key for decreasing the volume.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Automatic forward key"); + gettext("Key for toggling autoforward.\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"); + gettext("Key for toggling display of minimap.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Screenshot"); + gettext("Key for taking screenshots.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Drop item key"); + gettext("Key for dropping the currently selected item.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("View zoom key"); + gettext("Key to use view zoom when possible.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 1 key"); + gettext("Key for selecting the first hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 2 key"); + gettext("Key for selecting the second hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 3 key"); + gettext("Key for selecting the third hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 4 key"); + gettext("Key for selecting the fourth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 5 key"); + gettext("Key for selecting the fifth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 6 key"); + gettext("Key for selecting the sixth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 7 key"); + gettext("Key for selecting the seventh hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 8 key"); + gettext("Key for selecting the eighth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 9 key"); + gettext("Key for selecting the ninth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 10 key"); + gettext("Key for selecting the tenth hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 11 key"); + gettext("Key for selecting the 11th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 12 key"); + gettext("Key for selecting the 12th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 13 key"); + gettext("Key for selecting the 13th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 14 key"); + gettext("Key for selecting the 14th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 15 key"); + gettext("Key for selecting the 15th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 16 key"); + gettext("Key for selecting the 16th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 17 key"); + gettext("Key for selecting the 17th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 18 key"); + gettext("Key for selecting the 18th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 19 key"); + gettext("Key for selecting the 19th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 20 key"); + gettext("Key for selecting the 20th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 21 key"); + gettext("Key for selecting the 21st hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 22 key"); + gettext("Key for selecting the 22nd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 23 key"); + gettext("Key for selecting the 23rd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 24 key"); + gettext("Key for selecting the 24th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 25 key"); + gettext("Key for selecting the 25th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 26 key"); + gettext("Key for selecting the 26th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 27 key"); + gettext("Key for selecting the 27th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 28 key"); + gettext("Key for selecting the 28th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 29 key"); + gettext("Key for selecting the 29th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 30 key"); + gettext("Key for selecting the 30th hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 31 key"); + gettext("Key for selecting the 31st hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Hotbar slot 32 key"); + gettext("Key for selecting the 32nd hotbar slot.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("HUD toggle key"); + gettext("Key for toggling the display of the HUD.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Chat toggle key"); + gettext("Key for toggling the display of chat.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Large chat console key"); + gettext("Key for toggling the display of the large chat console.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Fog toggle key"); + gettext("Key for toggling the display of fog.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Camera update toggle key"); + 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"); + gettext("Key for toggling the display of the profiler. Used for development.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("Toggle camera mode key"); + gettext("Key for switching between first- and third-person camera.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("View range increase key"); + gettext("Key for increasing the viewing range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); + gettext("View range decrease key"); + gettext("Key for decreasing the viewing range.\nSee http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3"); } diff --git a/src/skyparams.h b/src/skyparams.h index f7f694427..07068634b 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -66,6 +66,7 @@ struct StarParams u32 count; video::SColor starcolor; f32 scale; + f32 day_opacity; }; struct CloudParams @@ -141,6 +142,7 @@ public: stars.count = 1000; stars.starcolor = video::SColor(105, 235, 235, 255); stars.scale = 1; + stars.day_opacity = 0; return stars; } diff --git a/src/sound.h b/src/sound.h index 6f7b0a1af..801c552a9 100644 --- a/src/sound.h +++ b/src/sound.h @@ -24,30 +24,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "irrlichttypes_bloated.h" +// This class describes the basic sound information for playback. +// Positional handling is done separately. + struct SimpleSoundSpec { SimpleSoundSpec(const std::string &name = "", float gain = 1.0f, - float fade = 0.0f, float pitch = 1.0f) : - name(name), - gain(gain), fade(fade), pitch(pitch) + bool loop = false, float fade = 0.0f, float pitch = 1.0f) : + name(name), gain(gain), fade(fade), pitch(pitch), loop(loop) { } bool exists() const { return !name.empty(); } - // Take cf_version from ContentFeatures::serialize to - // keep in sync with item definitions - void serialize(std::ostream &os, u8 cf_version) const + void serialize(std::ostream &os, u16 protocol_version) const { os << serializeString16(name); writeF32(os, gain); writeF32(os, pitch); writeF32(os, fade); - // if (cf_version < ?) - // return; } - void deSerialize(std::istream &is, u8 cf_version) + void deSerialize(std::istream &is, u16 protocol_version) { name = deSerializeString16(is); gain = readF32(is); @@ -59,4 +57,13 @@ struct SimpleSoundSpec float gain = 1.0f; float fade = 0.0f; float pitch = 1.0f; + bool loop = false; +}; + + +// The order must not be changed. This is sent over the network. +enum class SoundLocation : u8 { + Local, + Position, + Object }; diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 1160ec68f..f92995d0b 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -28,12 +28,12 @@ StaticObject::StaticObject(const ServerActiveObject *s_obj, const v3f &pos_): s_obj->getStaticData(&data); } -void StaticObject::serialize(std::ostream &os) +void StaticObject::serialize(std::ostream &os) const { // type writeU8(os, type); // pos - writeV3F1000(os, pos); + writeV3F1000(os, clampToF1000(pos)); // data os<<serializeString16(data); } diff --git a/src/staticobject.h b/src/staticobject.h index 6fb486193..03cd23cc8 100644 --- a/src/staticobject.h +++ b/src/staticobject.h @@ -37,7 +37,7 @@ struct StaticObject StaticObject() = default; StaticObject(const ServerActiveObject *s_obj, const v3f &pos_); - void serialize(std::ostream &os); + void serialize(std::ostream &os) const; void deSerialize(std::istream &is, u8 version); }; diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp index 9e3d33736..b12261c3b 100644 --- a/src/terminal_chat_console.cpp +++ b/src/terminal_chat_console.cpp @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <inttypes.h> #include "config.h" #if USE_CURSES #include "version.h" @@ -398,7 +399,7 @@ void TerminalChatConsole::step(int ch) minutes = (float)minutes / 1000 * 60; if (m_game_time) - printw(" | Game %d Time of day %02d:%02d ", + printw(" | Game %" PRIu64 " Time of day %02d:%02d ", m_game_time, hours, minutes); // draw text diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index 930fd9473..a79a36e57 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tileanimation.h" #include "util/serialize.h" -void TileAnimationParams::serialize(std::ostream &os, u8 tiledef_version) const +void TileAnimationParams::serialize(std::ostream &os, u16 protocol_version) const { writeU8(os, type); if (type == TAT_VERTICAL_FRAMES) { @@ -33,7 +33,7 @@ void TileAnimationParams::serialize(std::ostream &os, u8 tiledef_version) const } } -void TileAnimationParams::deSerialize(std::istream &is, u8 tiledef_version) +void TileAnimationParams::deSerialize(std::istream &is, u16 protocol_version) { type = (TileAnimationType) readU8(is); diff --git a/src/tileanimation.h b/src/tileanimation.h index 7e3285ed7..0449de0b8 100644 --- a/src/tileanimation.h +++ b/src/tileanimation.h @@ -50,8 +50,8 @@ struct TileAnimationParams } sheet_2d; }; - void serialize(std::ostream &os, u8 tiledef_version) const; - void deSerialize(std::istream &is, u8 tiledef_version); + void serialize(std::ostream &os, u16 protocol_version) const; + void deSerialize(std::istream &is, u16 protocol_version); void determineParams(v2u32 texture_size, int *frame_count, int *frame_length_ms, v2u32 *frame_size) const; void getTextureModifer(std::ostream &os, v2u32 texture_size, int frame) const; diff --git a/src/tool.cpp b/src/tool.cpp index 075c6b3c5..821ddf07d 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -183,7 +183,7 @@ void ToolCapabilities::deserializeJson(std::istream &is) } } -static u32 calculateResultWear(const u32 uses, const u16 initial_wear) +u32 calculateResultWear(const u32 uses, const u16 initial_wear) { if (uses == 0) { // Trivial case: Infinite uses diff --git a/src/tool.h b/src/tool.h index 8409f59af..c2444a834 100644 --- a/src/tool.h +++ b/src/tool.h @@ -142,4 +142,5 @@ PunchDamageResult getPunchDamage( u16 initial_wear = 0 ); +u32 calculateResultWear(const u32 uses, const u16 initial_wear); f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand); diff --git a/src/translation.cpp b/src/translation.cpp index 1e43b0894..eabd0ec0a 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2017 Nore, Nathanaël Courant <nore@mesecons.net> +Copyright (C) 2017 Nore, Nathanaëlle 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 diff --git a/src/translation.h b/src/translation.h index f1a336fca..e3f5b21ea 100644 --- a/src/translation.h +++ b/src/translation.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2017 Nore, Nathanaël Courant <nore@mesecons.net> +Copyright (C) 2017 Nore, Nathanaëlle 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 diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 92f31ecac..84f769e87 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -11,6 +11,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index f223d567e..af30c209d 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -360,7 +360,7 @@ struct TestMapBlock: public TestBase MapNode node; bool position_valid; - core::list<v3s16> validity_exceptions; + std::list<v3s16> validity_exceptions; TC() { @@ -371,7 +371,7 @@ struct TestMapBlock: public TestBase { //return position_valid ^ (p==position_valid_exception); bool exception = false; - for(core::list<v3s16>::Iterator i=validity_exceptions.begin(); + for(std::list<v3s16>::iterator i=validity_exceptions.begin(); i != validity_exceptions.end(); i++) { if(p == *i) @@ -533,7 +533,7 @@ struct TestMapBlock: public TestBase parent.node.setContent(CONTENT_AIR); parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN); parent.node.setLight(LIGHTBANK_NIGHT, 0); - core::map<v3s16, bool> light_sources; + std::map<v3s16, bool> light_sources; // The bottom block is invalid, because we have a shadowing node UASSERT(b.propagateSunlight(light_sources) == false); UASSERT(b.getNode(v3s16(1,4,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); @@ -560,7 +560,7 @@ struct TestMapBlock: public TestBase parent.position_valid = true; b.setIsUnderground(true); parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); - core::map<v3s16, bool> light_sources; + std::map<v3s16, bool> light_sources; // The block below should be valid because there shouldn't be // sunlight in there either UASSERT(b.propagateSunlight(light_sources, true) == true); @@ -601,7 +601,7 @@ struct TestMapBlock: public TestBase } // Lighting value for the valid nodes parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); - core::map<v3s16, bool> light_sources; + std::map<v3s16, bool> light_sources; // Bottom block is not valid UASSERT(b.propagateSunlight(light_sources) == false); } diff --git a/src/unittest/test.h b/src/unittest/test.h index 1102f6d33..79ea09471 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -80,7 +80,7 @@ class TestFailedException : public std::exception { << #expected << std::endl \ << " at " << fs::GetFilenameFromPath(__FILE__) << ":" \ << __LINE__ << std::endl \ - << " actual: " << a << std::endl << " expected: " \ + << " actual : " << a << std::endl << " expected: " \ << e << std::endl; \ throw TestFailedException(); \ } \ diff --git a/src/unittest/test_address.cpp b/src/unittest/test_address.cpp index 35d4effb6..f46135577 100644 --- a/src/unittest/test_address.cpp +++ b/src/unittest/test_address.cpp @@ -56,7 +56,7 @@ void TestAddress::testIsLocalhost() UASSERT(!Address(172, 45, 37, 68, 0).isLocalhost()); // v6 - std::unique_ptr<IPv6AddressBytes> ipv6Bytes(new IPv6AddressBytes()); + auto ipv6Bytes = std::make_unique<IPv6AddressBytes>(); std::vector<u8> ipv6RawAddr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; memcpy(ipv6Bytes->bytes, &ipv6RawAddr[0], 16); UASSERT(Address(ipv6Bytes.get(), 0).isLocalhost()) diff --git a/src/unittest/test_eventmanager.cpp b/src/unittest/test_eventmanager.cpp index bb0e59336..fec57f9fe 100644 --- a/src/unittest/test_eventmanager.cpp +++ b/src/unittest/test_eventmanager.cpp @@ -82,7 +82,7 @@ void TestEventManager::testDeregister() void TestEventManager::testRealEvent() { EventManager ev; - std::unique_ptr<EventManagerTest> emt(new EventManagerTest()); + auto emt = std::make_unique<EventManagerTest>(); ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get()); // Put event & verify event value @@ -93,7 +93,7 @@ void TestEventManager::testRealEvent() void TestEventManager::testRealEventAfterDereg() { EventManager ev; - std::unique_ptr<EventManagerTest> emt(new EventManagerTest()); + auto emt = std::make_unique<EventManagerTest>(); ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get()); // Put event & verify event value diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp index 3484f1514..2fb7cfcd6 100644 --- a/src/unittest/test_irrptr.cpp +++ b/src/unittest/test_irrptr.cpp @@ -93,7 +93,9 @@ void TestIrrPtr::testRefCounting() #if defined(__clang__) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #if __clang_major__ >= 7 + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #endif #pragma GCC diagnostic ignored "-Wself-move" #endif diff --git a/src/unittest/test_lua.cpp b/src/unittest/test_lua.cpp new file mode 100644 index 000000000..fc8f895af --- /dev/null +++ b/src/unittest/test_lua.cpp @@ -0,0 +1,79 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2021 TurkeyMcMac, Jude Melton-Houghton <jwmhjwmh@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +extern "C" { +#include <lua.h> +#include <lauxlib.h> +} + +class TestLua : public TestBase +{ +public: + TestLua() { TestManager::registerTestModule(this); } + const char *getName() { return "TestLua"; } + + void runTests(IGameDef *gamedef); + + void testLuaDestructors(); +}; + +static TestLua g_test_instance; + +void TestLua::runTests(IGameDef *gamedef) +{ + TEST(testLuaDestructors); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace +{ + + class DestructorDetector { + bool *did_destruct; + public: + DestructorDetector(bool *did_destruct) : did_destruct(did_destruct) + { + *did_destruct = false; + } + ~DestructorDetector() + { + *did_destruct = true; + } + }; + +} + +void TestLua::testLuaDestructors() +{ + bool did_destruct = false; + + lua_State *L = luaL_newstate(); + lua_cpcall(L, [](lua_State *L) -> int { + DestructorDetector d(reinterpret_cast<bool*>(lua_touserdata(L, 1))); + luaL_error(L, "error"); + return 0; + }, &did_destruct); + lua_close(L); + + UASSERT(did_destruct); +} diff --git a/src/unittest/test_nodedef.cpp b/src/unittest/test_nodedef.cpp index 66ca0ccbc..acf669783 100644 --- a/src/unittest/test_nodedef.cpp +++ b/src/unittest/test_nodedef.cpp @@ -60,7 +60,7 @@ void TestNodeDef::testContentFeaturesSerialization() std::istringstream is(os.str(), std::ios::binary); ContentFeatures f2; - f2.deSerialize(is); + f2.deSerialize(is, LATEST_PROTOCOL_VERSION); UASSERT(f.walkable == f2.walkable); UASSERT(f.node_box.type == f2.node_box.type); diff --git a/src/unittest/test_noise.cpp b/src/unittest/test_noise.cpp index 421f3b66e..12b155f46 100644 --- a/src/unittest/test_noise.cpp +++ b/src/unittest/test_noise.cpp @@ -30,8 +30,14 @@ public: void runTests(IGameDef *gamedef); + void testNoise2dAtOriginWithZeroSeed(); + void testNoise2dWithMaxSeed(); + void testNoise2dWithFunPrimes(); void testNoise2dPoint(); void testNoise2dBulk(); + void testNoise3dAtOriginWithZeroSeed(); + void testNoise3dWithMaxSeed(); + void testNoise3dWithFunPrimes(); void testNoise3dPoint(); void testNoise3dBulk(); void testNoiseInvalidParams(); @@ -44,8 +50,14 @@ static TestNoise g_test_instance; void TestNoise::runTests(IGameDef *gamedef) { + TEST(testNoise2dAtOriginWithZeroSeed); + TEST(testNoise2dWithMaxSeed); + TEST(testNoise2dWithFunPrimes); TEST(testNoise2dPoint); TEST(testNoise2dBulk); + TEST(testNoise3dAtOriginWithZeroSeed); + TEST(testNoise3dWithMaxSeed); + TEST(testNoise3dWithFunPrimes); TEST(testNoise3dPoint); TEST(testNoise3dBulk); TEST(testNoiseInvalidParams); @@ -53,6 +65,27 @@ void TestNoise::runTests(IGameDef *gamedef) //////////////////////////////////////////////////////////////////////////////// +void TestNoise::testNoise2dAtOriginWithZeroSeed() +{ + float actual{ noise2d(0, 0, 0) }; + constexpr float expected{ -0.281791f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + +void TestNoise::testNoise2dWithMaxSeed() +{ + float actual{ noise2d(4096, 4096, 2147483647) }; + constexpr float expected{ 0.950606f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + +void TestNoise::testNoise2dWithFunPrimes() +{ + float actual{ noise2d(-3947, -2333, 7027) }; + constexpr float expected{ -0.294907f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + void TestNoise::testNoise2dPoint() { NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); @@ -79,6 +112,27 @@ void TestNoise::testNoise2dBulk() } } +void TestNoise::testNoise3dAtOriginWithZeroSeed() +{ + float actual{ noise2d(0, 0, 0) }; + constexpr float expected{ -0.281791f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + +void TestNoise::testNoise3dWithMaxSeed() +{ + float actual{ noise3d(4096, 4096, 4096, 2147483647) }; + constexpr float expected{ -0.775243f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + +void TestNoise::testNoise3dWithFunPrimes() +{ + float actual{ noise2d(3903, -1723, 7411) }; + constexpr float expected{ 0.989124f }; + UASSERT(std::fabs(actual - expected) <= 0.00001); +} + void TestNoise::testNoise3dPoint() { NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp index 660d77d02..ff6b57507 100644 --- a/src/unittest/test_serialization.cpp +++ b/src/unittest/test_serialization.cpp @@ -169,23 +169,50 @@ void TestSerialization::testDeSerializeLongString() void TestSerialization::testSerializeJsonString() { + std::istringstream is(std::ios::binary); + const auto reset_is = [&] (const std::string &s) { + is.clear(); + is.str(s); + }; + const auto assert_at_eof = [] (std::istream &is) { + is.get(); + UASSERT(is.eof()); + }; + // Test blank string - UASSERT(serializeJsonString("") == "\"\""); + UASSERTEQ(std::string, serializeJsonString(""), "\"\""); + reset_is("\"\""); + UASSERTEQ(std::string, deSerializeJsonString(is), ""); + assert_at_eof(is); // Test basic string - UASSERT(serializeJsonString("Hello world!") == "\"Hello world!\""); + UASSERTEQ(std::string, serializeJsonString("Hello world!"), "\"Hello world!\""); + reset_is("\"Hello world!\""); + UASSERTEQ(std::string, deSerializeJsonString(is), "Hello world!"); + assert_at_eof(is); + + // Test optional serialization + const std::pair<const char*, const char*> test_pairs[] = { + { "abc", "abc" }, + { "x y z", "\"x y z\"" }, + { "\"", "\"\\\"\"" }, + }; + for (auto it : test_pairs) { + UASSERTEQ(std::string, serializeJsonStringIfNeeded(it.first), it.second); + reset_is(it.second); + UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), it.first); + assert_at_eof(is); + } - // MSVC fails when directly using "\\\\" - std::string backslash = "\\"; - UASSERT(serializeJsonString(teststring2) == - mkstr("\"") + + // Test all byte values + const std::string bs = "\\"; // MSVC fails when directly using "\\\\" + const std::string expected = mkstr("\"") + "\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" + "\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f" + "\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + "\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" + - " !\\\"" + teststring2.substr(0x23, 0x2f-0x23) + - "\\/" + teststring2.substr(0x30, 0x5c-0x30) + - backslash + backslash + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" + + " !\\\"" + teststring2.substr(0x23, 0x5c-0x23) + + bs + bs + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" + "\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" + "\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f" + "\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" + @@ -202,14 +229,31 @@ void TestSerialization::testSerializeJsonString() "\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" + "\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6\\u00f7" + "\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" + - "\""); - - // Test deserialize - std::istringstream is(serializeJsonString(teststring2), std::ios::binary); - UASSERT(deSerializeJsonString(is) == teststring2); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); + "\""; + std::string serialized = serializeJsonString(teststring2); + UASSERTEQ(std::string, serialized, expected); + + reset_is(serialized); + UASSERTEQ(std::string, deSerializeJsonString(is), teststring2); + UASSERT(!is.eof()); // should have stopped at " so eof must not be set yet + assert_at_eof(is); + + // Test that deserialization leaves rest of stream alone + std::string tmp; + reset_is("\"foo\"bar"); + UASSERTEQ(std::string, deSerializeJsonString(is), "foo"); + std::getline(is, tmp, '\0'); + UASSERTEQ(std::string, tmp, "bar"); + + reset_is("\"x y z\"bar"); + UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), "x y z"); + std::getline(is, tmp, '\0'); + UASSERTEQ(std::string, tmp, "bar"); + + reset_is("foo bar"); + UASSERTEQ(std::string, deSerializeJsonStringIfNeeded(is), "foo"); + std::getline(is, tmp, '\0'); + UASSERTEQ(std::string, tmp, " bar"); } diff --git a/src/unittest/test_server_shutdown_state.cpp b/src/unittest/test_server_shutdown_state.cpp index fbb76ff6a..50305e725 100644 --- a/src/unittest/test_server_shutdown_state.cpp +++ b/src/unittest/test_server_shutdown_state.cpp @@ -26,12 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc., class FakeServer : public Server { public: - // clang-format off FakeServer() : Server("fakeworld", SubgameSpec("fakespec", "fakespec"), true, Address(), true, nullptr) { } - // clang-format on private: void SendChatMessage(session_t peer_id, const ChatMessage &message) @@ -95,7 +93,7 @@ void TestServerShutdownState::testTrigger() void TestServerShutdownState::testTick() { - std::unique_ptr<FakeServer> fakeServer(new FakeServer()); + auto fakeServer = std::make_unique<FakeServer>(); Server::ShutdownState ss; ss.trigger(28.0f, "testtrigger", true); ss.tick(0.0f, fakeServer.get()); diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 4c473d8b5..91bf5d3a4 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/mods.h" #include "settings.h" #include "test_config.h" +#include "util/string.h" class TestServerModManager : public TestBase { @@ -190,4 +191,11 @@ void TestServerModManager::testGetModMediaPaths() std::vector<std::string> result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), false); + + // Test media overriding: + // unittests depends on basenodes to override default_dirt.png, + // thus the unittests texture path must come first in the returned media paths to take priority + auto it = std::find(result.begin(), result.end(), sm.getModSpec("unittests")->path + DIR_DELIM + "textures"); + UASSERT(it != result.end()); + UASSERT(std::find(++it, result.end(), sm.getModSpec("basenodes")->path + DIR_DELIM + "textures") != result.end()); } diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 743fe4462..98a143d1f 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -43,7 +43,6 @@ public: void testPadString(); void testStartsWith(); void testStrEqual(); - void testStringTrim(); void testStrToIntConversion(); void testStringReplace(); void testStringAllowed(); @@ -58,6 +57,7 @@ public: void testStringJoin(); void testEulerConversion(); void testBase64(); + void testSanitizeDirName(); }; static TestUtilities g_test_instance; @@ -75,7 +75,6 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testPadString); TEST(testStartsWith); TEST(testStrEqual); - TEST(testStringTrim); TEST(testStrToIntConversion); TEST(testStringReplace); TEST(testStringAllowed); @@ -90,6 +89,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testStringJoin); TEST(testEulerConversion); TEST(testBase64); + TEST(testSanitizeDirName); } //////////////////////////////////////////////////////////////////////////////// @@ -190,6 +190,8 @@ void TestUtilities::testTrim() UASSERT(trim("dirt_with_grass") == "dirt_with_grass"); UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR"); UASSERT(trim("\n \t\r \r\n\t\t ") == ""); + UASSERT(trim(" a") == "a"); + UASSERT(trim("a ") == "a"); } @@ -255,15 +257,6 @@ void TestUtilities::testStrEqual() } -void TestUtilities::testStringTrim() -{ - UASSERT(trim(" a") == "a"); - UASSERT(trim(" a ") == "a"); - UASSERT(trim("a ") == "a"); - UASSERT(trim("") == ""); -} - - void TestUtilities::testStrToIntConversion() { UASSERT(mystoi("123", 0, 1000) == 123); @@ -630,3 +623,16 @@ void TestUtilities::testBase64() UASSERT(base64_is_valid("AAAA=A") == false); UASSERT(base64_is_valid("AAAAA=A") == false); } + + +void TestUtilities::testSanitizeDirName() +{ + UASSERT(sanitizeDirName("a", "~") == "a"); + UASSERT(sanitizeDirName(" ", "~") == "__"); + UASSERT(sanitizeDirName(" a ", "~") == "_a_"); + UASSERT(sanitizeDirName("COM1", "~") == "~COM1"); + UASSERT(sanitizeDirName("COM1", ":") == "_COM1"); + UASSERT(sanitizeDirName("cOm\u00B2", "~") == "~cOm\u00B2"); + UASSERT(sanitizeDirName("cOnIn$", "~") == "~cOnIn$"); + UASSERT(sanitizeDirName(" cOnIn$ ", "~") == "_cOnIn$_"); +} diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index 9826d2ee7..a79c9778e 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -120,7 +120,7 @@ void TestVoxelArea::test_extent() VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); UASSERT(v1.getExtent() == v3s16(1191, 995, 1459)); - VoxelArea v2(v3s16(32493, -32507, 32753), v3s16(32508, -32492, 32768)); + VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); UASSERT(v2.getExtent() == v3s16(16, 16, 16)); } @@ -129,7 +129,7 @@ void TestVoxelArea::test_volume() VoxelArea v1(v3s16(-1337, -547, -789), v3s16(-147, 447, 669)); UASSERTEQ(s32, v1.getVolume(), 1728980655); - VoxelArea v2(v3s16(32493, -32507, 32753), v3s16(32508, -32492, 32768)); + VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); UASSERTEQ(s32, v2.getVolume(), 4096); } diff --git a/src/util/basic_macros.h b/src/util/basic_macros.h index 334e342e0..3910c6185 100644 --- a/src/util/basic_macros.h +++ b/src/util/basic_macros.h @@ -29,13 +29,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) // To disable copy constructors and assignment operations for some class -// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) as a private member. +// 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) in the class definition. // Note this also disables copying for any classes derived from 'Foobar' as well // as classes having a 'Foobar' member. #define DISABLE_CLASS_COPY(C) \ C(const C &) = delete; \ C &operator=(const C &) = delete; +// If you have used DISABLE_CLASS_COPY with a class but still want to permit moving +// use this macro to add the default move constructors back. +#define ALLOW_CLASS_MOVE(C) \ + C(C &&other) = default; \ + C &operator=(C &&) = default; + #ifndef _MSC_VER #define UNUSED_ATTRIBUTE __attribute__ ((unused)) #else diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp index b1f95215e..941f8b08d 100644 --- a/src/util/enriched_string.cpp +++ b/src/util/enriched_string.cpp @@ -1,6 +1,6 @@ /* Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is> -Copyright (C) 2016 Nore, Nathanaël Courant <nore@mesecons.net> +Copyright (C) 2016 Nore, Nathanaëlle 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 diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h index 16a0eef74..4e249eac5 100644 --- a/src/util/enriched_string.h +++ b/src/util/enriched_string.h @@ -1,6 +1,6 @@ /* Copyright (C) 2013 xyz, Ilya Zhuravlev <whatever@xyz.is> -Copyright (C) 2016 Nore, Nathanaël Courant <nore@mesecons.net> +Copyright (C) 2016 Nore, Nathanaëlle 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 diff --git a/src/util/ieee_float.cpp b/src/util/ieee_float.cpp index 887258921..b73763c55 100644 --- a/src/util/ieee_float.cpp +++ b/src/util/ieee_float.cpp @@ -39,7 +39,7 @@ f32 u32Tof32Slow(u32 i) if (exp == 0xFF) { // Inf/NaN if (imant == 0) { - if (std::numeric_limits<f32>::has_infinity) + if (std::numeric_limits<f32>::has_infinity) return sign ? -std::numeric_limits<f32>::infinity() : std::numeric_limits<f32>::infinity(); return sign ? std::numeric_limits<f32>::max() : diff --git a/src/util/metricsbackend.cpp b/src/util/metricsbackend.cpp index 4454557a3..63b49ac0a 100644 --- a/src/util/metricsbackend.cpp +++ b/src/util/metricsbackend.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "metricsbackend.h" +#include "util/thread.h" #if USE_PROMETHEUS #include <prometheus/exposer.h> #include <prometheus/registry.h> @@ -27,18 +28,78 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #endif +/* Plain implementation */ + +class SimpleMetricCounter : public MetricCounter +{ +public: + SimpleMetricCounter() : MetricCounter(), m_counter(0.0) {} + + virtual ~SimpleMetricCounter() {} + + void increment(double number) override + { + MutexAutoLock lock(m_mutex); + m_counter += number; + } + double get() const override + { + MutexAutoLock lock(m_mutex); + return m_counter; + } + +private: + mutable std::mutex m_mutex; + double m_counter; +}; + +class SimpleMetricGauge : public MetricGauge +{ +public: + SimpleMetricGauge() : MetricGauge(), m_gauge(0.0) {} + + virtual ~SimpleMetricGauge() {} + + void increment(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge += number; + } + void decrement(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge -= number; + } + void set(double number) override + { + MutexAutoLock lock(m_mutex); + m_gauge = number; + } + double get() const override + { + MutexAutoLock lock(m_mutex); + return m_gauge; + } + +private: + mutable std::mutex m_mutex; + double m_gauge; +}; + MetricCounterPtr MetricsBackend::addCounter( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<SimpleMetricCounter>(name, help_str); + return std::make_shared<SimpleMetricCounter>(); } MetricGaugePtr MetricsBackend::addGauge( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<SimpleMetricGauge>(name, help_str); + return std::make_shared<SimpleMetricGauge>(); } +/* Prometheus backend */ + #if USE_PROMETHEUS class PrometheusMetricCounter : public MetricCounter @@ -47,13 +108,14 @@ public: PrometheusMetricCounter() = delete; PrometheusMetricCounter(const std::string &name, const std::string &help_str, + MetricsBackend::Labels labels, std::shared_ptr<prometheus::Registry> registry) : MetricCounter(), m_family(prometheus::BuildCounter() .Name(name) .Help(help_str) .Register(*registry)), - m_counter(m_family.Add({})) + m_counter(m_family.Add(labels)) { } @@ -73,13 +135,14 @@ public: PrometheusMetricGauge() = delete; PrometheusMetricGauge(const std::string &name, const std::string &help_str, + MetricsBackend::Labels labels, std::shared_ptr<prometheus::Registry> registry) : MetricGauge(), m_family(prometheus::BuildGauge() .Name(name) .Help(help_str) .Register(*registry)), - m_gauge(m_family.Add({})) + m_gauge(m_family.Add(labels)) { } @@ -99,8 +162,7 @@ class PrometheusMetricsBackend : public MetricsBackend { public: PrometheusMetricsBackend(const std::string &addr) : - MetricsBackend(), m_exposer(std::unique_ptr<prometheus::Exposer>( - new prometheus::Exposer(addr))), + MetricsBackend(), m_exposer(std::make_unique<prometheus::Exposer>(addr)), m_registry(std::make_shared<prometheus::Registry>()) { m_exposer->RegisterCollectable(m_registry); @@ -108,10 +170,12 @@ public: virtual ~PrometheusMetricsBackend() {} - virtual MetricCounterPtr addCounter( - const std::string &name, const std::string &help_str); - virtual MetricGaugePtr addGauge( - const std::string &name, const std::string &help_str); + MetricCounterPtr addCounter( + const std::string &name, const std::string &help_str, + Labels labels = {}) override; + MetricGaugePtr addGauge( + const std::string &name, const std::string &help_str, + Labels labels = {}) override; private: std::unique_ptr<prometheus::Exposer> m_exposer; @@ -119,15 +183,15 @@ private: }; MetricCounterPtr PrometheusMetricsBackend::addCounter( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<PrometheusMetricCounter>(name, help_str, m_registry); + return std::make_shared<PrometheusMetricCounter>(name, help_str, labels, m_registry); } MetricGaugePtr PrometheusMetricsBackend::addGauge( - const std::string &name, const std::string &help_str) + const std::string &name, const std::string &help_str, Labels labels) { - return std::make_shared<PrometheusMetricGauge>(name, help_str, m_registry); + return std::make_shared<PrometheusMetricGauge>(name, help_str, labels, m_registry); } MetricsBackend *createPrometheusMetricsBackend() diff --git a/src/util/metricsbackend.h b/src/util/metricsbackend.h index c37306392..644c73325 100644 --- a/src/util/metricsbackend.h +++ b/src/util/metricsbackend.h @@ -19,8 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include <memory> +#include <string> +#include <utility> #include "config.h" -#include "util/thread.h" class MetricCounter { @@ -35,38 +36,6 @@ public: typedef std::shared_ptr<MetricCounter> MetricCounterPtr; -class SimpleMetricCounter : public MetricCounter -{ -public: - SimpleMetricCounter() = delete; - - virtual ~SimpleMetricCounter() {} - - SimpleMetricCounter(const std::string &name, const std::string &help_str) : - MetricCounter(), m_name(name), m_help_str(help_str), - m_counter(0.0) - { - } - - virtual void increment(double number) - { - MutexAutoLock lock(m_mutex); - m_counter += number; - } - virtual double get() const - { - MutexAutoLock lock(m_mutex); - return m_counter; - } - -private: - std::string m_name; - std::string m_help_str; - - mutable std::mutex m_mutex; - double m_counter; -}; - class MetricGauge { public: @@ -81,47 +50,6 @@ public: typedef std::shared_ptr<MetricGauge> MetricGaugePtr; -class SimpleMetricGauge : public MetricGauge -{ -public: - SimpleMetricGauge() = delete; - - SimpleMetricGauge(const std::string &name, const std::string &help_str) : - MetricGauge(), m_name(name), m_help_str(help_str), m_gauge(0.0) - { - } - - virtual ~SimpleMetricGauge() {} - - virtual void increment(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge += number; - } - virtual void decrement(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge -= number; - } - virtual void set(double number) - { - MutexAutoLock lock(m_mutex); - m_gauge = number; - } - virtual double get() const - { - MutexAutoLock lock(m_mutex); - return m_gauge; - } - -private: - std::string m_name; - std::string m_help_str; - - mutable std::mutex m_mutex; - double m_gauge; -}; - class MetricsBackend { public: @@ -129,10 +57,14 @@ public: virtual ~MetricsBackend() {} + typedef std::initializer_list<std::pair<const std::string, std::string>> Labels; + virtual MetricCounterPtr addCounter( - const std::string &name, const std::string &help_str); + const std::string &name, const std::string &help_str, + Labels labels = {}); virtual MetricGaugePtr addGauge( - const std::string &name, const std::string &help_str); + const std::string &name, const std::string &help_str, + Labels labels = {}); }; #if USE_PROMETHEUS diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 702ddce95..aa3bb843d 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -46,11 +46,22 @@ void myrand_bytes(void *out, size_t len) g_pcgrand.bytes(out, len); } +float myrand_float() +{ + u32 uv = g_pcgrand.next(); + return (float)uv / (float)U32_MAX; +} + int myrand_range(int min, int max) { return g_pcgrand.range(min, max); } +float myrand_range(float min, float max) +{ + return (max-min) * myrand_float() + min; +} + /* 64-bit unaligned version of MurmurHash diff --git a/src/util/numeric.h b/src/util/numeric.h index 32a6f4312..265046a63 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -223,6 +223,8 @@ u32 myrand(); void mysrand(unsigned int seed); void myrand_bytes(void *out, size_t len); int myrand_range(int min, int max); +float myrand_range(float min, float max); +float myrand_float(); /* Miscellaneous functions @@ -446,3 +448,24 @@ inline irr::video::SColor multiplyColorValue(const irr::video::SColor &color, fl core::clamp<u32>(color.getGreen() * mod, 0, 255), core::clamp<u32>(color.getBlue() * mod, 0, 255)); } + +template <typename T> inline T numericAbsolute(T v) { return v < 0 ? T(-v) : v; } +template <typename T> inline T numericSign(T v) { return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); } + +inline v3f vecAbsolute(v3f v) +{ + return v3f( + numericAbsolute(v.X), + numericAbsolute(v.Y), + numericAbsolute(v.Z) + ); +} + +inline v3f vecSign(v3f v) +{ + return v3f( + numericSign(v.X), + numericSign(v.Y), + numericSign(v.Z) + ); +} diff --git a/src/util/pointer.h b/src/util/pointer.h index 245ac85bf..b659cea0e 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -45,7 +45,7 @@ public: Buffer() { m_size = 0; - data = NULL; + data = nullptr; } Buffer(unsigned int size) { @@ -53,7 +53,7 @@ public: if(size != 0) data = new T[size]; else - data = NULL; + data = nullptr; } // Disable class copy @@ -82,7 +82,7 @@ public: memcpy(data, t, size); } else - data = NULL; + data = nullptr; } ~Buffer() @@ -166,7 +166,7 @@ public: if(m_size != 0) data = new T[m_size]; else - data = NULL; + data = nullptr; refcount = new unsigned int; memset(data,0,sizeof(T)*m_size); (*refcount) = 1; @@ -201,7 +201,7 @@ public: memcpy(data, t, m_size); } else - data = NULL; + data = nullptr; refcount = new unsigned int; (*refcount) = 1; } @@ -216,7 +216,7 @@ public: memcpy(data, *buffer, buffer.getSize()); } else - data = NULL; + data = nullptr; refcount = new unsigned int; (*refcount) = 1; } @@ -256,3 +256,4 @@ private: unsigned int m_size; unsigned int *refcount; }; + diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 281061229..ee46fd941 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -18,15 +18,14 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "serialize.h" -#include "pointer.h" #include "porting.h" #include "util/string.h" +#include "util/hex.h" #include "exceptions.h" #include "irrlichttypes.h" -#include <sstream> -#include <iomanip> -#include <vector> +#include <iostream> +#include <cassert> FloatType g_serialize_f32_type = FLOATTYPE_UNKNOWN; @@ -120,121 +119,148 @@ std::string deSerializeString32(std::istream &is) } //// -//// JSON +//// JSON-like strings //// std::string serializeJsonString(const std::string &plain) { - std::ostringstream os(std::ios::binary); - os << "\""; + std::string tmp; + + tmp.reserve(plain.size() + 2); + tmp.push_back('"'); for (char c : plain) { switch (c) { case '"': - os << "\\\""; + tmp.append("\\\""); break; case '\\': - os << "\\\\"; - break; - case '/': - os << "\\/"; + tmp.append("\\\\"); break; case '\b': - os << "\\b"; + tmp.append("\\b"); break; case '\f': - os << "\\f"; + tmp.append("\\f"); break; case '\n': - os << "\\n"; + tmp.append("\\n"); break; case '\r': - os << "\\r"; + tmp.append("\\r"); break; case '\t': - os << "\\t"; + tmp.append("\\t"); break; default: { if (c >= 32 && c <= 126) { - os << c; + tmp.push_back(c); } else { - u32 cnum = (u8)c; - os << "\\u" << std::hex << std::setw(4) - << std::setfill('0') << cnum; + // We pretend that Unicode codepoints map to bytes (they don't) + u8 cnum = static_cast<u8>(c); + tmp.append("\\u00"); + tmp.push_back(hex_chars[cnum >> 4]); + tmp.push_back(hex_chars[cnum & 0xf]); } break; } } } - os << "\""; - return os.str(); + tmp.push_back('"'); + return tmp; +} + +static void deSerializeJsonString(std::string &s) +{ + assert(s.size() >= 2); + assert(s.front() == '"' && s.back() == '"'); + + size_t w = 0; // write index + size_t i = 1; // read index + const size_t len = s.size() - 1; // string length with trailing quote removed + + while (i < len) { + char c = s[i++]; + assert(c != '"'); + + if (c != '\\') { + s[w++] = c; + continue; + } + + if (i >= len) + throw SerializationError("JSON string ended prematurely"); + char c2 = s[i++]; + switch (c2) { + case 'b': + s[w++] = '\b'; + break; + case 'f': + s[w++] = '\f'; + break; + case 'n': + s[w++] = '\n'; + break; + case 'r': + s[w++] = '\r'; + break; + case 't': + s[w++] = '\t'; + break; + case 'u': { + if (i + 3 >= len) + throw SerializationError("JSON string ended prematurely"); + unsigned char v[4] = {}; + for (int j = 0; j < 4; j++) + hex_digit_decode(s[i+j], v[j]); + i += 4; + u32 hexnumber = (v[0] << 12) | (v[1] << 8) | (v[2] << 4) | v[3]; + // Note that this does not work for anything other than ASCII + // but these functions do not actually interact with real JSON input. + s[w++] = (int) hexnumber; + break; + } + default: + s[w++] = c2; + break; + } + } + + assert(w <= i && i <= len); + // Truncate string to current write index + s.resize(w); } std::string deSerializeJsonString(std::istream &is) { - std::ostringstream os(std::ios::binary); - char c, c2; + std::string tmp; + char c; + bool was_backslash = false; // Parse initial doublequote - is >> c; + c = is.get(); if (c != '"') throw SerializationError("JSON string must start with doublequote"); + tmp.push_back(c); - // Parse characters + // Grab the entire json string for (;;) { c = is.get(); if (is.eof()) throw SerializationError("JSON string ended prematurely"); - if (c == '"') { - return os.str(); - } - - if (c == '\\') { - c2 = is.get(); - if (is.eof()) - throw SerializationError("JSON string ended prematurely"); - switch (c2) { - case 'b': - os << '\b'; - break; - case 'f': - os << '\f'; - break; - case 'n': - os << '\n'; - break; - case 'r': - os << '\r'; - break; - case 't': - os << '\t'; - break; - case 'u': { - int hexnumber; - char hexdigits[4 + 1]; - - is.read(hexdigits, 4); - if (is.eof()) - throw SerializationError("JSON string ended prematurely"); - hexdigits[4] = 0; - - std::istringstream tmp_is(hexdigits, std::ios::binary); - tmp_is >> std::hex >> hexnumber; - os << (char)hexnumber; - break; - } - default: - os << c2; - break; - } - } else { - os << c; - } + tmp.push_back(c); + if (was_backslash) + was_backslash = false; + else if (c == '\\') + was_backslash = true; + else if (c == '"') + break; // found end of string } - return os.str(); + deSerializeJsonString(tmp); + return tmp; } std::string serializeJsonStringIfNeeded(const std::string &s) @@ -248,41 +274,21 @@ std::string serializeJsonStringIfNeeded(const std::string &s) std::string deSerializeJsonStringIfNeeded(std::istream &is) { - std::stringstream tmp_os(std::ios_base::binary | std::ios_base::in | std::ios_base::out); - bool expect_initial_quote = true; - bool is_json = false; - bool was_backslash = false; - for (;;) { - char c = is.get(); - if (is.eof()) - break; - - if (expect_initial_quote && c == '"') { - tmp_os << c; - is_json = true; - } else if(is_json) { - tmp_os << c; - if (was_backslash) - was_backslash = false; - else if (c == '\\') - was_backslash = true; - else if (c == '"') - break; // Found end of string - } else { - if (c == ' ') { - // Found end of word - is.unget(); - break; - } - - tmp_os << c; - } - expect_initial_quote = false; - } - if (is_json) { - return deSerializeJsonString(tmp_os); + // Check for initial quote + char c = is.peek(); + if (is.eof()) + return ""; + + if (c == '"') { + // json string: defer to the right implementation + return deSerializeJsonString(is); } - return tmp_os.str(); + // not a json string: + std::string tmp; + std::getline(is, tmp, ' '); + if (!is.eof()) + is.unget(); // we hit a space, put it back + return tmp; } diff --git a/src/util/serialize.h b/src/util/serialize.h index 15bdd050d..4dc1a54c6 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -439,6 +439,16 @@ MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); //// More serialization stuff //// +inline float clampToF1000(float v) +{ + return core::clamp(v, F1000_MIN, F1000_MAX); +} + +inline v3f clampToF1000(v3f v) +{ + return {clampToF1000(v.X), clampToF1000(v.Y), clampToF1000(v.Z)}; +} + // Creates a string with the length as the first two bytes std::string serializeString16(const std::string &plain); diff --git a/src/util/srp.cpp b/src/util/srp.cpp index ceb2fef9e..daa7f332b 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -354,7 +354,7 @@ static size_t hash_length(SRP_HashAlgorithm alg) case SRP_SHA384: return SHA384_DIGEST_LENGTH; case SRP_SHA512: return SHA512_DIGEST_LENGTH; */ - default: return -1; + default: return 0; }; } // clang-format on @@ -422,7 +422,7 @@ static SRP_Result H_nn( } static SRP_Result H_ns(mpz_t result, SRP_HashAlgorithm alg, const unsigned char *n, - size_t len_n, const unsigned char *bytes, uint32_t len_bytes) + size_t len_n, const unsigned char *bytes, size_t len_bytes) { unsigned char buff[SHA512_DIGEST_LENGTH]; size_t nbytes = len_n + len_bytes; diff --git a/src/util/stream.h b/src/util/stream.h new file mode 100644 index 000000000..2e61b46d2 --- /dev/null +++ b/src/util/stream.h @@ -0,0 +1,70 @@ +/* +Minetest +Copyright (C) 2022 Minetest Authors + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include <iostream> +#include <string> +#include <functional> + +template<int BufferLength, typename Emitter = std::function<void(const std::string &)> > +class StringStreamBuffer : public std::streambuf { +public: + StringStreamBuffer(Emitter emitter) : m_emitter(emitter) { + buffer_index = 0; + } + + int overflow(int c) { + push_back(c); + return c; + } + + void push_back(char c) { + if (c == '\n' || c == '\r') { + if (buffer_index) + m_emitter(std::string(buffer, buffer_index)); + buffer_index = 0; + } else { + buffer[buffer_index++] = c; + if (buffer_index >= BufferLength) { + m_emitter(std::string(buffer, buffer_index)); + buffer_index = 0; + } + } + } + + std::streamsize xsputn(const char *s, std::streamsize n) { + for (int i = 0; i < n; ++i) + push_back(s[i]); + return n; + } +private: + Emitter m_emitter; + char buffer[BufferLength]; + int buffer_index; +}; + +class DummyStreamBuffer : public std::streambuf { + int overflow(int c) { + return c; + } + std::streamsize xsputn(const char *s, std::streamsize n) { + return n; + } +}; diff --git a/src/util/string.cpp b/src/util/string.cpp index bc4664997..778e4d1e1 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -39,16 +39,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <windows.h> #endif -#ifdef __NetBSD__ - #include <sys/param.h> - #if __NetBSD_Version__ <= 999001500 - #define BSD_ICONV_USED - #endif -#elif defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__OpenBSD__) || \ - defined(__DragonFly__)) - #define BSD_ICONV_USED -#endif - #ifndef _WIN32 static bool convert(const char *to, const char *from, char *outbuf, @@ -56,11 +46,7 @@ static bool convert(const char *to, const char *from, char *outbuf, { iconv_t cd = iconv_open(to, from); -#ifdef BSD_ICONV_USED - const char *inbuf_ptr = inbuf; -#else char *inbuf_ptr = inbuf; -#endif char *outbuf_ptr = outbuf; size_t *inbuf_left_ptr = &inbuf_size; @@ -84,7 +70,7 @@ static bool convert(const char *to, const char *from, char *outbuf, #ifdef __ANDROID__ // On Android iconv disagrees how big a wchar_t is for whatever reason const char *DEFAULT_ENCODING = "UTF-32LE"; -#elif defined(__NetBSD__) +#elif defined(__NetBSD__) || defined(__OpenBSD__) // NetBSD does not allow "WCHAR_T" as a charset input to iconv. #include <sys/endian.h> #if BYTE_ORDER == BIG_ENDIAN @@ -107,8 +93,8 @@ std::wstring utf8_to_wide(const std::string &input) std::wstring out; out.resize(outbuf_size / sizeof(wchar_t)); -#if defined(__ANDROID__) || defined(__NetBSD__) - SANITY_CHECK(sizeof(wchar_t) == 4); +#if defined(__ANDROID__) || defined(__NetBSD__) || defined(__OpenBSD__) + static_assert(sizeof(wchar_t) == 4, "Unexpected wide char size"); #endif char *outbuf = reinterpret_cast<char*>(&out[0]); @@ -494,6 +480,7 @@ const static std::unordered_map<std::string, u32> s_named_colors = { {"plum", 0xdda0dd}, {"powderblue", 0xb0e0e6}, {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, {"red", 0xff0000}, {"rosybrown", 0xbc8f8f}, {"royalblue", 0x4169e1}, @@ -821,9 +808,11 @@ std::wstring translate_string(const std::wstring &s) #endif } -static const std::array<std::wstring, 22> disallowed_dir_names = { +static const std::array<std::wstring, 30> disallowed_dir_names = { // Problematic filenames from here: // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names + // Plus undocumented values from here: + // https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html L"CON", L"PRN", L"AUX", @@ -837,6 +826,9 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { L"COM7", L"COM8", L"COM9", + L"COM\u00B2", + L"COM\u00B3", + L"COM\u00B9", L"LPT1", L"LPT2", L"LPT3", @@ -846,6 +838,11 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { L"LPT7", L"LPT8", L"LPT9", + L"LPT\u00B2", + L"LPT\u00B3", + L"LPT\u00B9", + L"CONIN$", + L"CONOUT$", }; /** @@ -853,12 +850,7 @@ static const std::array<std::wstring, 22> disallowed_dir_names = { */ static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*."; -/** - * Sanitize the name of a new directory. This consists of two stages: - * 1. Check for 'reserved filenames' that can't be used on some filesystems - * and add a prefix to them - * 2. Remove 'unsafe' characters from the name by replacing them with '_' - */ + std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix) { std::wstring safe_name = utf8_to_wide(str); @@ -870,7 +862,18 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ } } - for (unsigned long i = 0; i < safe_name.length(); i++) { + // Replace leading and trailing spaces with underscores. + size_t start = safe_name.find_first_not_of(L' '); + size_t end = safe_name.find_last_not_of(L' '); + if (start == std::wstring::npos || end == std::wstring::npos) + start = end = safe_name.size(); + for (size_t i = 0; i < start; i++) + safe_name[i] = L'_'; + for (size_t i = end + 1; i < safe_name.size(); i++) + safe_name[i] = L'_'; + + // Replace other disallowed characters with underscores + for (size_t i = 0; i < safe_name.length(); i++) { bool is_valid = true; // Unlikely, but control characters should always be blacklisted @@ -882,7 +885,7 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ } if (!is_valid) - safe_name[i] = '_'; + safe_name[i] = L'_'; } return wide_to_utf8(safe_name); diff --git a/src/util/string.h b/src/util/string.h index d73540ceb..aa4329f2f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -410,7 +410,7 @@ DEFINE_STD_TOSTRING_FLOATINGPOINT(long double) template <typename T> inline wstring to_wstring(T val) { - return utf8_to_wide(to_string(val)); + return utf8_to_wide(to_string(val)); } } #endif @@ -749,7 +749,7 @@ inline irr::core::stringw utf8_to_stringw(const std::string &input) /** * Sanitize the name of a new directory. This consists of two stages: * 1. Check for 'reserved filenames' that can't be used on some filesystems - * and prefix them + * and add a prefix to them * 2. Remove 'unsafe' characters from the name by replacing them with '_' */ std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix); |