diff options
author | Elias Fleckenstein <eliasfleckenstein@web.de> | 2021-09-19 20:56:13 +0200 |
---|---|---|
committer | Elias Fleckenstein <eliasfleckenstein@web.de> | 2021-09-19 20:56:13 +0200 |
commit | c8900e169a1ddceec07a449f1ae7c4322ff02036 (patch) | |
tree | 5156605fb473d25786426eb6876ba2e7d3b7507b /src | |
parent | 950d2c9b3e10cbace9236e820c8119d1abb9e01f (diff) | |
parent | e0529da5c84f224c380e6d5e063392cb01f85683 (diff) | |
download | dragonfireclient-c8900e169a1ddceec07a449f1ae7c4322ff02036.tar.xz |
Merge branch 'master' of https://github.com/minetest/minetest
Diffstat (limited to 'src')
184 files changed, 5150 insertions, 1590 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9526e88f9..dc2072d11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -203,6 +203,7 @@ endif(ENABLE_REDIS) find_package(SQLite3 REQUIRED) + OPTION(ENABLE_PROMETHEUS "Enable prometheus client support" FALSE) set(USE_PROMETHEUS FALSE) @@ -239,6 +240,10 @@ if(ENABLE_SPATIAL) endif(ENABLE_SPATIAL) +find_package(ZLIB REQUIRED) +find_package(Zstd REQUIRED) + + if(NOT MSVC) set(USE_GPROF FALSE CACHE BOOL "Use -pg flag for g++") endif() @@ -267,13 +272,10 @@ if(WIN32) endif() set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib ${PLATFORM_LIBS}) - # Zlib stuff - find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory") - find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library") - - # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON + # DLLs are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON if(NOT VCPKG_APPLOCAL_DEPS) - find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)") + find_file(ZLIB_DLL NAMES "" DOC "Path to Zlib DLL for installation (optional)") + find_file(ZSTD_DLL NAMES "" DOC "Path to Zstd DLL for installation (optional)") if(ENABLE_SOUND) set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") @@ -293,35 +295,8 @@ else() if(NOT HAIKU AND NOT APPLE) find_package(X11 REQUIRED) endif(NOT HAIKU AND NOT APPLE) + endif() - ## - # The following dependencies are transitive dependencies from Irrlicht. - # Minetest itself does not use them, but we link them so that statically - # linking Irrlicht works. - if(NOT HAIKU AND NOT APPLE) - # This way Xxf86vm is found on OpenBSD too - find_library(XXF86VM_LIBRARY Xxf86vm) - mark_as_advanced(XXF86VM_LIBRARY) - set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) - endif(NOT HAIKU AND NOT APPLE) - - find_package(JPEG REQUIRED) - find_package(PNG REQUIRED) - if(APPLE) - find_library(CARBON_LIB Carbon REQUIRED) - find_library(COCOA_LIB Cocoa REQUIRED) - find_library(IOKIT_LIB IOKit REQUIRED) - mark_as_advanced( - CARBON_LIB - COCOA_LIB - IOKIT_LIB - ) - SET(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${CARBON_LIB} ${COCOA_LIB} ${IOKIT_LIB}) - endif(APPLE) - ## - endif(BUILD_CLIENT) - - find_package(ZLIB REQUIRED) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) if(APPLE) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) @@ -511,9 +486,8 @@ endif() include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} - ${IRRLICHT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} - ${PNG_INCLUDE_DIR} + ${ZSTD_INCLUDE_DIR} ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} @@ -548,10 +522,8 @@ if(BUILD_CLIENT) target_link_libraries( ${PROJECT_NAME} ${ZLIB_LIBRARIES} - ${IRRLICHT_LIBRARY} - ${JPEG_LIBRARIES} - ${BZIP2_LIBRARIES} - ${PNG_LIBRARIES} + IrrlichtMt::IrrlichtMt + ${ZSTD_LIBRARY} ${X11_LIBRARIES} ${SOUND_LIBRARIES} ${SQLITE3_LIBRARY} @@ -559,7 +531,6 @@ if(BUILD_CLIENT) ${GMP_LIBRARY} ${JSON_LIBRARY} ${PLATFORM_LIBS} - ${CLIENT_PLATFORM_LIBS} ) if(NOT USE_LUAJIT) set_target_properties(${PROJECT_NAME} PROPERTIES @@ -629,9 +600,15 @@ endif(BUILD_CLIENT) if(BUILD_SERVER) add_executable(${PROJECT_NAME}server ${server_SRCS} ${extra_windows_SRCS}) add_dependencies(${PROJECT_NAME}server GenerateVersion) + + get_target_property( + IRRLICHT_INCLUDES IrrlichtMt::IrrlichtMt INTERFACE_INCLUDE_DIRECTORIES) + # Doesn't work without PRIVATE/PUBLIC/INTERFACE mode specified. + target_include_directories(${PROJECT_NAME}server PRIVATE ${IRRLICHT_INCLUDES}) target_link_libraries( ${PROJECT_NAME}server ${ZLIB_LIBRARIES} + ${ZSTD_LIBRARY} ${SQLITE3_LIBRARY} ${JSON_LIBRARY} ${LUA_LIBRARY} @@ -685,12 +662,11 @@ set(GETTEXT_BLACKLISTED_LOCALES he hi kn - ky ms_Arab th ) -option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE) +option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid known broken locales" TRUE) if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST) set(GETTEXT_USED_LOCALES "") @@ -700,6 +676,8 @@ if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST) endif() endforeach() message(STATUS "Locale blacklist applied; Locales used: ${GETTEXT_USED_LOCALES}") +elseif (GETTEXTLIB_FOUND) + set(GETTEXT_USED_LOCALES ${GETTEXT_AVAILABLE_LOCALES}) endif() # Set some optimizations and tweaks @@ -758,7 +736,16 @@ else() check_c_source_compiles("#ifndef __aarch64__\n#error\n#endif\nint main(){}" IS_AARCH64) if(IS_AARCH64) # Move text segment below LuaJIT's 47-bit limit (see issue #9367) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000") + 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 + # 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 + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--image-base=0x200000000") + else() + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000") + endif() endif() endif() @@ -838,6 +825,9 @@ if(WIN32) if(ZLIB_DLL) install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR}) endif() + if(ZSTD_DLL) + install(FILES ${ZSTD_DLL} DESTINATION ${BINDIR}) + endif() if(BUILD_CLIENT AND FREETYPE_DLL) install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR}) endif() diff --git a/src/chat.cpp b/src/chat.cpp index c9317a079..162622abe 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -35,6 +35,17 @@ ChatBuffer::ChatBuffer(u32 scrollback): if (m_scrollback == 0) m_scrollback = 1; m_empty_formatted_line.first = true; + + m_cache_clickable_chat_weblinks = false; + // Curses mode cannot access g_settings here + if (g_settings != nullptr) { + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); + if (m_cache_clickable_chat_weblinks) { + std::string colorval = g_settings->get("chat_weblink_color"); + parseColorString(colorval, m_cache_chat_weblink_color, false, 255); + m_cache_chat_weblink_color.setAlpha(255); + } + } } void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text) @@ -263,78 +274,144 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, //EnrichedString line_text(line.text); next_line.first = true; - bool text_processing = false; + // Set/use forced newline after the last frag in each line + bool mark_newline = false; // Produce fragments and layout them into lines - while (!next_frags.empty() || in_pos < line.text.size()) - { + while (!next_frags.empty() || in_pos < line.text.size()) { + mark_newline = false; // now using this to USE line-end frag + // Layout fragments into lines - while (!next_frags.empty()) - { + while (!next_frags.empty()) { ChatFormattedFragment& frag = next_frags[0]; - if (frag.text.size() <= cols - out_column) - { + + // Force newline after this frag, if marked + if (frag.column == INT_MAX) + mark_newline = true; + + if (frag.text.size() <= cols - out_column) { // Fragment fits into current line frag.column = out_column; next_line.fragments.push_back(frag); out_column += frag.text.size(); next_frags.erase(next_frags.begin()); - } - else - { + } else { // Fragment does not fit into current line // So split it up temp_frag.text = frag.text.substr(0, cols - out_column); temp_frag.column = out_column; - //temp_frag.bold = frag.bold; + temp_frag.weblink = frag.weblink; + next_line.fragments.push_back(temp_frag); frag.text = frag.text.substr(cols - out_column); + frag.column = 0; out_column = cols; } - if (out_column == cols || text_processing) - { + + if (out_column == cols || mark_newline) { // End the current line destination.push_back(next_line); num_added++; next_line.fragments.clear(); next_line.first = false; - out_column = text_processing ? hanging_indentation : 0; + out_column = hanging_indentation; + mark_newline = false; } } - // Produce fragment - if (in_pos < line.text.size()) - { - u32 remaining_in_input = line.text.size() - in_pos; - u32 remaining_in_output = cols - out_column; + // Produce fragment(s) for next formatted line + if (!(in_pos < line.text.size())) + continue; + const std::wstring &linestring = line.text.getString(); + u32 remaining_in_output = cols - out_column; + size_t http_pos = std::wstring::npos; + mark_newline = false; // now using this to SET line-end frag + + // Construct all frags for next output line + while (!mark_newline) { // Determine a fragment length <= the minimum of // remaining_in_{in,out}put. Try to end the fragment // on a word boundary. - u32 frag_length = 1, space_pos = 0; + u32 frag_length = 0, space_pos = 0; + u32 remaining_in_input = line.text.size() - in_pos; + + if (m_cache_clickable_chat_weblinks) { + // Note: unsigned(-1) on fail + http_pos = linestring.find(L"https://", in_pos); + if (http_pos == std::wstring::npos) + http_pos = linestring.find(L"http://", in_pos); + if (http_pos != std::wstring::npos) + http_pos -= in_pos; + } + while (frag_length < remaining_in_input && - frag_length < remaining_in_output) - { - if (iswspace(line.text.getString()[in_pos + frag_length])) + frag_length < remaining_in_output) { + if (iswspace(linestring[in_pos + frag_length])) space_pos = frag_length; ++frag_length; } + + if (http_pos >= remaining_in_output) { + // Http not in range, grab until space or EOL, halt as normal. + // Note this works because (http_pos = npos) is unsigned(-1) + + mark_newline = true; + } else if (http_pos == 0) { + // At http, grab ALL until FIRST whitespace or end marker. loop. + // If at end of string, next loop will be empty string to mark end of weblink. + + frag_length = 6; // Frag is at least "http://" + + // Chars to mark end of weblink + // TODO? replace this with a safer (slower) regex whitelist? + static const std::wstring delim_chars = L"\'\";,"; + wchar_t tempchar = linestring[in_pos+frag_length]; + while (frag_length < remaining_in_input && + !iswspace(tempchar) && + delim_chars.find(tempchar) == std::wstring::npos) { + ++frag_length; + tempchar = linestring[in_pos+frag_length]; + } + + space_pos = frag_length - 1; + // This frag may need to be force-split. That's ok, urls aren't "words" + if (frag_length >= remaining_in_output) { + mark_newline = true; + } + } else { + // Http in range, grab until http, loop + + space_pos = http_pos - 1; + frag_length = http_pos; + } + + // Include trailing space in current frag if (space_pos != 0 && frag_length < remaining_in_input) frag_length = space_pos + 1; temp_frag.text = line.text.substr(in_pos, frag_length); - temp_frag.column = 0; - //temp_frag.bold = 0; + // A hack so this frag remembers mark_newline for the layout phase + temp_frag.column = mark_newline ? INT_MAX : 0; + + if (http_pos == 0) { + // Discard color stuff from the source frag + temp_frag.text = EnrichedString(temp_frag.text.getString()); + temp_frag.text.setDefaultColor(m_cache_chat_weblink_color); + // Set weblink in the frag meta + temp_frag.weblink = wide_to_utf8(temp_frag.text.getString()); + } else { + temp_frag.weblink.clear(); + } next_frags.push_back(temp_frag); in_pos += frag_length; - text_processing = true; + remaining_in_output -= std::min(frag_length, remaining_in_output); } } // End the last line - if (num_added == 0 || !next_line.fragments.empty()) - { + if (num_added == 0 || !next_line.fragments.empty()) { destination.push_back(next_line); num_added++; } diff --git a/src/chat.h b/src/chat.h index 0b98e4d3c..aabb0821e 100644 --- a/src/chat.h +++ b/src/chat.h @@ -57,6 +57,8 @@ struct ChatFormattedFragment EnrichedString text; // starting column u32 column; + // web link is empty for most frags + std::string weblink; // formatting //u8 bold:1; }; @@ -118,6 +120,7 @@ public: std::vector<ChatFormattedLine>& destination) const; void resize(u32 scrollback); + protected: s32 getTopScrollPos() const; s32 getBottomScrollPos() const; @@ -138,6 +141,11 @@ private: std::vector<ChatFormattedLine> m_formatted; // Empty formatted line, for error returns ChatFormattedLine m_empty_formatted_line; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; + // Color of clickable chat weblinks + irr::video::SColor m_cache_chat_weblink_color; }; class ChatPrompt diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 140814911..8d058852a 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -58,5 +58,9 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sky.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp ${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 PARENT_SCOPE ) diff --git a/src/client/client.cpp b/src/client/client.cpp index 57f8e6593..3c4ea5f95 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -178,11 +178,7 @@ void Client::loadMods() // Load "mod" scripts for (const ModSpec &mod : m_mods) { - if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { - throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming conventions: " - "Only characters [a-z0-9_] are allowed."); - } + mod.checkAndLog(); scanModIntoMemory(mod.name, mod.path); } @@ -213,6 +209,9 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo std::string full_path = mod_path + DIR_DELIM + mod_subpath; std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path); for (const fs::DirListNode &j : mod) { + if (j.name[0] == '.') + continue; + if (j.dir) { scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM); continue; @@ -559,6 +558,29 @@ void Client::step(float dtime) m_media_downloader = NULL; } } + { + // Acknowledge dynamic media downloads to server + std::vector<u32> done; + for (auto it = m_pending_media_downloads.begin(); + it != m_pending_media_downloads.end();) { + assert(it->second->isStarted()); + it->second->step(this); + if (it->second->isDone()) { + done.emplace_back(it->first); + + it = m_pending_media_downloads.erase(it); + } else { + it++; + } + + if (done.size() == 255) { // maximum in one packet + sendHaveMedia(done); + done.clear(); + } + } + if (!done.empty()) + sendHaveMedia(done); + } /* If the server didn't update the inventory in a while, revert @@ -774,7 +796,8 @@ void Client::request_media(const std::vector<std::string> &file_requests) Send(&pkt); infostream << "Client: Sending media request list to server (" - << file_requests.size() << " files. packet size)" << std::endl; + << file_requests.size() << " files, packet size " + << pkt.getSize() << ")" << std::endl; } void Client::initLocalMapSaving(const Address &address, @@ -1307,6 +1330,19 @@ void Client::sendPlayerPos() sendPlayerPos(player->getLegitPosition()); } +void Client::sendHaveMedia(const std::vector<u32> &tokens) +{ + NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4); + + sanity_check(tokens.size() < 256); + + pkt << static_cast<u8>(tokens.size()); + for (u32 token : tokens) + pkt << token; + + Send(&pkt); +} + void Client::removeNode(v3s16 p) { std::map<v3s16, MapBlock*> modified_blocks; @@ -1679,13 +1715,13 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -typedef struct TextureUpdateArgs { +struct TextureUpdateArgs { gui::IGUIEnvironment *guienv; u64 last_time_ms; u16 last_percent; const wchar_t* text_base; ITextureSource *tsrc; -} TextureUpdateArgs; +}; void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress) { @@ -1704,8 +1740,8 @@ void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progres if (do_draw) { targs->last_time_ms = time_ms; - std::basic_stringstream<wchar_t> strm; - strm << targs->text_base << " " << targs->last_percent << "%..."; + std::wostringstream strm; + strm << targs->text_base << L" " << targs->last_percent << L"%..."; m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); } @@ -1758,7 +1794,7 @@ void Client::afterContentReceived() tu_args.guienv = guienv; tu_args.last_time_ms = porting::getTimeMs(); tu_args.last_percent = 0; - tu_args.text_base = wgettext("Initializing nodes"); + tu_args.text_base = wgettext("Initializing nodes"); tu_args.tsrc = m_tsrc; m_nodedef->updateTextures(this, &tu_args); delete[] tu_args.text_base; diff --git a/src/client/client.h b/src/client/client.h index 8d7f63c0c..1493d3ce3 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -52,6 +52,7 @@ class ISoundManager; class NodeDefManager; //class IWritableCraftDefManager; class ClientMediaDownloader; +class SingleMediaDownloader; struct MapDrawControl; class ModChannelMgr; class MtEventManager; @@ -244,6 +245,7 @@ public: void sendDamage(u16 damage); void sendRespawn(); void sendReady(); + void sendHaveMedia(const std::vector<u32> &tokens); ClientEnvironment& getEnv() { return m_env; } ITextureSource *tsrc() { return getTextureSource(); } @@ -324,6 +326,10 @@ public: m_access_denied = true; m_access_denied_reason = reason; } + inline void setFatalError(const LuaError &e) + { + setFatalError(std::string("Lua: ") + e.what()); + } // Renaming accessDeniedReason to better name could be good as it's used to // disconnect client when CSM failed. @@ -542,9 +548,13 @@ private: bool m_activeobjects_received = false; bool m_mods_loaded = false; + std::vector<std::string> m_remote_media_servers; + // Media downloader, only exists during init ClientMediaDownloader *m_media_downloader; // Set of media filenames pushed by server at runtime std::unordered_set<std::string> m_media_pushed_files; + // Pending downloads of dynamic media (key: token) + std::vector<std::pair<u32, std::unique_ptr<SingleMediaDownloader>>> m_pending_media_downloads; // time_of_day speed approximation for old protocol bool m_time_of_day_set = false; diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 2215aecbd..17d3aedd6 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -59,7 +59,7 @@ struct ClientEventHudAdd v2f pos, scale; std::string name; std::string text, text2; - u32 number, item, dir; + u32 number, item, dir, style; v2f align, offset; v3f world_pos; v2s32 size; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 6db5f2e70..6ab610670 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -99,10 +99,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) init_args(start_data, cmd_args); - // List video modes if requested - if (list_video_modes) - return m_rendering_engine->print_video_modes(); - #if USE_SOUND if (g_settings->getBool("enable_sound")) g_sound_manager_singleton = createSoundManagerSingleton(); @@ -277,14 +273,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) chat_backend, &reconnect_requested ); - m_rendering_engine->get_scene_manager()->clear(); - -#ifdef HAVE_TOUCHSCREENGUI - delete g_touchscreengui; - g_touchscreengui = NULL; - receiver->m_touchscreengui = NULL; -#endif - } //try catch (con::PeerNotFoundException &e) { error_message = gettext("Connection error (timed out?)"); @@ -300,6 +288,14 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) } #endif + m_rendering_engine->get_scene_manager()->clear(); + +#ifdef HAVE_TOUCHSCREENGUI + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; +#endif + // If no main menu, show error and exit if (skip_main_menu) { if (!error_message.empty()) { @@ -336,8 +332,6 @@ void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_ar if (cmd_args.exists("name")) start_data.name = cmd_args.get("name"); - list_video_modes = cmd_args.getFlag("videomodes"); - random_input = g_settings->getBool("random_input") || cmd_args.getFlag("random-input"); } @@ -518,8 +512,8 @@ bool ClientLauncher::launch_game(std::string &error_message, // Load gamespec for required game start_data.game_spec = findWorldSubgame(worldspec.path); if (!start_data.game_spec.isValid()) { - error_message = gettext("Could not find or load game \"") - + worldspec.gameid + "\""; + error_message = gettext("Could not find or load game: ") + + worldspec.gameid; errorstream << error_message << std::endl; return false; } diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h index 79727e1fe..d1fd9a258 100644 --- a/src/client/clientlauncher.h +++ b/src/client/clientlauncher.h @@ -46,7 +46,6 @@ private: void speed_tests(); - bool list_video_modes = false; bool skip_main_menu = false; bool random_input = false; RenderingEngine *m_rendering_engine = nullptr; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 29a7fd3ba..4a4784f91 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -72,8 +72,15 @@ ClientMap::ClientMap( scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(), rendering_engine->get_scene_manager(), id), m_client(client), + m_rendering_engine(rendering_engine), m_control(control) { + + /* + * @Liso: Sadly C++ doesn't have introspection, so the only way we have to know + * the class is whith a name ;) Name property cames from ISceneNode base class. + */ + Name = "ClientMap"; m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, BS*1000000,BS*1000000,BS*1000000); @@ -115,12 +122,21 @@ void ClientMap::OnRegisterSceneNode() } ISceneNode::OnRegisterSceneNode(); + + if (!m_added_to_shadow_renderer) { + m_added_to_shadow_renderer = true; + if (auto shadows = m_rendering_engine->get_shadow_renderer()) + shadows->addNodeToShadowList(this); + } } void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max) + v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range) { - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); + if (range <= 0.0f) + range = m_control.wanted_range; + + v3s16 box_nodes_d = range * v3s16(1, 1, 1); // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' // can exceed the range of v3s16 when a large view range is used near the // world edges. @@ -321,7 +337,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) // Mesh animation if (pass == scene::ESNRP_SOLID) { - //MutexAutoLock lock(block->mesh_mutex); MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); // Pretty random but this should work somewhat nicely @@ -342,8 +357,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) Get the meshbuffers of the block */ { - //MutexAutoLock lock(block->mesh_mutex); - MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); @@ -394,6 +407,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) "returning." << std::endl; return; } + + // pass the shadow map texture to the buffer texture + ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer(); + if (shadow && shadow->is_active()) { + auto &layer = list.m.TextureLayer[3]; + 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; + layer.TrilinearFilter = true; + } + driver->setMaterial(list.m); drawcall_count += list.bufs.size(); @@ -610,3 +634,210 @@ void ClientMap::PrintInfo(std::ostream &out) { out<<"ClientMap: "; } + +void ClientMap::renderMapShadows(video::IVideoDriver *driver, + const video::SMaterial &material, s32 pass, int frame, int total_frames) +{ + bool is_transparent_pass = pass != scene::ESNRP_SOLID; + std::string prefix; + if (is_transparent_pass) + prefix = "renderMap(SHADOW TRANS): "; + else + prefix = "renderMap(SHADOW SOLID): "; + + u32 drawcall_count = 0; + u32 vertex_count = 0; + + MeshBufListList drawbufs; + + int count = 0; + int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame; + int high_bound = is_transparent_pass ? m_drawlist_shadow.size() : m_drawlist_shadow.size() / total_frames * (frame + 1); + + // transparent pass should be rendered in one go + if (is_transparent_pass && frame != total_frames - 1) { + return; + } + + for (auto &i : m_drawlist_shadow) { + // only process specific part of the list & break early + ++count; + if (count <= low_bound) + continue; + if (count > high_bound) + break; + + v3s16 block_pos = i.first; + MapBlock *block = i.second; + + // If the mesh of the block happened to get deleted, ignore it + if (!block->mesh) + continue; + + /* + Get the meshbuffers of the block + */ + { + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + scene::IMesh *mesh = mapBlockMesh->getMesh(layer); + assert(mesh); + + u32 c = mesh->getMeshBufferCount(); + for (u32 i = 0; i < c; i++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + + 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); + } + } + } + } + + TimeTaker draw("Drawing shadow mesh buffers"); + + core::matrix4 m; // Model matrix + v3f offset = intToFloat(m_camera_offset, BS); + + // 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(); + } + + drawcall_count += list.bufs.size(); + } + } + + // 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); +} + +/* + 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) +{ + 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 p_blocks_min; + v3s16 p_blocks_max; + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, shadow_range); + + std::vector<v2s16> blocks_in_range; + + for (auto &i : m_drawlist_shadow) { + MapBlock *block = i.second; + block->refDrop(); + } + 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 + u32 blocks_in_range_with_mesh = 0; + // Number of blocks occlusion culled + u32 blocks_occlusion_culled = 0; + + for (auto §or_it : m_sectors) { + MapSector *sector = sector_it.second; + if (!sector) + continue; + blocks_loaded += sector->size(); + + MapBlockVect sectorblocks; + sector->getBlocks(sectorblocks); + + /* + Loop through blocks in sector + */ + for (MapBlock *block : sectorblocks) { + if (!block->mesh) { + // Ignore if mesh doesn't exist + continue; + } + + float range = shadow_range; + + float d = 0.0; + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, range, &d)) + 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(); + + // Add to set + if (m_drawlist_shadow.find(block->getPos()) == m_drawlist_shadow.end()) { + block->refGrab(); + m_drawlist_shadow[block->getPos()] = block; + } + } + } + + g_profiler->avg("SHADOW MapBlock meshes in range [#]", blocks_in_range_with_mesh); + g_profiler->avg("SHADOW MapBlocks occlusion culled [#]", blocks_occlusion_culled); + g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size()); + g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded); +} diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 80add4a44..97ce8d355 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -119,10 +119,14 @@ public: } void getBlocksInViewRange(v3s16 cam_pos_nodes, - v3s16 *p_blocks_min, v3s16 *p_blocks_max); + 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 renderMap(video::IVideoDriver* driver, s32 pass); + void renderMapShadows(video::IVideoDriver *driver, + const video::SMaterial &material, s32 pass, int frame, int total_frames); + int getBackgroundBrightness(float max_d, u32 daylight_factor, int oldvalue, bool *sunlight_seen_result); @@ -132,9 +136,12 @@ public: virtual void PrintInfo(std::ostream &out); const MapDrawControl & getControl() const { return m_control; } + f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } + private: Client *m_client; + RenderingEngine *m_rendering_engine; aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000, BS * 1000000, BS * 1000000, BS * 1000000); @@ -147,10 +154,12 @@ private: v3s16 m_camera_offset; std::map<v3s16, MapBlock*> m_drawlist; + std::map<v3s16, MapBlock*> m_drawlist_shadow; std::set<v2s16> m_last_drawn_sectors; bool m_cache_trilinear_filter; bool m_cache_bilinear_filter; bool m_cache_anistropic_filter; + bool m_added_to_shadow_renderer{false}; }; diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 0f9ba5356..6c5d4a8bf 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -49,7 +49,6 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file */ ClientMediaDownloader::ClientMediaDownloader(): - m_media_cache(getMediaCacheDir()), m_httpfetch_caller(HTTPFETCH_DISCARD) { } @@ -66,6 +65,12 @@ ClientMediaDownloader::~ClientMediaDownloader() delete remote; } +bool ClientMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name); +} + void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1) { assert(!m_initial_step_done); // pre-condition @@ -105,7 +110,7 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) { assert(!m_initial_step_done); // pre-condition - #ifdef USE_CURL +#ifdef USE_CURL if (g_settings->getBool("enable_remote_media_server")) { infostream << "Client: Adding remote server \"" @@ -117,13 +122,13 @@ void ClientMediaDownloader::addRemoteServer(const std::string &baseurl) m_remotes.push_back(remote); } - #else +#else infostream << "Client: Ignoring remote server \"" << baseurl << "\" because cURL support is not compiled in" << std::endl; - #endif +#endif } void ClientMediaDownloader::step(Client *client) @@ -172,36 +177,21 @@ void ClientMediaDownloader::initialStep(Client *client) // Check media cache m_uncached_count = m_files.size(); for (auto &file_it : m_files) { - std::string name = file_it.first; + const std::string &name = file_it.first; FileStatus *filestatus = file_it.second; const std::string &sha1 = filestatus->sha1; - std::ostringstream tmp_os(std::ios_base::binary); - bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); - - // If found in cache, try to load it from there - if (found_in_cache) { - bool success = checkAndLoad(name, sha1, - tmp_os.str(), true, client); - if (success) { - filestatus->received = true; - m_uncached_count--; - } + if (tryLoadFromCache(name, sha1, client)) { + filestatus->received = true; + m_uncached_count--; } } assert(m_uncached_received_count == 0); // Create the media cache dir if we are likely to write to it - if (m_uncached_count != 0) { - bool did = fs::CreateAllDirs(getMediaCacheDir()); - if (!did) { - errorstream << "Client: " - << "Could not create media cache directory: " - << getMediaCacheDir() - << std::endl; - } - } + if (m_uncached_count != 0) + createCacheDirs(); // If we found all files in the cache, report this fact to the server. // If the server reported no remote servers, immediately start @@ -301,8 +291,7 @@ void ClientMediaDownloader::remoteHashSetReceived( // available on this server, add this server // to the available_remotes array - for(std::map<std::string, FileStatus*>::iterator - it = m_files.upper_bound(m_name_bound); + for(auto it = m_files.upper_bound(m_name_bound); it != m_files.end(); ++it) { FileStatus *f = it->second; if (!f->received && sha1_set.count(f->sha1)) @@ -328,8 +317,7 @@ void ClientMediaDownloader::remoteMediaReceived( std::string name; { - std::unordered_map<unsigned long, std::string>::iterator it = - m_remote_file_transfers.find(fetch_result.request_id); + auto it = m_remote_file_transfers.find(fetch_result.request_id); assert(it != m_remote_file_transfers.end()); name = it->second; m_remote_file_transfers.erase(it); @@ -398,8 +386,7 @@ void ClientMediaDownloader::startRemoteMediaTransfers() { bool changing_name_bound = true; - for (std::map<std::string, FileStatus*>::iterator - files_iter = m_files.upper_bound(m_name_bound); + for (auto files_iter = m_files.upper_bound(m_name_bound); files_iter != m_files.end(); ++files_iter) { // Abort if active fetch limit is exceeded @@ -477,19 +464,18 @@ void ClientMediaDownloader::startConventionalTransfers(Client *client) } } -void ClientMediaDownloader::conventionalTransferDone( +bool ClientMediaDownloader::conventionalTransferDone( const std::string &name, const std::string &data, Client *client) { // Check that file was announced - std::map<std::string, FileStatus*>::iterator - file_iter = m_files.find(name); + auto file_iter = m_files.find(name); if (file_iter == m_files.end()) { errorstream << "Client: server sent media file that was" << "not announced, ignoring it: \"" << name << "\"" << std::endl; - return; + return false; } FileStatus *filestatus = file_iter->second; assert(filestatus != NULL); @@ -499,7 +485,7 @@ void ClientMediaDownloader::conventionalTransferDone( errorstream << "Client: server sent media file that we already" << "received, ignoring it: \"" << name << "\"" << std::endl; - return; + return true; } // Mark file as received, regardless of whether loading it works and @@ -512,9 +498,45 @@ void ClientMediaDownloader::conventionalTransferDone( // Check that received file matches announced checksum // If so, load it checkAndLoad(name, filestatus->sha1, data, false, client); + + return true; +} + +/* + IClientMediaDownloader +*/ + +IClientMediaDownloader::IClientMediaDownloader(): + m_media_cache(getMediaCacheDir()), m_write_to_cache(true) +{ } -bool ClientMediaDownloader::checkAndLoad( +void IClientMediaDownloader::createCacheDirs() +{ + if (!m_write_to_cache) + return; + + std::string path = getMediaCacheDir(); + if (!fs::CreateAllDirs(path)) { + errorstream << "Client: Could not create media cache directory: " + << path << std::endl; + } +} + +bool IClientMediaDownloader::tryLoadFromCache(const std::string &name, + const std::string &sha1, Client *client) +{ + std::ostringstream tmp_os(std::ios_base::binary); + bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); + + // If found in cache, try to load it from there + if (found_in_cache) + return checkAndLoad(name, sha1, tmp_os.str(), true, client); + + return false; +} + +bool IClientMediaDownloader::checkAndLoad( const std::string &name, const std::string &sha1, const std::string &data, bool is_from_cache, Client *client) { @@ -544,7 +566,7 @@ bool ClientMediaDownloader::checkAndLoad( } // Checksum is ok, try loading the file - bool success = client->loadMedia(data, name); + bool success = loadMedia(client, data, name); if (!success) { infostream << "Client: " << "Failed to load " << cached_or_received << " media: " @@ -559,7 +581,7 @@ bool ClientMediaDownloader::checkAndLoad( << std::endl; // Update cache (unless we just loaded the file from the cache) - if (!is_from_cache) + if (!is_from_cache && m_write_to_cache) m_media_cache.update(sha1_hex, data); return true; @@ -587,12 +609,10 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() // Write list of hashes of files that have not been // received (found in cache) yet - for (std::map<std::string, FileStatus*>::iterator - it = m_files.begin(); - it != m_files.end(); ++it) { - if (!it->second->received) { - FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); - os << it->second->sha1; + for (const auto &it : m_files) { + if (!it.second->received) { + FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size"); + os << it.second->sha1; } } @@ -628,3 +648,145 @@ void ClientMediaDownloader::deSerializeHashSet(const std::string &data, result.insert(data.substr(pos, 20)); } } + +/* + SingleMediaDownloader +*/ + +SingleMediaDownloader::SingleMediaDownloader(bool write_to_cache): + m_httpfetch_caller(HTTPFETCH_DISCARD) +{ + m_write_to_cache = write_to_cache; +} + +SingleMediaDownloader::~SingleMediaDownloader() +{ + if (m_httpfetch_caller != HTTPFETCH_DISCARD) + httpfetch_caller_free(m_httpfetch_caller); +} + +bool SingleMediaDownloader::loadMedia(Client *client, const std::string &data, + const std::string &name) +{ + return client->loadMedia(data, name, true); +} + +void SingleMediaDownloader::addFile(const std::string &name, const std::string &sha1) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + assert(!name.empty()); + assert(sha1.size() == 20); + + FATAL_ERROR_IF(!m_file_name.empty(), "Cannot add a second file"); + m_file_name = name; + m_file_sha1 = sha1; +} + +void SingleMediaDownloader::addRemoteServer(const std::string &baseurl) +{ + assert(m_stage == STAGE_INIT); // pre-condition + + if (g_settings->getBool("enable_remote_media_server")) + m_remotes.emplace_back(baseurl); +} + +void SingleMediaDownloader::step(Client *client) +{ + if (m_stage == STAGE_INIT) { + m_stage = STAGE_CACHE_CHECKED; + initialStep(client); + } + + // Remote media: check for completion of fetches + if (m_httpfetch_caller != HTTPFETCH_DISCARD) { + HTTPFetchResult fetch_result; + while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { + remoteMediaReceived(fetch_result, client); + } + } +} + +bool SingleMediaDownloader::conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) +{ + if (name != m_file_name) + return false; + + // Mark file as received unconditionally and try to load it + m_stage = STAGE_DONE; + checkAndLoad(name, m_file_sha1, data, false, client); + return true; +} + +void SingleMediaDownloader::initialStep(Client *client) +{ + if (tryLoadFromCache(m_file_name, m_file_sha1, client)) + m_stage = STAGE_DONE; + if (isDone()) + return; + + createCacheDirs(); + + // If the server reported no remote servers, immediately fall back to + // conventional transfer. + if (!USE_CURL || m_remotes.empty()) { + startConventionalTransfer(client); + } else { + // Otherwise start by requesting the file from the first remote media server + m_httpfetch_caller = httpfetch_caller_alloc(); + m_current_remote = 0; + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::remoteMediaReceived( + const HTTPFetchResult &fetch_result, Client *client) +{ + sanity_check(!isDone()); + sanity_check(m_current_remote >= 0); + + // If fetch succeeded, try to load it + if (fetch_result.succeeded) { + bool success = checkAndLoad(m_file_name, m_file_sha1, + fetch_result.data, false, client); + if (success) { + m_stage = STAGE_DONE; + return; + } + } + + // Otherwise try the next remote server or fall back to conventional transfer + m_current_remote++; + if (m_current_remote >= (int)m_remotes.size()) { + infostream << "Client: Failed to remote-fetch \"" << m_file_name + << "\". Requesting it the usual way." << std::endl; + m_current_remote = -1; + startConventionalTransfer(client); + } else { + startRemoteMediaTransfer(); + } +} + +void SingleMediaDownloader::startRemoteMediaTransfer() +{ + std::string url = m_remotes.at(m_current_remote) + hex_encode(m_file_sha1); + verbosestream << "Client: Requesting remote media file " + << "\"" << m_file_name << "\" " << "\"" << url << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = url; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; + fetch_request.timeout = g_settings->getS32("curl_file_download_timeout"); + httpfetch_async(fetch_request); + + m_httpfetch_next_id++; +} + +void SingleMediaDownloader::startConventionalTransfer(Client *client) +{ + std::vector<std::string> requests; + requests.emplace_back(m_file_name); + client->request_media(requests); +} diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index e97a0f24b..aa7b0f398 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "filecache.h" +#include "util/basic_macros.h" #include <ostream> #include <map> #include <set> @@ -38,7 +39,62 @@ struct HTTPFetchResult; bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata); -class ClientMediaDownloader +// more of a base class than an interface but this name was most convenient... +class IClientMediaDownloader +{ +public: + DISABLE_CLASS_COPY(IClientMediaDownloader) + + virtual bool isStarted() const = 0; + + // If this returns true, the downloader is done and can be deleted + virtual bool isDone() const = 0; + + // Add a file to the list of required file (but don't fetch it yet) + virtual void addFile(const std::string &name, const std::string &sha1) = 0; + + // Add a remote server to the list; ignored if not built with cURL + virtual void addRemoteServer(const std::string &baseurl) = 0; + + // Steps the media downloader: + // - May load media into client by calling client->loadMedia() + // - May check media cache for files + // - May add files to media cache + // - May start remote transfers by calling httpfetch_async + // - May check for completion of current remote transfers + // - May start conventional transfers by calling client->request_media() + // - May inform server that all media has been loaded + // by calling client->received_media() + // After step has been called once, don't call addFile/addRemoteServer. + virtual void step(Client *client) = 0; + + // Must be called for each file received through TOCLIENT_MEDIA + // returns true if this file belongs to this downloader + virtual bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) = 0; + +protected: + IClientMediaDownloader(); + virtual ~IClientMediaDownloader() = default; + + // Forwards the call to the appropriate Client method + virtual bool loadMedia(Client *client, const std::string &data, + const std::string &name) = 0; + + void createCacheDirs(); + + bool tryLoadFromCache(const std::string &name, const std::string &sha1, + Client *client); + + bool checkAndLoad(const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, Client *client); + + // Filesystem-based media cache + FileCache m_media_cache; + bool m_write_to_cache; +}; + +class ClientMediaDownloader : public IClientMediaDownloader { public: ClientMediaDownloader(); @@ -52,39 +108,29 @@ public: return 0.0f; } - bool isStarted() const { + bool isStarted() const override { return m_initial_step_done; } - // If this returns true, the downloader is done and can be deleted - bool isDone() const { + bool isDone() const override { return m_initial_step_done && m_uncached_received_count == m_uncached_count; } - // Add a file to the list of required file (but don't fetch it yet) - void addFile(const std::string &name, const std::string &sha1); + void addFile(const std::string &name, const std::string &sha1) override; - // Add a remote server to the list; ignored if not built with cURL - void addRemoteServer(const std::string &baseurl); + void addRemoteServer(const std::string &baseurl) override; - // Steps the media downloader: - // - May load media into client by calling client->loadMedia() - // - May check media cache for files - // - May add files to media cache - // - May start remote transfers by calling httpfetch_async - // - May check for completion of current remote transfers - // - May start conventional transfers by calling client->request_media() - // - May inform server that all media has been loaded - // by calling client->received_media() - // After step has been called once, don't call addFile/addRemoteServer. - void step(Client *client); + void step(Client *client) override; - // Must be called for each file received through TOCLIENT_MEDIA - void conventionalTransferDone( + bool conventionalTransferDone( const std::string &name, const std::string &data, - Client *client); + Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; private: struct FileStatus { @@ -107,13 +153,9 @@ private: void startRemoteMediaTransfers(); void startConventionalTransfers(Client *client); - bool checkAndLoad(const std::string &name, const std::string &sha1, - const std::string &data, bool is_from_cache, - Client *client); - - std::string serializeRequiredHashSet(); static void deSerializeHashSet(const std::string &data, std::set<std::string> &result); + std::string serializeRequiredHashSet(); // Maps filename to file status std::map<std::string, FileStatus*> m_files; @@ -121,9 +163,6 @@ private: // Array of remote media servers std::vector<RemoteServerStatus*> m_remotes; - // Filesystem-based media cache - FileCache m_media_cache; - // Has an attempt been made to load media files from the file cache? // Have hash sets been requested from remote servers? bool m_initial_step_done = false; @@ -149,3 +188,63 @@ private: std::string m_name_bound = ""; }; + +// A media downloader that only downloads a single file. +// It does/doesn't do several things the normal downloader does: +// - won't fetch hash sets from remote servers +// - will mark loaded media as coming from file push +// - writing to file cache is optional +class SingleMediaDownloader : public IClientMediaDownloader +{ +public: + SingleMediaDownloader(bool write_to_cache); + ~SingleMediaDownloader(); + + bool isStarted() const override { + return m_stage > STAGE_INIT; + } + + bool isDone() const override { + return m_stage >= STAGE_DONE; + } + + void addFile(const std::string &name, const std::string &sha1) override; + + void addRemoteServer(const std::string &baseurl) override; + + void step(Client *client) override; + + bool conventionalTransferDone(const std::string &name, + const std::string &data, Client *client) override; + +protected: + bool loadMedia(Client *client, const std::string &data, + const std::string &name) override; + +private: + void initialStep(Client *client); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, Client *client); + void startRemoteMediaTransfer(); + void startConventionalTransfer(Client *client); + + enum Stage { + STAGE_INIT, + STAGE_CACHE_CHECKED, // we have tried to load the file from cache + STAGE_DONE + }; + + // Information about the one file we want to fetch + std::string m_file_name; + std::string m_file_sha1; + s32 m_current_remote; + + // Array of remote media servers + std::vector<std::string> m_remotes; + + enum Stage m_stage = STAGE_INIT; + + // Status of remote transfers + unsigned long m_httpfetch_caller; + unsigned long m_httpfetch_next_id = 0; + +}; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 5008047af..383a1d799 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -352,7 +352,7 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) // is the camera inside the cloud mesh? m_camera_inside_cloud = false; // default if (m_enable_3d) { - float camera_height = camera_p.Y; + float camera_height = camera_p.Y - BS * m_camera_offset.Y; if (camera_height >= m_box.MinEdge.Y && camera_height <= m_box.MaxEdge.Y) { v2f camera_in_noise; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 3a6ca3e29..5d8a597a2 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IMeshManipulator.h> #include <IAnimatedMeshSceneNode.h> #include "client/client.h" +#include "client/renderingengine.h" #include "client/sound.h" #include "client/tile.h" #include "util/basic_macros.h" @@ -346,18 +347,6 @@ void GenericCAO::initialize(const std::string &data) infostream<<"GenericCAO: Got init data"<<std::endl; processInitData(data); - if (m_is_player) { - // Check if it's the current player - LocalPlayer *player = m_env->getLocalPlayer(); - if (player && strcmp(player->getName(), m_name.c_str()) == 0) { - m_is_local_player = true; - m_is_visible = false; - player->setCAO(this); - - m_prop.show_on_minimap = false; - } - } - m_enable_shaders = g_settings->getBool("enable_shaders"); } @@ -380,6 +369,16 @@ void GenericCAO::processInitData(const std::string &data) m_rotation = readV3F32(is); m_hp = readU16(is); + if (m_is_player) { + // Check if it's the current player + LocalPlayer *player = m_env->getLocalPlayer(); + if (player && strcmp(player->getName(), m_name.c_str()) == 0) { + m_is_local_player = true; + m_is_visible = false; + player->setCAO(this); + } + } + const u8 num_messages = readU8(is); for (int i = 0; i < num_messages; i++) { @@ -560,6 +559,9 @@ void GenericCAO::removeFromScene(bool permanent) clearParentAttachment(); } + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->removeNodeFromShadowList(getSceneNode()); + if (m_meshnode) { m_meshnode->remove(); m_meshnode->drop(); @@ -808,10 +810,13 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) if (m_reset_textures_timer < 0) updateTextures(m_current_texture_modifier); - scene::ISceneNode *node = getSceneNode(); + if (scene::ISceneNode *node = getSceneNode()) { + if (m_matrixnode) + node->setParent(m_matrixnode); - if (node && m_matrixnode) - node->setParent(m_matrixnode); + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->addNodeToShadowList(node); + } updateNametag(); updateMarker(); @@ -1011,9 +1016,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) const PlayerControl &controls = player->getPlayerControl(); bool walking = false; - if ((controls.up || controls.down || controls.left || controls.right || - controls.forw_move_joystick_axis != 0.f || - controls.sidew_move_joystick_axis != 0.f) && ! g_settings->getBool("freecam")) + if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) walking = true; f32 new_speed = player->local_animation_speed; @@ -1028,9 +1031,10 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam")) new_speed *= 1.5; - // slowdown speed if sneeking + // slowdown speed if sneaking if (controls.sneak && walking && ! g_settings->getBool("no_slow")) new_speed /= 2; + new_speed *= controls.movement_speed; if (walking && (controls.dig || controls.place)) { new_anim = player->local_animations[3]; @@ -1744,6 +1748,7 @@ void GenericCAO::processMessage(const std::string &data) m_tx_basepos = p; m_anim_num_frames = num_frames; + m_anim_frame = 0; m_anim_framelength = framelength; m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 810c57138..bb2d6398f 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -958,10 +958,38 @@ void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, vertex.rotateXZBy(rotation + rotate_degree); vertex += offset; } + + u8 wall = n.getWallMounted(nodedef); + if (wall != DWM_YN) { + for (v3f &vertex : vertices) { + switch (wall) { + case DWM_YP: + vertex.rotateYZBy(180); + vertex.rotateXZBy(180); + break; + case DWM_XP: + vertex.rotateXYBy(90); + break; + case DWM_XN: + vertex.rotateXYBy(-90); + vertex.rotateYZBy(180); + break; + case DWM_ZP: + vertex.rotateYZBy(-90); + vertex.rotateXYBy(90); + break; + case DWM_ZN: + vertex.rotateYZBy(90); + vertex.rotateXYBy(90); + break; + } + } + } + drawQuad(vertices, v3s16(0, 0, 0), plant_height); } -void MapblockMeshGenerator::drawPlantlike() +void MapblockMeshGenerator::drawPlantlike(bool is_rooted) { draw_style = PLANT_STYLE_CROSS; scale = BS / 2 * f->visual_scale; @@ -998,6 +1026,22 @@ void MapblockMeshGenerator::drawPlantlike() break; } + if (is_rooted) { + u8 wall = n.getWallMounted(nodedef); + switch (wall) { + case DWM_YP: + offset.Y += BS*2; + break; + case DWM_XN: + case DWM_XP: + case DWM_ZN: + case DWM_ZP: + offset.X += -BS; + offset.Y += BS; + break; + } + } + switch (draw_style) { case PLANT_STYLE_CROSS: drawPlantlikeQuad(46); @@ -1048,7 +1092,7 @@ void MapblockMeshGenerator::drawPlantlikeRootedNode() MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); light = LightPair(getInteriorLight(ntop, 1, nodedef)); } - drawPlantlike(); + drawPlantlike(true); p.Y--; } diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 237cc7847..7344f05ee 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -146,7 +146,7 @@ public: void drawPlantlikeQuad(float rotation, float quad_offset = 0, bool offset_top_only = false); - void drawPlantlike(); + void drawPlantlike(bool is_rooted = false); // firelike-specific void drawFirelikeQuad(float rotation, float opening_angle, diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 0382c2f18..f64315db4 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -342,8 +342,9 @@ gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path"); size_t pos_dot = font_path.find_last_of('.'); - std::string basename = font_path; - std::string ending = lowercase(font_path.substr(pos_dot)); + std::string basename = font_path, ending; + if (pos_dot != std::string::npos) + ending = lowercase(font_path.substr(pos_dot)); if (ending == ".ttf") { errorstream << "FontEngine: Found font \"" << font_path diff --git a/src/client/game.cpp b/src/client/game.cpp index 5bfe55e66..d2a751040 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -300,6 +300,7 @@ void Game::run() m_game_ui->clearInfoText(); hud->resizeHotbar(); + updateProfilers(stats, draw_times, dtime); processUserInput(dtime); // Update camera before player movement to avoid camera lag of one frame @@ -311,10 +312,11 @@ void Game::run() updatePlayerControl(cam_view); step(&dtime); processClientEvents(&cam_view_target); + updateDebugState(); updateCamera(draw_times.busy_time, dtime); updateSound(dtime); processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_debug); + m_game_ui->m_flags.show_basic_debug); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -823,7 +825,7 @@ bool Game::getServerContent(bool *aborted) dtime, progress); delete[] text; } else { - std::stringstream message; + std::ostringstream message; std::fixed(message); message.precision(0); float receive = client->mediaReceiveProgress() * 100; @@ -932,6 +934,25 @@ void Game::processQueues() shader_src->processQueue(); } +void Game::updateDebugState() +{ + bool has_basic_debug = client->checkPrivilege("basic_debug"); + bool has_debug = client->checkPrivilege("debug"); + + if (m_game_ui->m_flags.show_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) { + m_game_ui->m_flags.show_basic_debug = true; + } + } + if (!has_basic_debug) + hud->disableBlockBounds(); + if (!has_debug) + draw_control->show_wireframe = false; +} void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) @@ -1133,24 +1154,18 @@ 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); - wchar_t buf[100]; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } } 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); - wchar_t buf[100]; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } @@ -1164,7 +1179,7 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::SCREENSHOT)) { client->makeScreenshot(); } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) { - hud->toggleBlockBounds(); + toggleBlockBounds(); } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { m_game_ui->toggleHud(); } else if (wasKeyDown(KeyType::MINIMAP)) { @@ -1452,6 +1467,32 @@ void Game::toggleCinematic() m_game_ui->showTranslatedStatusText("Cinematic mode disabled"); } +void Game::toggleBlockBounds() +{ + if (client->checkPrivilege("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)"); + } +} + // Autoforward by toggling continuous forward. void Game::toggleAutoforward() { @@ -1515,24 +1556,41 @@ void Game::toggleFog() void Game::toggleDebug() { - // Initial / 4x toggle: Chat only - // 1x toggle: Debug text with chat + // Initial: No debug info + // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph - // 3x toggle: Debug text and wireframe - if (!m_game_ui->m_flags.show_debug) { - m_game_ui->m_flags.show_debug = true; + // 3x toggle: Debug text and wireframe (needs "debug" priv) + // Next toggle: Back to initial + // + // 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 used when player has "basic_debug" priv, + // 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 (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; m_game_ui->showTranslatedStatusText("Debug info shown"); } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { + if (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { + if (client->checkPrivilege("basic_debug")) { + m_game_ui->m_flags.show_basic_debug = true; + } m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = true; m_game_ui->showTranslatedStatusText("Wireframe shown"); } else { - m_game_ui->m_flags.show_debug = false; + m_game_ui->m_flags.show_minimal_debug = false; + 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")) { @@ -1561,20 +1619,13 @@ void Game::increaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range + 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new > 4000) { range_new = 4000; - str = wgettext("Viewing range is at maximum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - + std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } @@ -1585,19 +1636,13 @@ void Game::decreaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range - 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new < 20) { range_new = 20; - str = wgettext("Viewing range is at minimum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } @@ -1694,7 +1739,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (m_cache_enable_joysticks) { f32 sens_scale = getSensitivityScaleFactor(); - f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale; + f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale; cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; } @@ -1705,18 +1750,12 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) void Game::updatePlayerControl(const CameraOrientation &cam) { - //TimeTaker tt("update player control", NULL, PRECISION_NANO); + LocalPlayer *player = client->getEnv().getLocalPlayer(); - // DO NOT use the isKeyDown method for the forward, backward, left, right - // buttons, as the code that uses the controls needs to be able to - // distinguish between the two in order to know when to use joysticks. + //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), @@ -1724,22 +1763,16 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) + input->getMovementSpeed(), + input->getMovementDirection() ); - u32 keypress_bits = ( - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | - ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | - ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) - ); + // autoforward if set: move towards pointed position at maximum speed + if (player->getPlayerSettings().continuous_forward && + client->activeObjectsReceived() && !player->isDead()) { + control.movement_speed = 1.0f; + control.movement_direction = 0.0f; + } #ifdef ANDROID /* For Android, simulate holding down AUX1 (fast move) if the user has @@ -1749,23 +1782,38 @@ void Game::updatePlayerControl(const CameraOrientation &cam) */ if (m_cache_hold_aux1) { control.aux1 = control.aux1 ^ true; - keypress_bits ^= ((u32)(1U << 5)); } #endif - LocalPlayer *player = client->getEnv().getLocalPlayer(); + u32 keypress_bits = ( + ( (u32)(control.jump & 0x1) << 4) | + ( (u32)(control.aux1 & 0x1) << 5) | + ( (u32)(control.sneak & 0x1) << 6) | + ( (u32)(control.dig & 0x1) << 7) | + ( (u32)(control.place & 0x1) << 8) | + ( (u32)(control.zoom & 0x1) << 9) + ); - // autojump if set: simulate "jump" key - if (player->getAutojump()) { - control.jump = true; - keypress_bits |= 1U << 4; - } + // Set direction keys to ensure mod compatibility + if (control.movement_speed > 0.001f) { + float absolute_direction; - // autoforward if set: simulate "up" key - if (player->getPlayerSettings().continuous_forward && - client->activeObjectsReceived() && !player->isDead()) { - control.up = true; - keypress_bits |= 1U << 0; + // Check in original orientation (absolute value indicates forward / backward) + absolute_direction = abs(control.movement_direction); + if (absolute_direction < (3.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 0); // Forward + if (absolute_direction > (5.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 1); // Backward + + // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right) + absolute_direction = control.movement_direction + M_PI_2; + if (absolute_direction >= M_PI) + absolute_direction -= 2 * M_PI; + absolute_direction = abs(absolute_direction); + if (absolute_direction < (3.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 2); // Left + if (absolute_direction > (5.0f / 8.0f * M_PI)) + keypress_bits |= (u32)(0x1 << 3); // Right } client->setPlayerControl(control); @@ -1970,6 +2018,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->size = event->hudadd->size; e->z_index = event->hudadd->z_index; e->text2 = event->hudadd->text2; + e->style = event->hudadd->style; m_hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd; @@ -2035,6 +2084,8 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca CASE_SET(HUD_STAT_Z_INDEX, z_index, data); CASE_SET(HUD_STAT_TEXT2, text2, sdata); + + CASE_SET(HUD_STAT_STYLE, style, data); } #undef CASE_SET @@ -3131,15 +3182,22 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, */ runData.update_draw_list_timer += dtime; + float update_draw_list_delta = 0.2f; + v3f camera_direction = camera->getDirection(); - if (runData.update_draw_list_timer >= 0.2 + if (runData.update_draw_list_timer >= update_draw_list_delta || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 || m_camera_offset_changed) { + runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; } + if (RenderingEngine::get_shadow_renderer()) { + updateShadows(); + } + m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); /* @@ -3205,7 +3263,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, if (! gui_chat_console->isOpen()) { if (m_game_ui->m_flags.show_cheat_menu) - m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug); + m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug); if (g_settings->getBool("cheat_hud")) m_cheat_menu->drawHUD(driver, dtime); } @@ -3277,7 +3335,34 @@ inline void Game::updateProfilerGraphs(ProfilerGraph *graph) graph->put(values); } +/**************************************************************************** + * Shadows + *****************************************************************************/ +void Game::updateShadows() +{ + ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer(); + if (!shadow) + return; + float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); + + float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f; + const float offset_constant = 10000.0f; + + v3f light(0.0f, 0.0f, -1.0f); + light.rotateXZBy(90); + light.rotateXYBy(timeoftheday * 360 - 90); + light.rotateYZBy(sky->getSkyBodyOrbitTilt()); + + v3f sun_pos = light * offset_constant; + + if (shadow->getDirectionalLightCount() == 0) + shadow->addDirectionalLight(); + shadow->getDirectionalLight().setDirection(sun_pos); + shadow->setTimeOfDay(in_timeofday); + + shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed); +} /**************************************************************************** Misc diff --git a/src/client/game.h b/src/client/game.h index 8197b9a9b..cb40d4890 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -684,6 +684,7 @@ public: bool handleCallbacks(); void processQueues(); void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void updateDebugState(); void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); void updateProfilerGraphs(ProfilerGraph *graph); @@ -706,6 +707,7 @@ public: void toggleScaffold(); void toggleNextItem(); void toggleCinematic(); + void toggleBlockBounds(); void toggleAutoforward(); void toggleMinimap(bool shift_pressed); @@ -752,6 +754,7 @@ public: const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam); + void updateShadows(); // Misc void limitFps(FpsControl *fps_timings, f32 *dtime); diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index c97ae93b8..66a006ea1 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -74,11 +74,14 @@ void GameUI::init() chat_font_size, FM_Unspecified)); } - // At the middle of the screen - // Object infos are shown in this + + // Infotext of nodes and objects. + // If in debug mode, object debug infos shown here, too. + // Located on the left on the screen, below chat. u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height; m_guitext_info = gui::StaticText::add(guienv, L"", - core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + + // Size is limited; text will be truncated after 6 lines. + core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 6) + v2s32(100, chat_font_height * (g_settings->getU16("recent_chat_messages") + 3)), false, true, guiroot); @@ -119,7 +122,8 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext_coords->setVisible(show_coords); - if (m_flags.show_debug) { + // Minimal debug text must only contain info that can't give a gameplay advantage + if (m_flags.show_minimal_debug) { static float drawtime_avg = 0; drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; u16 fps = 1.0 / stats.dtime_jitter.avg; @@ -145,9 +149,13 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ } // Finally set the guitext visible depending on the flag - m_guitext->setVisible(m_flags.show_debug); + m_guitext->setVisible(m_flags.show_minimal_debug); + + // Basic debug text also shows info that might give a gameplay advantage + if (m_flags.show_basic_debug) { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); - if (m_flags.show_debug) { std::ostringstream os(std::ios_base::binary); os << std::setprecision(1) << std::fixed << "pos: (" << (player_position.X / BS) @@ -177,7 +185,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ )); } - m_guitext2->setVisible(m_flags.show_debug); + m_guitext2->setVisible(m_flags.show_basic_debug); setStaticText(m_guitext_info, m_infotext.c_str()); m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0); @@ -220,7 +228,8 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ void GameUI::initFlags() { m_flags = GameUI::Flags(); - m_flags.show_debug = g_settings->getBool("show_debug"); + m_flags.show_minimal_debug = g_settings->getBool("show_debug"); + m_flags.show_basic_debug = false; } void GameUI::showMinimap(bool show) @@ -244,8 +253,10 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) s32 chat_y = window_size.Y - 150 - m_guitext_chat->getTextHeight(); - if (m_flags.show_debug) - chat_y += 2 * g_fontengine->getLineHeight(); + if (m_flags.show_minimal_debug) + chat_y += g_fontengine->getLineHeight(); + if (m_flags.show_basic_debug) + chat_y += g_fontengine->getLineHeight(); core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0); @@ -320,12 +331,9 @@ void GameUI::toggleProfiler() updateProfiler(); if (m_profiler_current_page != 0) { - wchar_t buf[255]; - const wchar_t* str = wgettext("Profiler shown (page %d of %d)"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, - m_profiler_current_page, m_profiler_max_page); - delete[] str; - showStatusText(buf); + std::wstring msg = fwgettext("Profiler shown (page %d of %d)", + m_profiler_current_page, m_profiler_max_page); + showStatusText(msg); } else { showTranslatedStatusText("Profiler hidden"); } diff --git a/src/client/gameui.h b/src/client/gameui.h index f04fd97b8..5404643e2 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -58,7 +58,8 @@ public: bool show_chat = true; bool show_hud = true; bool show_minimap = false; - bool show_debug = true; + bool show_minimal_debug = false; + bool show_basic_debug = false; bool show_profiler_graph = false; bool show_cheat_menu = true; }; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 391af0995..3f687b698 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -331,8 +331,8 @@ bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *p void Hud::drawLuaElements(const v3s16 &camera_offset) { - u32 text_height = g_fontengine->getTextHeight(); - irr::gui::IGUIFont* font = g_fontengine->getFont(); + const u32 text_height = g_fontengine->getTextHeight(); + gui::IGUIFont *const font = g_fontengine->getFont(); // Reorder elements by z_index std::vector<HudElement*> elems; @@ -356,38 +356,34 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { case HUD_ELEM_TEXT: { - irr::gui::IGUIFont *textfont = font; unsigned int font_size = g_fontengine->getDefaultFontSize(); if (e->size.X > 0) font_size *= e->size.X; - if (font_size != g_fontengine->getDefaultFontSize()) - textfont = g_fontengine->getFont(font_size); +#ifdef __ANDROID__ + // The text size on Android is not proportional with the actual scaling + // FIXME: why do we have such a weird unportable hack?? + if (font_size > 3 && e->offset.X < -20) + font_size -= 3; +#endif + auto textfont = g_fontengine->getFont(FontSpec(font_size, + (e->style & HUD_STYLE_MONO) ? FM_Mono : FM_Unspecified, + e->style & HUD_STYLE_BOLD, e->style & HUD_STYLE_ITALIC)); video::SColor color(255, (e->number >> 16) & 0xFF, (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); std::wstring text = unescape_translate(utf8_to_wide(e->text)); core::dimension2d<u32> textsize = textfont->getDimension(text.c_str()); -#ifdef __ANDROID__ - // The text size on Android is not proportional with the actual scaling - irr::gui::IGUIFont *font_scaled = font_size <= 3 ? - textfont : g_fontengine->getFont(font_size - 3); - if (e->offset.X < -20) - textsize = font_scaled->getDimension(text.c_str()); -#endif + v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), (e->align.Y - 1.0) * (textsize.Height / 2)); core::rect<s32> size(0, 0, e->scale.X * m_scale_factor, text_height * e->scale.Y * m_scale_factor); v2s32 offs(e->offset.X * m_scale_factor, e->offset.Y * m_scale_factor); -#ifdef __ANDROID__ - if (e->offset.X < -20) - font_scaled->draw(text.c_str(), size + pos + offset + offs, color); - else -#endif + { textfont->draw(text.c_str(), size + pos + offset + offs, color); } @@ -861,13 +857,19 @@ void Hud::drawSelectionMesh() } } -void Hud::toggleBlockBounds() +enum Hud::BlockBoundsMode Hud::toggleBlockBounds() { m_block_bounds_mode = static_cast<BlockBoundsMode>(m_block_bounds_mode + 1); if (m_block_bounds_mode >= BLOCK_BOUNDS_MAX) { m_block_bounds_mode = BLOCK_BOUNDS_OFF; } + return m_block_bounds_mode; +} + +void Hud::disableBlockBounds() +{ + m_block_bounds_mode = BLOCK_BOUNDS_OFF; } void Hud::drawBlockBounds() @@ -889,7 +891,7 @@ void Hud::drawBlockBounds() v3f offset = intToFloat(client->getCamera()->getOffset(), BS); - s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_ALL ? 2 : 0; + s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0; v3f halfNode = v3f(BS, BS, BS) / 2.0f; diff --git a/src/client/hud.h b/src/client/hud.h index d341105d2..fd79183a0 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -35,6 +35,14 @@ struct ItemStack; class Hud { public: + enum BlockBoundsMode + { + BLOCK_BOUNDS_OFF, + BLOCK_BOUNDS_CURRENT, + BLOCK_BOUNDS_NEAR, + BLOCK_BOUNDS_MAX + } m_block_bounds_mode = BLOCK_BOUNDS_OFF; + video::SColor crosshair_argb; video::SColor selectionbox_argb; @@ -51,7 +59,8 @@ public: Inventory *inventory); ~Hud(); - void toggleBlockBounds(); + enum BlockBoundsMode toggleBlockBounds(); + void disableBlockBounds(); void drawBlockBounds(); void drawHotbar(u16 playeritem); @@ -126,14 +135,6 @@ private: scene::SMeshBuffer m_rotation_mesh_buffer; - enum BlockBoundsMode - { - BLOCK_BOUNDS_OFF, - BLOCK_BOUNDS_CURRENT, - BLOCK_BOUNDS_ALL, - BLOCK_BOUNDS_MAX - } m_block_bounds_mode = BLOCK_BOUNDS_OFF; - enum { HIGHLIGHT_BOX, diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 7b2ef9822..97ad094e5 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -95,9 +95,14 @@ void imageCleanTransparent(video::IImage *src, u32 threshold) Bitmap newmap = bitmap; + // Cap iterations to keep runtime reasonable, for higher-res textures we can + // get away with filling less pixels. + int iter_max = 11 - std::max(dim.Width, dim.Height) / 16; + iter_max = std::max(iter_max, 2); + // Then repeatedly look for transparent pixels, filling them in until - // we're finished (capped at 50 iterations). - for (u32 iter = 0; iter < 50; iter++) { + // we're finished. + for (int iter = 0; iter < iter_max; iter++) { for (u32 ctry = 0; ctry < dim.Height; ctry++) for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index d833db2f1..d2d81be9c 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -148,11 +148,8 @@ bool MyEventReceiver::OnEvent(const SEvent &event) #endif } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { - /* TODO add a check like: - if (event.JoystickEvent != joystick_we_listen_for) - return false; - */ - return joystick->handleEvent(event.JoystickEvent); + // joystick may be nullptr if game is launched with '--random-input' parameter + return joystick && joystick->handleEvent(event.JoystickEvent); } else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { // Handle mouse events KeyPress key; @@ -253,4 +250,39 @@ void RandomInputHandler::step(float dtime) } } mousepos += mousespeed; + static bool useJoystick = false; + { + static float counterUseJoystick = 0; + counterUseJoystick -= dtime; + if (counterUseJoystick < 0.0) { + counterUseJoystick = 5.0; // switch between joystick and keyboard direction input + useJoystick = !useJoystick; + } + } + if (useJoystick) { + static float counterMovement = 0; + counterMovement -= dtime; + if (counterMovement < 0.0) { + counterMovement = 0.1 * Rand(1, 40); + movementSpeed = Rand(0,100)*0.01; + movementDirection = Rand(-100, 100)*0.01 * M_PI; + } + } else { + bool f = keydown[keycache.key[KeyType::FORWARD]], + l = keydown[keycache.key[KeyType::LEFT]]; + if (f || l) { + movementSpeed = 1.0f; + if (f && !l) + movementDirection = 0.0; + else if (!f && l) + movementDirection = -M_PI_2; + else if (f && l) + movementDirection = -M_PI_4; + else + movementDirection = 0.0; + } else { + movementSpeed = 0.0; + movementDirection = 0.0; + } + } } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index efe9442d5..951ec8884 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -242,6 +242,9 @@ public: virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; + virtual float getMovementSpeed() = 0; + virtual float getMovementDirection() = 0; + virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} @@ -296,6 +299,44 @@ public: { return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } + virtual float getMovementSpeed() + { + bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), + b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]), + l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]), + r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]); + if (f || b || l || r) + { + // if contradictory keys pressed, stay still + if (f && b && l && r) + return 0.0f; + else if (f && b && !l && !r) + return 0.0f; + else if (!f && !b && l && r) + return 0.0f; + return 1.0f; // If there is a keyboard event, assume maximum speed + } + return joystick.getMovementSpeed(); + } + virtual float getMovementDirection() + { + float x = 0, z = 0; + + /* Check keyboard for input */ + if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD])) + z += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD])) + z -= 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT])) + x += 1; + if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT])) + x -= 1; + + if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ + return atan2(x, z); + else + return joystick.getMovementDirection(); + } virtual bool cancelPressed() { return wasKeyDown(KeyType::ESC) || m_receiver->WasKeyDown(CancelKey); @@ -371,6 +412,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } + virtual float getMovementSpeed() { return movementSpeed; } + virtual float getMovementDirection() { return movementDirection; } virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } @@ -384,4 +427,6 @@ private: KeyList keydown; v2s32 mousepos; v2s32 mousespeed; + float movementSpeed; + float movementDirection; }; diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index 919db5315..630565d8d 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -160,6 +160,7 @@ JoystickController::JoystickController() : for (float &i : m_past_pressed_time) { i = 0; } + m_layout.axes_deadzone = 0; clear(); } @@ -251,10 +252,27 @@ void JoystickController::clear() memset(m_axes_vals, 0, sizeof(m_axes_vals)); } -s16 JoystickController::getAxisWithoutDead(JoystickAxis axis) +float JoystickController::getAxisWithoutDead(JoystickAxis axis) { s16 v = m_axes_vals[axis]; + if (abs(v) < m_layout.axes_deadzone) - return 0; - return v; + return 0.0f; + + v += (v < 0 ? m_layout.axes_deadzone : -m_layout.axes_deadzone); + + return (float)v / ((float)(INT16_MAX - m_layout.axes_deadzone)); +} + +float JoystickController::getMovementDirection() +{ + return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE)); +} + +float JoystickController::getMovementSpeed() +{ + float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2)); + if (speed > 1.0f) + speed = 1.0f; + return speed; } diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 3f361e4ef..cbc60886c 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -144,7 +144,10 @@ public: return m_axes_vals[axis]; } - s16 getAxisWithoutDead(JoystickAxis axis); + float getAxisWithoutDead(JoystickAxis axis); + + float getMovementDirection(); + float getMovementSpeed(); f32 doubling_dtime; diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index e979c5600..0a0a57cce 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -571,23 +571,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env) } } - if (control.up) - speedH += v3f(0.0f, 0.0f, 1.0f); - - if (control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f); - - if (!control.up && !control.down) - speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f); - - if (control.left) - speedH += v3f(-1.0f, 0.0f, 0.0f); - - if (control.right) - speedH += v3f(1.0f, 0.0f, 0.0f); - - if (!control.left && !control.right) - speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f); + speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction)); if (m_autojump) { // release autojump after a given time @@ -644,6 +628,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) else speedH = speedH.normalize() * movement_speed_walk; + speedH *= control.movement_speed; /* Apply analog input */ + // Acceleration increase f32 incH = 0.0f; // Horizontal (X, Z) f32 incV = 0.0f; // Vertical (Y) @@ -1133,9 +1119,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, if (m_autojump) return; - bool control_forward = control.up || - (!control.up && !control.down && - control.forw_move_joystick_axis < -0.05f); + bool control_forward = keyPressed & (1 << 0); bool could_autojump = m_can_jump && !control.jump && !control.sneak && control_forward; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 8877e0549..52729632a 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -410,20 +410,20 @@ static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, f if (dir.X > 0 || dir.Y != 0 || dir.Z < 0) base -= scale; if (dir == v3s16(0,0,1)) { - *u = -base.X - 1; - *v = -base.Y - 1; + *u = -base.X; + *v = -base.Y; } else if (dir == v3s16(0,0,-1)) { *u = base.X + 1; - *v = -base.Y - 2; + *v = -base.Y - 1; } else if (dir == v3s16(1,0,0)) { *u = base.Z + 1; - *v = -base.Y - 2; - } else if (dir == v3s16(-1,0,0)) { - *u = -base.Z - 1; *v = -base.Y - 1; + } else if (dir == v3s16(-1,0,0)) { + *u = -base.Z; + *v = -base.Y; } else if (dir == v3s16(0,1,0)) { *u = base.X + 1; - *v = -base.Z - 2; + *v = -base.Z - 1; } else if (dir == v3s16(0,-1,0)) { *u = base.X + 1; *v = base.Z + 1; @@ -805,7 +805,7 @@ static void getTileInfo( VoxelManipulator &vmanip = data->m_vmanip; const NodeDefManager *ndef = data->m_client->ndef(); v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; - + const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); content_t c0 = n0.getContent(); @@ -894,6 +894,9 @@ static void updateFastFaceRow( g_settings->getBool("enable_shaders") && g_settings->getBool("enable_waving_water"); + static thread_local const bool force_not_tiling = + g_settings->getBool("enable_dynamic_shadows"); + v3s16 p = startpos; u16 continuous_tiles_count = 1; @@ -925,7 +928,7 @@ static void updateFastFaceRow( // the face must be drawn anyway if (j != MAP_BLOCKSIZE - 1) { p += translate_dir; - + getTileInfo(data, p, face_dir, next_makes_face, next_p_corrected, next_face_dir_corrected, next_lights, @@ -933,8 +936,9 @@ static void updateFastFaceRow( next_tile, xray, xraySet); - - if (next_makes_face == makes_face + + if (!force_not_tiling + && next_makes_face == makes_face && next_p_corrected == p_corrected + translate_dir && next_face_dir_corrected == face_dir_corrected && memcmp(next_lights, lights, sizeof(lights)) == 0 @@ -1077,16 +1081,16 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): std::set<content_t> xraySet, nodeESPSet; if (xray) xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef()); - + nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef()); - + /* We are including the faces of the trailing edges of the block. This means that when something changes, the caller must also update the meshes of the blocks at the leading edges. NOTE: This is the slowest part of this method. - */ + */ { // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) //TimeTaker timer2("updateAllFastFaceRows()"); diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 99af085f9..1028a96e1 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -28,25 +28,35 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/content_cao.h" #include "mapblock.h" #include "mapsector.h" +#include "client/shadows/dynamicshadowsrender.h" RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) : device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()), guienv(device->getGUIEnvironment()), client(_client), camera(client->getCamera()), - mapper(client->getMinimap()), hud(_hud) + mapper(client->getMinimap()), hud(_hud), + shadow_renderer(nullptr) { screensize = driver->getScreenSize(); virtual_size = screensize; + + if (g_settings->getBool("enable_shaders") && + g_settings->getBool("enable_dynamic_shadows")) { + shadow_renderer = new ShadowRenderer(device, client); + } } RenderingCore::~RenderingCore() { clearTextures(); + delete shadow_renderer; } void RenderingCore::initialize() { // have to be called late as the VMT is not ready in the constructor: initTextures(); + if (shadow_renderer) + shadow_renderer->initialize(); } void RenderingCore::updateScreenSize() @@ -75,24 +85,27 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min draw_player_tracers = g_settings->getBool("enable_player_tracers"); draw_node_esp = g_settings->getBool("enable_node_esp"); draw_node_tracers = g_settings->getBool("enable_node_tracers"); - v3f entity_color = g_settings->getV3F("entity_esp_color"); + v3f entity_color = g_settings->getV3F("entity_esp_color"); v3f player_color = g_settings->getV3F("player_esp_color"); entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z); player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z); - + + if (shadow_renderer) + shadow_renderer->update(); + beforeDraw(); drawAll(); } void RenderingCore::drawTracersAndESP() -{ +{ ClientEnvironment &env = client->getEnv(); Camera *camera = client->getCamera(); - + v3f camera_offset = intToFloat(camera->getOffset(), BS); - + v3f eye_pos = (camera->getPosition() + camera->getDirection() - camera_offset); - + video::SMaterial material, oldmaterial; oldmaterial = driver->getMaterial2D(); material.setFlag(video::EMF_LIGHTING, false); @@ -100,7 +113,7 @@ void RenderingCore::drawTracersAndESP() material.setFlag(video::EMF_ZBUFFER, false); material.setFlag(video::EMF_ZWRITE_ENABLE, false); driver->setMaterial(material); - + if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers) { auto allObjects = env.getAllActiveObjects(); for (auto &it : allObjects) { @@ -153,13 +166,16 @@ void RenderingCore::drawTracersAndESP() } } } - + driver->setMaterial(oldmaterial); } void RenderingCore::draw3D() { smgr->drawAll(); + if (shadow_renderer) + shadow_renderer->drawDebug(); + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); if (!show_hud) return; @@ -176,7 +192,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/core.h b/src/client/render/core.h index 386c5a840..0864f25bd 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +class ShadowRenderer; class Camera; class Client; class Hud; @@ -55,6 +56,8 @@ protected: Minimap *mapper; Hud *hud; + ShadowRenderer *shadow_renderer; + void updateScreenSize(); virtual void initTextures() {} virtual void clearTextures() {} @@ -81,4 +84,6 @@ public: bool _draw_wield_tool, bool _draw_crosshair); inline v2u32 getVirtualSize() const { return virtual_size; } + + ShadowRenderer *get_shadow_renderer() { return shadow_renderer; }; }; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 138012414..2e4994a40 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include <IrrlichtDevice.h> -#include <irrlicht.h> #include "fontengine.h" #include "client.h" #include "clouds.h" @@ -92,7 +91,6 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); - u16 bits = g_settings->getU16("fullscreen_bpp"); u16 fsaa = g_settings->getU16("fsaa"); // stereo buffer required for pageflip stereo @@ -106,7 +104,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) u32 i; for (i = 0; i != drivers.size(); i++) { if (!strcasecmp(driverstring.c_str(), - RenderingEngine::getVideoDriverName(drivers[i]))) { + RenderingEngine::getVideoDriverInfo(drivers[i]).name.c_str())) { driverType = drivers[i]; break; } @@ -122,15 +120,13 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) params.LoggingLevel = irr::ELL_DEBUG; params.DriverType = driverType; params.WindowSize = core::dimension2d<u32>(screen_w, screen_h); - params.Bits = bits; params.AntiAlias = fsaa; params.Fullscreen = fullscreen; params.Stencilbuffer = false; params.Stereobuffer = stereo_buffer; params.Vsync = vsync; params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); - params.ZBufferBits = 24; + params.HighPrecisionFPU = true; #ifdef __ANDROID__ params.PrivateData = porting::app_global; #endif @@ -171,60 +167,6 @@ void RenderingEngine::setResizable(bool resize) m_device->setResizable(resize); } -bool RenderingEngine::print_video_modes() -{ - IrrlichtDevice *nulldevice; - - bool vsync = g_settings->getBool("vsync"); - u16 fsaa = g_settings->getU16("fsaa"); - MyEventReceiver *receiver = new MyEventReceiver(); - - SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - params.DriverType = video::EDT_NULL; - params.WindowSize = core::dimension2d<u32>(640, 480); - params.Bits = 24; - params.AntiAlias = fsaa; - params.Fullscreen = false; - params.Stencilbuffer = false; - params.Vsync = vsync; - params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); - - nulldevice = createDeviceEx(params); - - if (!nulldevice) { - delete receiver; - return false; - } - - std::cout << _("Available video modes (WxHxD):") << std::endl; - - video::IVideoModeList *videomode_list = nulldevice->getVideoModeList(); - - if (videomode_list != NULL) { - s32 videomode_count = videomode_list->getVideoModeCount(); - core::dimension2d<u32> videomode_res; - s32 videomode_depth; - for (s32 i = 0; i < videomode_count; ++i) { - videomode_res = videomode_list->getVideoModeResolution(i); - videomode_depth = videomode_list->getVideoModeDepth(i); - std::cout << videomode_res.Width << "x" << videomode_res.Height - << "x" << videomode_depth << std::endl; - } - - std::cout << _("Active video mode (WxHxD):") << std::endl; - videomode_res = videomode_list->getDesktopResolution(); - videomode_depth = videomode_list->getDesktopDepth(); - std::cout << videomode_res.Width << "x" << videomode_res.Height << "x" - << videomode_depth << std::endl; - } - - nulldevice->drop(); - delete receiver; - - return videomode_list != NULL; -} - void RenderingEngine::removeMesh(const scene::IMesh* mesh) { m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); @@ -310,7 +252,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name) // force a shutdown of an application if it doesn't respond to the destroy // window message. - verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property" + verbosestream << "Client: Setting Xorg _NET_WM_PID extended window manager property" << std::endl; Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false); @@ -341,14 +283,6 @@ static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd) const video::SExposedVideoData exposedData = driver->getExposedVideoData(); switch (driver->getDriverType()) { -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 - case video::EDT_DIRECT3D8: - hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd); - break; -#endif - case video::EDT_DIRECT3D9: - hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd); - break; #if ENABLE_GLES case video::EDT_OGLES1: case video::EDT_OGLES2: @@ -581,32 +515,21 @@ void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv, get_video_driver()->endScene(); } -std::vector<core::vector3d<u32>> RenderingEngine::getSupportedVideoModes() -{ - IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL); - sanity_check(nulldevice); - - std::vector<core::vector3d<u32>> mlist; - video::IVideoModeList *modelist = nulldevice->getVideoModeList(); - - s32 num_modes = modelist->getVideoModeCount(); - for (s32 i = 0; i != num_modes; i++) { - core::dimension2d<u32> mode_res = modelist->getVideoModeResolution(i); - u32 mode_depth = (u32)modelist->getVideoModeDepth(i); - mlist.emplace_back(mode_res.Width, mode_res.Height, mode_depth); - } - - nulldevice->drop(); - return mlist; -} - std::vector<irr::video::E_DRIVER_TYPE> RenderingEngine::getSupportedVideoDrivers() { + // Only check these drivers. + // We do not support software and D3D in any capacity. + static const irr::video::E_DRIVER_TYPE glDrivers[4] = { + irr::video::EDT_NULL, + irr::video::EDT_OPENGL, + irr::video::EDT_OGLES1, + irr::video::EDT_OGLES2, + }; std::vector<irr::video::E_DRIVER_TYPE> drivers; - for (int i = 0; i != irr::video::EDT_COUNT; i++) { - if (irr::IrrlichtDevice::isDriverSupported((irr::video::E_DRIVER_TYPE)i)) - drivers.push_back((irr::video::E_DRIVER_TYPE)i); + for (int i = 0; i < 4; i++) { + if (irr::IrrlichtDevice::isDriverSupported(glDrivers[i])) + drivers.push_back(glDrivers[i]); } return drivers; @@ -630,36 +553,15 @@ void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair); } -const char *RenderingEngine::getVideoDriverName(irr::video::E_DRIVER_TYPE type) +const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type) { - static const char *driver_ids[] = { - "null", - "software", - "burningsvideo", - "direct3d8", - "direct3d9", - "opengl", - "ogles1", - "ogles2", + static const std::unordered_map<int, VideoDriverInfo> driver_info_map = { + {(int)video::EDT_NULL, {"null", "NULL Driver"}}, + {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}}, + {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, + {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, }; - - return driver_ids[type]; -} - -const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type) -{ - static const char *driver_names[] = { - "NULL Driver", - "Software Renderer", - "Burning's Video", - "Direct3D 8", - "Direct3D 9", - "OpenGL", - "OpenGL ES1", - "OpenGL ES2", - }; - - return driver_names[type]; + return driver_info_map.at((int)type); } #ifndef __ANDROID__ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 28ddc8652..6f104bba9 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -25,6 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include "irrlichttypes_extrabloated.h" #include "debug.h" +#include "client/render/core.h" +// include the shadow mapper classes too +#include "client/shadows/dynamicshadowsrender.h" + +struct VideoDriverInfo { + std::string name; + std::string friendly_name; +}; class ITextureSource; class Camera; @@ -45,8 +53,7 @@ public: video::IVideoDriver *getVideoDriver() { return driver; } - static const char *getVideoDriverName(irr::video::E_DRIVER_TYPE type); - static const char *getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type); + static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type); static float getDisplayDensity(); static v2u32 getDisplaySize(); @@ -113,7 +120,13 @@ public: return m_device->run(); } - static std::vector<core::vector3d<u32>> getSupportedVideoModes(); + // 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(); + return nullptr; + } static std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers(); private: diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 58946b90f..dc9e9ae6d 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -225,6 +225,16 @@ class MainShaderConstantSetter : public IShaderConstantSetter { CachedVertexShaderSetting<float, 16> m_world_view_proj; CachedVertexShaderSetting<float, 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<s32> m_shadow_texture; + #if ENABLE_GLES // Modelview matrix CachedVertexShaderSetting<float, 16> m_world_view; @@ -243,6 +253,13 @@ public: , 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_shadow_texture("ShadowMapSampler") {} ~MainShaderConstantSetter() = default; @@ -280,6 +297,36 @@ public: }; m_normal.set(m, services); #endif + + // Set uniforms for Shadow shader + if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) { + const auto &light = shadow->getDirectionalLight(); + + core::matrix4 shadowViewProj = light.getProjectionMatrix(); + shadowViewProj *= light.getViewMatrix(); + m_shadow_view_proj.set(shadowViewProj.pointer(), services); + + float v_LightDirection[3]; + light.getDirection().getAs3Values(v_LightDirection); + m_light_direction.set(v_LightDirection, services); + + float TextureResolution = light.getMapResolution(); + m_texture_res.set(&TextureResolution, services); + + float ShadowStrength = shadow->getShadowStrength(); + m_shadow_strength.set(&ShadowStrength, services); + + float timeOfDay = shadow->getTimeOfDay(); + m_time_of_day.set(&timeOfDay, services); + + float shadowFar = shadow->getMaxShadowFar(); + m_shadowfar.set(&shadowFar, 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); + } } }; @@ -627,8 +674,12 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, if (strstr(gl_renderer, "GC7000")) use_discard = true; #endif - if (use_discard && shaderinfo.base_material != video::EMT_SOLID) - shaders_header << "#define USE_DISCARD 1\n"; + if (use_discard) { + if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) + shaders_header << "#define USE_DISCARD 1\n"; + else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF) + shaders_header << "#define USE_DISCARD_REF 1\n"; + } #define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n" @@ -682,6 +733,23 @@ 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 (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"; + + if (g_settings->getBool("shadow_poisson_filter")) + shaders_header << "#define POISSON_FILTER 1\n"; + + s32 shadow_filter = g_settings->getS32("shadow_filters"); + shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n"; + + float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius"); + if (shadow_soft_radius < 1.0f) + shadow_soft_radius = 1.0f; + shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; + } + 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 new file mode 100644 index 000000000..0c7eea0e7 --- /dev/null +++ b/src/client/shadows/dynamicshadows.cpp @@ -0,0 +1,182 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 <cmath> + +#include "client/shadows/dynamicshadows.h" +#include "client/client.h" +#include "client/clientenvironment.h" +#include "client/clientmap.h" +#include "client/camera.h" + +using m4f = core::matrix4; + +void DirectionalLight::createSplitMatrices(const Camera *cam) +{ + float radius; + v3f newCenter; + v3f look = cam->getDirection(); + + // camera view tangents + float tanFovY = tanf(cam->getFovY() * 0.5f); + float tanFovX = tanf(cam->getFovX() * 0.5f); + + // adjusted frustum boundaries + float sfNear = future_frustum.zNear; + 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; + + // 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); + + // 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; + // Compute the frustumBoundingSphere radius + v3f boundVec = (camPos + farCorner * sfFar) - newCenter; + radius = boundVec.getLength() * 2.0f; + // boundVec.getLength(); + float vvolume = radius * 2.0f; + + float texelsPerUnit = getMapResolution() / vvolume; + m4f mTexelScaling; + mTexelScaling.setScale(texelsPerUnit); + + m4f mLookAt, mLookAtInv; + + mLookAt.buildCameraLookAtMatrixLH(v3f(0.0f, 0.0f, 0.0f), -direction, v3f(0.0f, 1.0f, 0.0f)); + + mLookAt *= mTexelScaling; + mLookAtInv = mLookAt; + mLookAtInv.makeInverse(); + + v3f frustumCenter = newCenter; + mLookAt.transformVect(frustumCenter); + frustumCenter.X = floorf(frustumCenter.X); // clamp to texel increment + frustumCenter.Y = floorf(frustumCenter.Y); // clamp to texel increment + frustumCenter.Z = floorf(frustumCenter.Z); + mLookAtInv.transformVect(frustumCenter); + // probar radius multipliacdor en funcion del I, a menor I mas multiplicador + v3f eye_displacement = direction * vvolume; + + // 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); + future_frustum.camera_offset = cam->getOffset(); +} + +DirectionalLight::DirectionalLight(const u32 shadowMapResolution, + const v3f &position, video::SColorf lightColor, + f32 farValue) : + diffuseColor(lightColor), + farPlane(farValue), mapRes(shadowMapResolution), pos(position) +{} + +void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool force) +{ + if (dirty && !force) + return; + + float zNear = cam->getCameraNode()->getNearValue(); + float zFar = getMaxFarValue(); + + /////////////////////////////////// + // update splits near and fars + future_frustum.zNear = zNear; + future_frustum.zFar = zFar; + + // update shadow frustum + createSplitMatrices(cam); + // get the draw list for shadows + client->getEnv().getClientMap().updateDrawListShadow( + getPosition(), getDirection(), future_frustum.length); + should_update_map_shadow = true; + dirty = true; + + // when camera offset changes, adjust the current frustum view matrix to avoid flicker + v3s16 cam_offset = cam->getOffset(); + if (cam_offset != shadow_frustum.camera_offset) { + 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.camera_offset = cam_offset; + } +} + +void DirectionalLight::commitFrustum() +{ + if (!dirty) + return; + + shadow_frustum = future_frustum; + dirty = false; +} + +void DirectionalLight::setDirection(v3f dir) +{ + direction = -dir; + direction.normalize(); +} + +v3f DirectionalLight::getPosition() const +{ + return shadow_frustum.position; +} + +const m4f &DirectionalLight::getViewMatrix() const +{ + return shadow_frustum.ViewMat; +} + +const m4f &DirectionalLight::getProjectionMatrix() const +{ + return shadow_frustum.ProjOrthMat; +} + +const m4f &DirectionalLight::getFutureViewMatrix() const +{ + return future_frustum.ViewMat; +} + +const m4f &DirectionalLight::getFutureProjectionMatrix() const +{ + return future_frustum.ProjOrthMat; +} + +m4f DirectionalLight::getViewProjMatrix() +{ + return shadow_frustum.ProjOrthMat * shadow_frustum.ViewMat; +} diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h new file mode 100644 index 000000000..d8be66be8 --- /dev/null +++ b/src/client/shadows/dynamicshadows.h @@ -0,0 +1,109 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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_bloated.h" +#include <matrix4.h> +#include "util/basic_macros.h" + +class Camera; +class Client; + +struct shadowFrustum +{ + float zNear{0.0f}; + float zFar{0.0f}; + float length{0.0f}; + core::matrix4 ProjOrthMat; + core::matrix4 ViewMat; + v3f position; + v3s16 camera_offset; +}; + +class DirectionalLight +{ +public: + DirectionalLight(const u32 shadowMapResolution, + const v3f &position, + video::SColorf lightColor = video::SColor(0xffffffff), + f32 farValue = 100.0f); + ~DirectionalLight() = default; + + //DISABLE_CLASS_COPY(DirectionalLight) + + void update_frustum(const Camera *cam, Client *client, bool force = false); + + // when set direction is updated to negative normalized(direction) + void setDirection(v3f dir); + v3f getDirection() const{ + return direction; + }; + v3f getPosition() const; + + /// Gets the light's matrices. + const core::matrix4 &getViewMatrix() const; + const core::matrix4 &getProjectionMatrix() const; + const core::matrix4 &getFutureViewMatrix() const; + const core::matrix4 &getFutureProjectionMatrix() const; + core::matrix4 getViewProjMatrix(); + + /// Gets the light's far value. + f32 getMaxFarValue() const + { + return farPlane; + } + + + /// Gets the light's color. + const video::SColorf &getLightColor() const + { + return diffuseColor; + } + + /// Sets the light's color. + void setLightColor(const video::SColorf &lightColor) + { + diffuseColor = lightColor; + } + + /// Gets the shadow map resolution for this light. + u32 getMapResolution() const + { + return mapRes; + } + + bool should_update_map_shadow{true}; + + void commitFrustum(); + +private: + void createSplitMatrices(const Camera *cam); + + video::SColorf diffuseColor; + + f32 farPlane; + u32 mapRes; + + v3f pos; + v3f direction{0}; + shadowFrustum shadow_frustum; + shadowFrustum future_frustum; + bool dirty{false}; +}; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp new file mode 100644 index 000000000..a913a9290 --- /dev/null +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -0,0 +1,630 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 <cstring> +#include "client/shadows/dynamicshadowsrender.h" +#include "client/shadows/shadowsScreenQuad.h" +#include "client/shadows/shadowsshadercallbacks.h" +#include "settings.h" +#include "filesys.h" +#include "util/string.h" +#include "client/shader.h" +#include "client/client.h" +#include "client/clientmap.h" +#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_shadows_enabled = true; + + m_shadow_strength = g_settings->getFloat("shadow_strength"); + + m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); + + m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size"); + + m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit"); + m_shadow_map_colored = g_settings->getBool("shadow_map_color"); + m_shadow_samples = g_settings->getS32("shadow_filters"); + m_map_shadow_update_frames = g_settings->getS16("shadow_update_frames"); +} + +ShadowRenderer::~ShadowRenderer() +{ + if (m_shadow_depth_cb) + delete m_shadow_depth_cb; + if (m_shadow_mix_cb) + delete m_shadow_mix_cb; + m_shadow_node_array.clear(); + m_light_list.clear(); + + if (shadowMapTextureDynamicObjects) + m_driver->removeTexture(shadowMapTextureDynamicObjects); + + if (shadowMapTextureFinal) + m_driver->removeTexture(shadowMapTextureFinal); + + if (shadowMapTextureColors) + m_driver->removeTexture(shadowMapTextureColors); + + if (shadowMapClientMap) + m_driver->removeTexture(shadowMapClientMap); + + if (shadowMapClientMapFuture) + m_driver->removeTexture(shadowMapClientMapFuture); +} + +void ShadowRenderer::initialize() +{ + auto *gpu = m_driver->getGPUProgrammingServices(); + + // we need glsl + if (m_shadows_enabled && gpu && m_driver->queryFeature(video::EVDF_ARB_GLSL)) { + createShaders(); + } else { + m_shadows_enabled = false; + + warningstream << "Shadows: GLSL Shader not supported on this system." + << std::endl; + return; + } + + m_texture_format = m_shadow_map_texture_32bit + ? video::ECOLOR_FORMAT::ECF_R32F + : video::ECOLOR_FORMAT::ECF_R16F; + + m_texture_format_color = m_shadow_map_texture_32bit + ? video::ECOLOR_FORMAT::ECF_G32R32F + : video::ECOLOR_FORMAT::ECF_G16R16F; +} + + +size_t ShadowRenderer::addDirectionalLight() +{ + m_light_list.emplace_back(m_shadow_map_texture_size, + v3f(0.f, 0.f, 0.f), + video::SColor(255, 255, 255, 255), m_shadow_map_max_distance); + return m_light_list.size() - 1; +} + +DirectionalLight &ShadowRenderer::getDirectionalLight(u32 index) +{ + return m_light_list[index]; +} + +size_t ShadowRenderer::getDirectionalLightCount() const +{ + return m_light_list.size(); +} + +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; + } + return 0.0f; +} + +void ShadowRenderer::addNodeToShadowList( + scene::ISceneNode *node, E_SHADOW_MODE shadowMode) +{ + m_shadow_node_array.emplace_back(NodeToApply(node, shadowMode)); +} + +void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node) +{ + 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); + break; + } else { + ++it; + } + } +} + +void ShadowRenderer::updateSMTextures() +{ + if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { + return; + } + + if (!shadowMapTextureDynamicObjects) { + + shadowMapTextureDynamicObjects = getSMTexture( + std::string("shadow_dynamic_") + itos(m_shadow_map_texture_size), + m_texture_format, true); + } + + if (!shadowMapClientMap) { + + shadowMapClientMap = getSMTexture( + std::string("shadow_clientmap_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + + if (!shadowMapClientMapFuture && m_map_shadow_update_frames > 1) { + shadowMapClientMapFuture = getSMTexture( + std::string("shadow_clientmap_bb_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + + if (m_shadow_map_colored && !shadowMapTextureColors) { + shadowMapTextureColors = getSMTexture( + std::string("shadow_colored_") + itos(m_shadow_map_texture_size), + m_shadow_map_colored ? m_texture_format_color : m_texture_format, + true); + } + + // The merge all shadowmaps texture + if (!shadowMapTextureFinal) { + video::ECOLOR_FORMAT frt; + if (m_shadow_map_texture_32bit) { + if (m_shadow_map_colored) + frt = video::ECOLOR_FORMAT::ECF_A32B32G32R32F; + else + frt = video::ECOLOR_FORMAT::ECF_R32F; + } else { + if (m_shadow_map_colored) + frt = video::ECOLOR_FORMAT::ECF_A16B16G16R16F; + else + frt = video::ECOLOR_FORMAT::ECF_R16F; + } + shadowMapTextureFinal = getSMTexture( + std::string("shadowmap_final_") + itos(m_shadow_map_texture_size), + frt, true); + } + + if (!m_shadow_node_array.empty() && !m_light_list.empty()) { + bool reset_sm_texture = false; + + // detect if SM should be regenerated + for (DirectionalLight &light : m_light_list) { + if (light.should_update_map_shadow) { + light.should_update_map_shadow = false; + m_current_frame = 0; + reset_sm_texture = true; + } + } + + video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture; + if (shadowMapTargetTexture == nullptr) + shadowMapTargetTexture = shadowMapClientMap; + + // 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; + + // 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) { + 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_shadow_map_colored) { + m_driver->setRenderTarget(0, false, false); + m_driver->setRenderTarget(shadowMapTextureColors, + true, false, video::SColor(255, 255, 255, 255)); + } + renderShadowMap(shadowMapTextureColors, light, + scene::ESNRP_TRANSPARENT); + } + m_driver->setRenderTarget(0, false, false); + } + + reset_sm_texture = false; + } // end for lights + + // move to the next section + if (m_current_frame <= m_map_shadow_update_frames) + ++m_current_frame; + + // pass finished, swap textures and commit light changes + if (m_current_frame == m_map_shadow_update_frames) { + if (shadowMapClientMapFuture != nullptr) + std::swap(shadowMapClientMapFuture, shadowMapClientMap); + + // Let all lights know that maps are updated + for (DirectionalLight &light : m_light_list) + light.commitFrustum(); + } + } +} + +void ShadowRenderer::update(video::ITexture *outputTarget) +{ + if (!m_shadows_enabled || m_smgr->getActiveCamera() == nullptr) { + return; + } + + updateSMTextures(); + + 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; + + // render shadows for the n0n-map objects. + m_driver->setRenderTarget(shadowMapTextureDynamicObjects, true, + true, video::SColor(255, 255, 255, 255)); + renderShadowObjects(shadowMapTextureDynamicObjects, light); + // clear the Render Target + m_driver->setRenderTarget(0, false, false); + + // in order to avoid too many map shadow renders, + // we should make a second pass to mix clientmap shadows and + // entities shadows :( + m_screen_quad->getMaterial().setTexture(0, shadowMapClientMap); + // dynamic objs shadow texture. + if (m_shadow_map_colored) + m_screen_quad->getMaterial().setTexture(1, shadowMapTextureColors); + m_screen_quad->getMaterial().setTexture(2, shadowMapTextureDynamicObjects); + + m_driver->setRenderTarget(shadowMapTextureFinal, false, false, + video::SColor(255, 255, 255, 255)); + m_screen_quad->render(m_driver); + m_driver->setRenderTarget(0, false, false); + + } // end for lights + } +} + +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())); + + 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 (m_shadow_map_colored) { + + m_driver->draw2DImage(shadowMapTextureColors, + core::rect<s32>(128,128 + 50 + 128 + 128, + 128 + 128, 128 + 50 + 128 + 128 + 128), + core::rect<s32>({0, 0}, shadowMapTextureColors->getSize())); + } + #endif +} + + +video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name, + video::ECOLOR_FORMAT texture_format, bool force_creation) +{ + if (force_creation) { + return m_driver->addRenderTargetTexture( + core::dimension2du(m_shadow_map_texture_size, + m_shadow_map_texture_size), + shadow_map_name.c_str(), texture_format); + } + + return m_driver->getTexture(shadow_map_name.c_str()); +} + +void ShadowRenderer::renderShadowMap(video::ITexture *target, + DirectionalLight &light, scene::E_SCENE_NODE_RENDER_PASS pass) +{ + m_driver->setTransform(video::ETS_VIEW, light.getFutureViewMatrix()); + m_driver->setTransform(video::ETS_PROJECTION, light.getFutureProjectionMatrix()); + + // Operate on the client map + for (const auto &shadow_node : m_shadow_node_array) { + if (strcmp(shadow_node.node->getName(), "ClientMap") != 0) + continue; + + ClientMap *map_node = static_cast<ClientMap *>(shadow_node.node); + + video::SMaterial material; + if (map_node->getMaterialCount() > 0) { + // we only want the first material, which is the one with the albedo info + material = map_node->getMaterial(0); + } + + 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; + } + else { + material.MaterialType = (video::E_MATERIAL_TYPE) depth_shader; + 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); + break; + } +} + +void ShadowRenderer::renderShadowObjects( + video::ITexture *target, DirectionalLight &light) +{ + m_driver->setTransform(video::ETS_VIEW, light.getViewMatrix()); + m_driver->setTransform(video::ETS_PROJECTION, light.getProjectionMatrix()); + + for (const auto &shadow_node : m_shadow_node_array) { + // we only take care of the shadow casters + if (shadow_node.shadowMode == ESM_RECEIVE || + strcmp(shadow_node.node->getName(), "ClientMap") == 0) + continue; + + // render other objects + u32 n_node_materials = shadow_node.node->getMaterialCount(); + std::vector<s32> BufferMaterialList; + std::vector<std::pair<bool, bool>> BufferMaterialCullingList; + std::vector<video::E_BLEND_OPERATION> BufferBlendOperationList; + BufferMaterialList.reserve(n_node_materials); + BufferMaterialCullingList.reserve(n_node_materials); + BufferBlendOperationList.reserve(n_node_materials); + + // backup materialtype for each material + // (aka shader) + // and replace it by our "depth" shader + for (u32 m = 0; m < n_node_materials; m++) { + auto ¤t_mat = shadow_node.node->getMaterial(m); + + BufferMaterialList.push_back(current_mat.MaterialType); + current_mat.MaterialType = + (video::E_MATERIAL_TYPE)depth_shader_entities; + + BufferMaterialCullingList.emplace_back( + (bool)current_mat.BackfaceCulling, (bool)current_mat.FrontfaceCulling); + BufferBlendOperationList.push_back(current_mat.BlendOperation); + + 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, + shadow_node.node->getAbsoluteTransformation()); + shadow_node.node->render(); + + // restore the material. + + for (u32 m = 0; m < n_node_materials; m++) { + auto ¤t_mat = shadow_node.node->getMaterial(m); + + current_mat.MaterialType = (video::E_MATERIAL_TYPE) BufferMaterialList[m]; + + current_mat.BackfaceCulling = BufferMaterialCullingList[m].first; + current_mat.FrontfaceCulling = BufferMaterialCullingList[m].second; + current_mat.BlendOperation = BufferBlendOperationList[m]; + } + + } // end for caster shadow nodes +} + +void ShadowRenderer::mixShadowsQuad() +{ +} + +/* + * @Liso's disclaimer ;) This function loads the Shadow Mapping Shaders. + * I used a custom loader because I couldn't figure out how to use the base + * Shaders system with custom IShaderConstantSetCallBack without messing up the + * code too much. If anyone knows how to integrate this with the standard MT + * shaders, please feel free to change it. + */ + +void ShadowRenderer::createShaders() +{ + video::IGPUProgrammingServices *gpu = m_driver->getGPUProgrammingServices(); + + if (depth_shader == -1) { + std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass1_vertex.glsl"); + if (depth_shader_vs.empty()) { + m_shadows_enabled = 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_depth_cb = new ShadowDepthShaderCB(); + + depth_shader = 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::EMT_ONETEXTURE_BLEND); + + if (depth_shader == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_depth_cb; + m_shadows_enabled = false; + errorstream << "Error compiling shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader)->grab(); + } + + // This creates a clone of depth_shader with base material set to EMT_SOLID, + // because entities won't render shadows with base material EMP_ONETEXTURE_BLEND + 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; + 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + + 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); + + if (depth_shader_entities == -1) { + // upsi, something went wrong loading shader. + m_shadows_enabled = false; + errorstream << "Error compiling shadow mapping shader (dynamic)." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader_entities)->grab(); + } + + if (mixcsm_shader == -1) { + std::string depth_shader_vs = getShaderPath("shadow_shaders", "pass2_vertex.glsl"); + if (depth_shader_vs.empty()) { + m_shadows_enabled = 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; + errorstream << "Error cascade shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_mix_cb = new shadowScreenQuadCB(); + m_screen_quad = new shadowScreenQuad(); + mixcsm_shader = 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_mix_cb); + + m_screen_quad->getMaterial().MaterialType = + (video::E_MATERIAL_TYPE)mixcsm_shader; + + if (mixcsm_shader == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_mix_cb; + delete m_screen_quad; + m_shadows_enabled = false; + errorstream << "Error compiling cascade shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(mixcsm_shader)->grab(); + } + + 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; + 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; + errorstream << "Error shadow mapping fs shader not found." << std::endl; + return; + } + m_shadow_depth_trans_cb = new ShadowDepthShaderCB(); + + depth_shader_trans = 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_trans_cb); + + if (depth_shader_trans == -1) { + // upsi, something went wrong loading shader. + delete m_shadow_depth_trans_cb; + m_shadow_map_colored = false; + m_shadows_enabled = false; + errorstream << "Error compiling colored shadow mapping shader." << std::endl; + return; + } + + // HACK, TODO: investigate this better + // Grab the material renderer once more so minetest doesn't crash + // on exit + m_driver->getMaterialRenderer(depth_shader_trans)->grab(); + } +} + +std::string ShadowRenderer::readShaderFile(const std::string &path) +{ + std::string prefix; + if (m_shadow_map_colored) + prefix.append("#define COLORED_SHADOWS 1\n"); + + std::string content; + fs::ReadFile(path, content); + + return prefix + content; +} diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h new file mode 100644 index 000000000..e4b3c3e22 --- /dev/null +++ b/src/client/shadows/dynamicshadowsrender.h @@ -0,0 +1,147 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include <string> +#include <vector> +#include "irrlichttypes_extrabloated.h" +#include "client/shadows/dynamicshadows.h" + +class ShadowDepthShaderCB; +class shadowScreenQuad; +class shadowScreenQuadCB; + +enum E_SHADOW_MODE : u8 +{ + ESM_RECEIVE = 0, + ESM_BOTH, +}; + +struct NodeToApply +{ + NodeToApply(scene::ISceneNode *n, + E_SHADOW_MODE m = E_SHADOW_MODE::ESM_BOTH) : + node(n), + shadowMode(m){}; + bool operator<(const NodeToApply &other) const { return node < other.node; }; + + scene::ISceneNode *node; + + E_SHADOW_MODE shadowMode{E_SHADOW_MODE::ESM_BOTH}; + bool dirty{false}; +}; + +class ShadowRenderer +{ +public: + ShadowRenderer(IrrlichtDevice *device, Client *client); + + ~ShadowRenderer(); + + void initialize(); + + /// Adds a directional light shadow map (Usually just one (the sun) except in + /// Tattoine ). + size_t addDirectionalLight(); + DirectionalLight &getDirectionalLight(u32 index = 0); + size_t getDirectionalLightCount() const; + f32 getMaxShadowFar() const; + + /// Adds a shadow to the scene node. + /// The shadow mode can be ESM_BOTH, or ESM_RECEIVE. + /// ESM_BOTH casts and receives shadows + /// ESM_RECEIVE only receives but does not cast shadows. + /// + void addNodeToShadowList(scene::ISceneNode *node, + E_SHADOW_MODE shadowMode = ESM_BOTH); + void removeNodeFromShadowList(scene::ISceneNode *node); + + void update(video::ITexture *outputTarget = nullptr); + void drawDebug(); + + video::ITexture *get_texture() + { + return shadowMapTextureFinal; + } + + + bool is_active() const { return m_shadows_enabled; } + void setTimeOfDay(float isDay) { m_time_day = isDay; }; + + s32 getShadowSamples() const { return m_shadow_samples; } + float getShadowStrength() const { return m_shadow_strength; } + float getTimeOfDay() const { return m_time_day; } + +private: + video::ITexture *getSMTexture(const std::string &shadow_map_name, + video::ECOLOR_FORMAT texture_format, + bool force_creation = false); + + void renderShadowMap(video::ITexture *target, DirectionalLight &light, + scene::E_SCENE_NODE_RENDER_PASS pass = + scene::ESNRP_SOLID); + void renderShadowObjects(video::ITexture *target, DirectionalLight &light); + void mixShadowsQuad(); + void updateSMTextures(); + + // a bunch of variables + IrrlichtDevice *m_device{nullptr}; + scene::ISceneManager *m_smgr{nullptr}; + video::IVideoDriver *m_driver{nullptr}; + Client *m_client{nullptr}; + video::ITexture *shadowMapClientMap{nullptr}; + video::ITexture *shadowMapClientMapFuture{nullptr}; + video::ITexture *shadowMapTextureFinal{nullptr}; + video::ITexture *shadowMapTextureDynamicObjects{nullptr}; + video::ITexture *shadowMapTextureColors{nullptr}; + + std::vector<DirectionalLight> m_light_list; + std::vector<NodeToApply> m_shadow_node_array; + + float m_shadow_strength; + 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_shadow_map_colored; + u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */ + u8 m_current_frame{0}; /* Current frame */ + + video::ECOLOR_FORMAT m_texture_format{video::ECOLOR_FORMAT::ECF_R16F}; + video::ECOLOR_FORMAT m_texture_format_color{video::ECOLOR_FORMAT::ECF_R16G16}; + + // Shadow Shader stuff + + void createShaders(); + std::string readShaderFile(const std::string &path); + + s32 depth_shader{-1}; + s32 depth_shader_entities{-1}; + s32 depth_shader_trans{-1}; + s32 mixcsm_shader{-1}; + + ShadowDepthShaderCB *m_shadow_depth_cb{nullptr}; + ShadowDepthShaderCB *m_shadow_depth_trans_cb{nullptr}; + + shadowScreenQuad *m_screen_quad{nullptr}; + shadowScreenQuadCB *m_shadow_mix_cb{nullptr}; +}; diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp new file mode 100644 index 000000000..5f6d38157 --- /dev/null +++ b/src/client/shadows/shadowsScreenQuad.cpp @@ -0,0 +1,61 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 "shadowsScreenQuad.h" + +shadowScreenQuad::shadowScreenQuad() +{ + Material.Wireframe = false; + Material.Lighting = false; + + video::SColor color(0x0); + Vertices[0] = video::S3DVertex( + -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f); + Vertices[1] = video::S3DVertex( + -1.0f, 1.0f, 0.0f, 0, 0, 1, color, 0.0f, 0.0f); + Vertices[2] = video::S3DVertex( + 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f); + Vertices[3] = video::S3DVertex( + 1.0f, -1.0f, 0.0f, 0, 0, 1, color, 1.0f, 1.0f); + Vertices[4] = video::S3DVertex( + -1.0f, -1.0f, 0.0f, 0, 0, 1, color, 0.0f, 1.0f); + Vertices[5] = video::S3DVertex( + 1.0f, 1.0f, 0.0f, 0, 0, 1, color, 1.0f, 0.0f); +} + +void shadowScreenQuad::render(video::IVideoDriver *driver) +{ + u16 indices[6] = {0, 1, 2, 3, 4, 5}; + driver->setMaterial(Material); + driver->setTransform(video::ETS_WORLD, core::matrix4()); + driver->drawIndexedTriangleList(&Vertices[0], 6, &indices[0], 2); +} + +void shadowScreenQuadCB::OnSetConstants( + video::IMaterialRendererServices *services, s32 userData) +{ + s32 TextureId = 0; + m_sm_client_map_setting.set(&TextureId, services); + + TextureId = 1; + m_sm_client_map_trans_setting.set(&TextureId, services); + + TextureId = 2; + m_sm_dynamic_sampler_setting.set(&TextureId, services); +} diff --git a/src/client/shadows/shadowsScreenQuad.h b/src/client/shadows/shadowsScreenQuad.h new file mode 100644 index 000000000..c18be9a2b --- /dev/null +++ b/src/client/shadows/shadowsScreenQuad.h @@ -0,0 +1,54 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 <IMaterialRendererServices.h> +#include <IShaderConstantSetCallBack.h> +#include "client/shader.h" + +class shadowScreenQuad +{ +public: + shadowScreenQuad(); + + void render(video::IVideoDriver *driver); + video::SMaterial &getMaterial() { return Material; } + +private: + video::S3DVertex Vertices[6]; + video::SMaterial Material; +}; + +class shadowScreenQuadCB : public video::IShaderConstantSetCallBack +{ +public: + shadowScreenQuadCB() : + m_sm_client_map_setting("ShadowMapClientMap"), + m_sm_client_map_trans_setting("ShadowMapClientMapTraslucent"), + m_sm_dynamic_sampler_setting("ShadowMapSamplerdynamic") + {} + + virtual void OnSetConstants(video::IMaterialRendererServices *services, + s32 userData); +private: + CachedPixelShaderSetting<s32> m_sm_client_map_setting; + CachedPixelShaderSetting<s32> m_sm_client_map_trans_setting; + CachedPixelShaderSetting<s32> m_sm_dynamic_sampler_setting; +}; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp new file mode 100644 index 000000000..65a63f49c --- /dev/null +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -0,0 +1,36 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 "client/shadows/shadowsshadercallbacks.h" + +void ShadowDepthShaderCB::OnSetConstants( + video::IMaterialRendererServices *services, s32 userData) +{ + video::IVideoDriver *driver = services->getVideoDriver(); + + core::matrix4 lightMVP = driver->getTransform(video::ETS_PROJECTION); + lightMVP *= driver->getTransform(video::ETS_VIEW); + lightMVP *= driver->getTransform(video::ETS_WORLD); + + m_light_mvp_setting.set(lightMVP.pointer(), services); + m_map_resolution_setting.set(&MapRes, services); + m_max_far_setting.set(&MaxFar, services); + s32 TextureId = 0; + m_color_map_sampler_setting.set(&TextureId, services); +} diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h new file mode 100644 index 000000000..3549567c3 --- /dev/null +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -0,0 +1,48 @@ +/* +Minetest +Copyright (C) 2021 Liso <anlismon@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 <IMaterialRendererServices.h> +#include <IShaderConstantSetCallBack.h> +#include "client/shader.h" + +class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack +{ +public: + ShadowDepthShaderCB() : + m_light_mvp_setting("LightMVP"), + m_map_resolution_setting("MapResolution"), + m_max_far_setting("MaxFar"), + m_color_map_sampler_setting("ColorMapSampler") + {} + + void OnSetMaterial(const video::SMaterial &material) override {} + + void OnSetConstants(video::IMaterialRendererServices *services, + s32 userData) override; + + f32 MaxFar{2048.0f}, MapRes{1024.0f}; + +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; +}; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 47296a7a5..1cf9a4afc 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -122,7 +122,14 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade m_materials[i].Lighting = true; m_materials[i].MaterialType = video::EMT_SOLID; } + m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); + + 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); + } + setStarCount(1000, true); } @@ -175,17 +182,7 @@ void Sky::render() video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1); video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1); - float nightlength = 0.415; - float wn = nightlength / 2; - float wicked_time_of_day = 0; - if (m_time_of_day > wn && m_time_of_day < 1.0 - wn) - wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25; - else if (m_time_of_day < 0.5) - wicked_time_of_day = m_time_of_day / wn * 0.25; - else - wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25); - /*std::cerr<<"time_of_day="<<m_time_of_day<<" -> " - <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/ + float wicked_time_of_day = getWickedTimeOfDay(m_time_of_day); video::SColor suncolor = suncolor_f.toSColor(); video::SColor suncolor2 = suncolor2_f.toSColor(); @@ -739,10 +736,15 @@ void Sky::place_sky_body( * day_position: turn the body around the Z axis, to place it depending of the time of the day */ { + v3f centrum(0, 0, -1); + centrum.rotateXZBy(horizon_position); + centrum.rotateXYBy(day_position); + centrum.rotateYZBy(m_sky_body_orbit_tilt); for (video::S3DVertex &vertex : vertices) { // Body is directed to -Z (south) by default vertex.Pos.rotateXZBy(horizon_position); vertex.Pos.rotateXYBy(day_position); + vertex.Pos.Z += centrum.Z; } } @@ -931,3 +933,17 @@ void Sky::setSkyDefaults() m_moon_params = sky_defaults.getMoonDefaults(); m_star_params = sky_defaults.getStarDefaults(); } + +float getWickedTimeOfDay(float time_of_day) +{ + float nightlength = 0.415f; + float wn = nightlength / 2; + float wicked_time_of_day = 0; + if (time_of_day > wn && time_of_day < 1.0f - wn) + wicked_time_of_day = (time_of_day - wn) / (1.0f - wn * 2) * 0.5f + 0.25f; + else if (time_of_day < 0.5f) + wicked_time_of_day = time_of_day / wn * 0.25f; + else + wicked_time_of_day = 1.0f - ((1.0f - time_of_day) / wn * 0.25f); + return wicked_time_of_day; +} diff --git a/src/client/sky.h b/src/client/sky.h index 121a16bb7..83106453b 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -105,6 +105,8 @@ public: ITextureSource *tsrc); const video::SColorf &getCurrentStarColor() const { return m_star_color; } + float getSkyBodyOrbitTilt() const { return m_sky_body_orbit_tilt; } + private: aabb3f m_box; video::SMaterial m_materials[SKY_MATERIAL_COUNT]; @@ -159,6 +161,7 @@ private: bool m_directional_colored_fog; bool m_in_clouds = true; // Prevent duplicating bools to remember old values bool m_enable_shaders = false; + float m_sky_body_orbit_tilt = 0.0f; video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); @@ -205,3 +208,7 @@ private: float horizon_position, float day_position); void setSkyDefaults(); }; + +// calculates value for sky body positions for the given observed time of day +// this is used to draw both Sun/Moon and shadows +float getWickedTimeOfDay(float time_of_day); diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 8dceeede6..0eda8842b 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -362,6 +362,14 @@ public: for (auto &buffer : m_buffers) { for (SoundBuffer *sb : buffer.second) { + alDeleteBuffers(1, &sb->buffer_id); + + ALenum error = alGetError(); + if (error != AL_NO_ERROR) { + warningstream << "Audio: Failed to free stream for " + << buffer.first << ": " << alErrorString(error) << std::endl; + } + delete sb; } buffer.second.clear(); diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 96312ea27..a31e3aca1 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -81,12 +81,8 @@ static bool replace_ext(std::string &path, const char *ext) std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions - const char *extensions[] = { - "png", "jpg", "bmp", "tga", - "pcx", "ppm", "psd", "wal", "rgb", - NULL - }; - // If there is no extension, add one + const char *extensions[] = { "png", "jpg", "bmp", "tga", NULL }; + // If there is no extension, assume PNG if (removeStringEnd(path, extensions).empty()) path = path + ".png"; // Check paths until something is found to exist diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 08fd49fc0..6beed3f3a 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include <map> #include <IMeshManipulator.h> +#include "client/renderingengine.h" #define WIELD_SCALE_FACTOR 30.0 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0 @@ -220,11 +221,18 @@ WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool l m_meshnode->setReadOnlyMaterials(false); m_meshnode->setVisible(false); dummymesh->drop(); // m_meshnode grabbed it + + m_shadow = RenderingEngine::get_shadow_renderer(); } WieldMeshSceneNode::~WieldMeshSceneNode() { sanity_check(g_extrusion_mesh_cache); + + // Remove node from shadow casters. m_shadow might be an invalid pointer! + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->removeNodeFromShadowList(m_meshnode); + if (g_extrusion_mesh_cache->drop()) g_extrusion_mesh_cache = nullptr; } @@ -527,6 +535,10 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) // need to normalize normals when lighting is enabled (because of setScale()) m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting); m_meshnode->setVisible(true); + + // Add mesh to shadow caster + if (m_shadow) + m_shadow->addNodeToShadowList(m_meshnode); } void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 933097230..d1eeb64f5 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -27,6 +27,7 @@ struct ItemStack; class Client; class ITextureSource; struct ContentFeatures; +class ShadowRenderer; /*! * Holds color information of an item mesh's buffer. @@ -124,6 +125,8 @@ private: // so this variable is just required so we can implement // getBoundingBox() and is set to an empty box. aabb3f m_bounding_box; + + ShadowRenderer *m_shadow; }; void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); diff --git a/src/content/mods.cpp b/src/content/mods.cpp index f791fa797..6f088a5b3 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -30,6 +30,29 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "script/common/c_internal.h" +void ModSpec::checkAndLog() const +{ + if (!string_allowed(name, MODNAME_ALLOWED_CHARS)) { + throw ModError("Error loading mod \"" + name + + "\": Mod name does not follow naming conventions: " + "Only characters [a-z0-9_] are allowed."); + } + + // Log deprecation messages + auto handling_mode = get_deprecated_handling_mode(); + if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) { + std::ostringstream os; + os << "Mod " << name << " 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(); + } +} + bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols) { dep = trim(dep); @@ -45,22 +68,6 @@ bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols) return !dep.empty(); } -static void log_mod_deprecation(const ModSpec &spec, const std::string &warning) -{ - auto handling_mode = get_deprecated_handling_mode(); - if (handling_mode != DeprecatedHandlingMode::Ignore) { - std::ostringstream os; - os << warning << " (" << spec.name << " at " << spec.path << ")" - << std::endl; - - if (handling_mode == DeprecatedHandlingMode::Error) { - throw ModError(os.str()); - } else { - warningstream << os.str(); - } - } -} - void parseModContents(ModSpec &spec) { // NOTE: this function works in mutual recursion with getModsInPath @@ -90,8 +97,7 @@ void parseModContents(ModSpec &spec) if (info.exists("name")) spec.name = info.get("name"); else - log_mod_deprecation(spec, "Mods not having a mod.conf file with " - "the name is deprecated."); + 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"); @@ -132,8 +138,7 @@ void parseModContents(ModSpec &spec) std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str()); if (is.good()) - log_mod_deprecation(spec, "depends.txt is deprecated, " - "please use mod.conf instead."); + spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead."); while (is.good()) { std::string dep; @@ -155,10 +160,8 @@ void parseModContents(ModSpec &spec) if (info.exists("description")) spec.desc = info.get("description"); - else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", - spec.desc)) - log_mod_deprecation(spec, "description.txt is deprecated, please " - "use mod.conf instead."); + 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."); } } diff --git a/src/content/mods.h b/src/content/mods.h index b3500fbc8..b56a97edb 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -49,6 +49,9 @@ struct ModSpec bool part_of_modpack = false; bool is_modpack = false; + // 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 = "") : @@ -59,6 +62,8 @@ struct ModSpec name(name), path(path), part_of_modpack(part_of_modpack) { } + + void checkAndLog() const; }; // Retrieves depends, optdepends, is_modpack and modpack_content diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp index 73cd63f6d..39f4c8442 100644 --- a/src/database/database-leveldb.cpp +++ b/src/database/database-leveldb.cpp @@ -70,11 +70,11 @@ bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block) { - std::string datastr; leveldb::Status status = m_database->Get(leveldb::ReadOptions(), - i64tos(getBlockAsInteger(pos)), &datastr); + i64tos(getBlockAsInteger(pos)), block); - *block = (status.ok()) ? datastr : ""; + if (!status.ok()) + block->clear(); } bool Database_LevelDB::deleteBlock(const v3s16 &pos) @@ -131,7 +131,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) std::string (long) serialized_inventory */ - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); PlayerSAO *sao = player->getPlayerSAO(); @@ -142,7 +142,7 @@ void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player) writeF32(os, sao->getRotation().Y); writeU16(os, sao->getBreath()); - StringMap stringvars = sao->getMeta().getStrings(); + const auto &stringvars = sao->getMeta().getStrings(); writeU32(os, stringvars.size()); for (const auto &it : stringvars) { os << serializeString16(it.first); @@ -170,7 +170,7 @@ bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao) player->getName(), &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); if (readU8(is) > 1) return false; @@ -230,7 +230,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) leveldb::Status s = m_database->Get(leveldb::ReadOptions(), name, &raw); if (!s.ok()) return false; - std::istringstream is(raw); + std::istringstream is(raw, std::ios_base::binary); /* u8 version = 1 @@ -262,7 +262,7 @@ bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res) bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry) { - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); writeU8(os, 1); os << serializeString16(authEntry.password); diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index e1bb39928..3469f4242 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -39,20 +39,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include "server/player_sao.h" -Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) : +Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string, + const char *type) : m_connect_string(connect_string) { if (m_connect_string.empty()) { - throw SettingNotFoundException( - "Set pgsql_connection string in world.mt to " + // Use given type to reference the exact setting in the error message + std::string s = type; + std::string msg = + "Set pgsql" + s + "_connection string in world.mt to " "use the postgresql backend\n" "Notes:\n" - "pgsql_connection has the following form: \n" - "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user " - "password=mt_password dbname=minetest_world\n" + "pgsql" + s + "_connection has the following form: \n" + "\tpgsql" + s + "_connection = host=127.0.0.1 port=5432 " + "user=mt_user password=mt_password dbname=minetest" + s + "\n" "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and " - "DELETE rights on the database.\n" - "Don't create mt_user as a SUPERUSER!"); + "DELETE rights on the database. " + "Don't create mt_user as a SUPERUSER!"; + throw SettingNotFoundException(msg); } } @@ -166,7 +170,7 @@ void Database_PostgreSQL::rollback() } MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), + Database_PostgreSQL(connect_string, ""), MapDatabase() { connectToDatabase(); @@ -270,10 +274,10 @@ void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block) PGresult *results = execPrepared("read_block", ARRLEN(args), args, argLen, argFmt, false); - *block = ""; - if (PQntuples(results)) - *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + block->assign(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0)); + else + block->clear(); PQclear(results); } @@ -315,7 +319,7 @@ void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst) * Player Database */ PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string): - Database_PostgreSQL(connect_string), + Database_PostgreSQL(connect_string, "_player"), PlayerDatabase() { connectToDatabase(); @@ -492,6 +496,7 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("remove_player_inventory_items", 1, rmvalues); std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; const std::string &name = list->getName(); @@ -508,9 +513,10 @@ void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player) execPrepared("add_player_inventory", 5, inv_values); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(), slotId = itos(j); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(), slotId = itos(j); const char* invitem_values[] = { player->getName(), @@ -637,7 +643,8 @@ void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res) } AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string) : - Database_PostgreSQL(connect_string), AuthDatabase() + Database_PostgreSQL(connect_string, "_auth"), + AuthDatabase() { connectToDatabase(); } diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index f47deda33..81b4a2b10 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -29,7 +29,7 @@ class Settings; class Database_PostgreSQL: public Database { public: - Database_PostgreSQL(const std::string &connect_string); + Database_PostgreSQL(const std::string &connect_string, const char *type); ~Database_PostgreSQL(); void beginSave(); diff --git a/src/database/database-redis.cpp b/src/database/database-redis.cpp index 096ea504d..5ffff67b7 100644 --- a/src/database/database-redis.cpp +++ b/src/database/database-redis.cpp @@ -127,8 +127,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) switch (reply->type) { case REDIS_REPLY_STRING: { - *block = std::string(reply->str, reply->len); - // std::string copies the memory so this won't cause any problems + block->assign(reply->str, reply->len); freeReplyObject(reply); return; } @@ -141,8 +140,7 @@ void Database_Redis::loadBlock(const v3s16 &pos, std::string *block) "Redis command 'HGET %s %s' errored: ") + errstr); } case REDIS_REPLY_NIL: { - *block = ""; - // block not found in database + block->clear(); freeReplyObject(reply); return; } diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 4560743b9..898acc265 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -302,7 +302,10 @@ void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block) const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); size_t len = sqlite3_column_bytes(m_stmt_read, 0); - *block = (data) ? std::string(data, len) : ""; + if (data) + block->assign(data, len); + else + block->clear(); sqlite3_step(m_stmt_read); // We should never get more than 1 row, so ok to reset @@ -491,6 +494,7 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_remove_inventory_items); std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists(); + std::ostringstream oss; for (u16 i = 0; i < inventory_lists.size(); i++) { const InventoryList* list = inventory_lists[i]; @@ -503,9 +507,10 @@ void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player) sqlite3_reset(m_stmt_player_add_inventory); for (u32 j = 0; j < list->getSize(); j++) { - std::ostringstream os; - list->getItem(j).serialize(os); - std::string itemStr = os.str(); + oss.str(""); + oss.clear(); + list->getItem(j).serialize(oss); + std::string itemStr = oss.str(); str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 3e784523d..64335afff 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -65,6 +65,8 @@ void set_default_settings() 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("chat_weblink_color", "#8888FF"); // Cheat Menu settings->setDefault("cheat_menu_font", "FM_Standard"); @@ -246,7 +248,6 @@ void set_default_settings() settings->setDefault("screen_h", "600"); settings->setDefault("autosave_screensize", "true"); settings->setDefault("fullscreen", "false"); - settings->setDefault("fullscreen_bpp", "24"); settings->setDefault("vsync", "false"); settings->setDefault("fov", "72"); settings->setDefault("leaves_style", "fancy"); @@ -331,6 +332,18 @@ void set_default_settings() settings->setDefault("enable_waving_leaves", "false"); settings->setDefault("enable_waving_plants", "false"); + // Effects Shadows + settings->setDefault("enable_dynamic_shadows", "false"); + settings->setDefault("shadow_strength", "0.2"); + settings->setDefault("shadow_map_max_distance", "200.0"); + settings->setDefault("shadow_map_texture_size", "2048"); + settings->setDefault("shadow_map_texture_32bit", "true"); + settings->setDefault("shadow_map_color", "false"); + 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_sky_body_orbit_tilt", "0.0"); // Input settings->setDefault("invert_mouse", "false"); @@ -454,7 +467,7 @@ void set_default_settings() settings->setDefault("chat_message_limit_per_10sec", "8.0"); settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("sqlite_synchronous", "2"); - settings->setDefault("map_compression_level_disk", "3"); + settings->setDefault("map_compression_level_disk", "-1"); settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); settings->setDefault("dedicated_server_step", "0.09"); @@ -511,7 +524,6 @@ void set_default_settings() settings->setDefault("server_name", ""); settings->setDefault("server_description", ""); - settings->setDefault("high_precision_fpu", "true"); settings->setDefault("enable_console", "false"); settings->setDefault("screen_dpi", "72"); @@ -541,7 +553,7 @@ void set_default_settings() 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", "3"); + settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("server_map_save_interval", "15"); settings->setDefault("client_mapblock_limit", "1000"); settings->setDefault("active_block_range", "2"); diff --git a/src/emerge.cpp b/src/emerge.cpp index 32e7d9f24..9234fe6d3 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -165,20 +165,17 @@ EmergeManager::EmergeManager(Server *server) if (nthreads < 1) nthreads = 1; - m_qlimit_total = g_settings->getU16("emergequeue_limit_total"); + m_qlimit_total = g_settings->getU32("emergequeue_limit_total"); // FIXME: these fallback values are probably not good - if (!g_settings->getU16NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly)) + if (!g_settings->getU32NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly)) m_qlimit_diskonly = nthreads * 5 + 1; - if (!g_settings->getU16NoEx("emergequeue_limit_generate", m_qlimit_generate)) + if (!g_settings->getU32NoEx("emergequeue_limit_generate", m_qlimit_generate)) m_qlimit_generate = nthreads + 1; // don't trust user input for something very important like this - if (m_qlimit_total < 1) - m_qlimit_total = 1; - if (m_qlimit_diskonly < 1) - m_qlimit_diskonly = 1; - if (m_qlimit_generate < 1) - m_qlimit_generate = 1; + m_qlimit_total = rangelim(m_qlimit_total, 1, 1000000); + m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 1, 1000000); + m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000); for (s16 i = 0; i < nthreads; i++) m_threads.push_back(new EmergeThread(server, i)); @@ -358,6 +355,13 @@ bool EmergeManager::enqueueBlockEmergeEx( } +bool EmergeManager::isBlockInQueue(v3s16 pos) +{ + MutexAutoLock queuelock(m_queue_mutex); + return m_blocks_enqueued.find(pos) != m_blocks_enqueued.end(); +} + + // // Mapgen-related helper functions // @@ -418,14 +422,14 @@ bool EmergeManager::pushBlockEmergeData( void *callback_param, bool *entry_already_exists) { - u16 &count_peer = m_peer_queue_count[peer_requested]; + u32 &count_peer = m_peer_queue_count[peer_requested]; if ((flags & BLOCK_EMERGE_FORCE_QUEUE) == 0) { if (m_blocks_enqueued.size() >= m_qlimit_total) return false; if (peer_requested != PEER_ID_INEXISTENT) { - u16 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? + u32 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? m_qlimit_generate : m_qlimit_diskonly; if (count_peer >= qlimit_peer) return false; @@ -460,20 +464,18 @@ bool EmergeManager::pushBlockEmergeData( bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) { - std::map<v3s16, BlockEmergeData>::iterator it; - std::unordered_map<u16, u16>::iterator it2; - - it = m_blocks_enqueued.find(pos); + auto it = m_blocks_enqueued.find(pos); if (it == m_blocks_enqueued.end()) return false; *bedata = it->second; - it2 = m_peer_queue_count.find(bedata->peer_requested); + auto it2 = m_peer_queue_count.find(bedata->peer_requested); if (it2 == m_peer_queue_count.end()) return false; - u16 &count_peer = it2->second; + u32 &count_peer = it2->second; + assert(count_peer != 0); count_peer--; @@ -645,7 +647,7 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_server->getScriptIface()->environment_OnGenerated( minp, maxp, m_mapgen->blockseed); } catch (LuaError &e) { - m_server->setAsyncFatalError("Lua: finishGen" + std::string(e.what())); + m_server->setAsyncFatalError(e); } /* diff --git a/src/emerge.h b/src/emerge.h index aac3e7dd3..e2d727973 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -174,6 +174,8 @@ public: EmergeCompletionCallback callback, void *callback_param); + bool isBlockInQueue(v3s16 pos); + v3s16 getContainingChunk(v3s16 blockpos); Mapgen *getCurrentMapgen(); @@ -192,11 +194,11 @@ private: std::mutex m_queue_mutex; std::map<v3s16, BlockEmergeData> m_blocks_enqueued; - std::unordered_map<u16, u16> m_peer_queue_count; + std::unordered_map<u16, u32> m_peer_queue_count; - u16 m_qlimit_total; - u16 m_qlimit_diskonly; - u16 m_qlimit_generate; + u32 m_qlimit_total; + u32 m_qlimit_diskonly; + u32 m_qlimit_generate; // Managers of various map generation-related components // Note that each Mapgen gets a copy(!) of these to work with diff --git a/src/filesys.cpp b/src/filesys.cpp index 99b030624..a07370c0e 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include <iostream> #include <cstdio> +#include <cstdlib> #include <cstring> #include <cerrno> #include <fstream> @@ -36,6 +37,7 @@ namespace fs #define _WIN32_WINNT 0x0501 #include <windows.h> #include <shlwapi.h> +#include <io.h> std::vector<DirListNode> GetDirListing(const std::string &pathstring) { @@ -176,13 +178,27 @@ std::string TempPath() errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; return ""; } - std::vector<char> buf(bufsize); + std::string buf; + buf.resize(bufsize); DWORD len = GetTempPath(bufsize, &buf[0]); if(len == 0 || len > bufsize){ errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; return ""; } - return std::string(buf.begin(), buf.begin() + len); + buf.resize(len); + return buf; +} + +std::string CreateTempFile() +{ + std::string path = TempPath() + DIR_DELIM "MT_XXXXXX"; + _mktemp_s(&path[0], path.size() + 1); // modifies path + HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + return ""; + CloseHandle(file); + return path; } #else // POSIX @@ -364,6 +380,16 @@ std::string TempPath() #endif } +std::string CreateTempFile() +{ + std::string path = TempPath() + DIR_DELIM "MT_XXXXXX"; + int fd = mkstemp(&path[0]); // modifies path + if (fd == -1) + return ""; + close(fd); + return path; +} + #endif void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir) diff --git a/src/filesys.h b/src/filesys.h index a9584b036..f72cb0ba2 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -71,6 +71,10 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path); // Returns path to temp directory, can return "" on error std::string TempPath(); +// Returns path to securely-created temporary file (will already exist when this function returns) +// can return "" on error +std::string CreateTempFile(); + /* Returns a list of subdirectories, including the path itself, but excluding hidden directories (whose names start with . or _) */ diff --git a/src/gettext.cpp b/src/gettext.cpp index 6818004df..de042cf35 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -127,6 +127,10 @@ void init_gettext(const char *path, const std::string &configured_language, // Add user specified locale to environment setenv("LANGUAGE", configured_language.c_str(), 1); +#ifdef __ANDROID__ + setenv("LANG", configured_language.c_str(), 1); +#endif + // Reload locale with changed environment setlocale(LC_ALL, ""); #elif defined(_MSC_VER) diff --git a/src/gettext.h b/src/gettext.h index 42b375d86..5a3654be4 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -59,3 +59,21 @@ inline std::string strgettext(const std::string &text) { return text.empty() ? "" : gettext(text.c_str()); } + +/** + * Returns translated string with format args applied + * + * @tparam Args Template parameter for format args + * @param src Translation source string + * @param args Variable format args + * @return translated string + */ +template <typename ...Args> +inline std::wstring fwgettext(const char *src, Args&&... args) +{ + wchar_t buf[255]; + const wchar_t* str = wgettext(src); + swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, std::forward<Args>(args)...); + delete[] str; + return std::wstring(buf); +} diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index baaaea5e8..0610c85cc 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -41,6 +41,10 @@ inline u32 clamp_u8(s32 value) return (u32) MYMIN(MYMAX(value, 0), 255); } +inline bool isInCtrlKeys(const irr::EKEY_CODE& kc) +{ + return kc == KEY_LCONTROL || kc == KEY_RCONTROL || kc == KEY_CONTROL; +} GUIChatConsole::GUIChatConsole( gui::IGUIEnvironment* env, @@ -91,6 +95,10 @@ GUIChatConsole::GUIChatConsole( // set default cursor options setCursor(true, true, 2.0, 0.1); + + // track ctrl keys for mouse event + m_is_ctrl_down = false; + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); } GUIChatConsole::~GUIChatConsole() @@ -330,7 +338,7 @@ void GUIChatConsole::drawText() false, false, &AbsoluteClippingRect); - } else + } else #endif { // Otherwise use standard text @@ -405,8 +413,21 @@ bool GUIChatConsole::OnEvent(const SEvent& event) ChatPrompt &prompt = m_chat_backend->getPrompt(); - if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + if (event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) + { + // CTRL up + if (isInCtrlKeys(event.KeyInput.Key)) + { + m_is_ctrl_down = false; + } + } + else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) { + // CTRL down + if (isInCtrlKeys(event.KeyInput.Key)) { + m_is_ctrl_down = true; + } + // Key input if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) { closeConsole(); @@ -559,8 +580,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) const c8 *text = os_operator->getTextFromClipboard(); if (!text) return true; - std::basic_string<unsigned char> str((const unsigned char*)text); - prompt.input(std::wstring(str.begin(), str.end())); + prompt.input(utf8_to_wide(text)); return true; } else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) @@ -613,11 +633,24 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.EventType == EET_MOUSE_INPUT_EVENT) { - if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { s32 rows = myround(-3.0 * event.MouseInput.Wheel); m_chat_backend->scroll(rows); } + // Middle click or ctrl-click opens weblink, if enabled in config + else if(m_cache_clickable_chat_weblinks && ( + event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN || + (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN && m_is_ctrl_down) + )) + { + // If clicked within console output region + if (event.MouseInput.Y / m_fontsize.Y < (m_height / m_fontsize.Y) - 1 ) + { + // Translate pixel position to font position + middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); + } + } } #if (IRRLICHT_VERSION_MT_REVISION >= 2) else if(event.EventType == EET_STRING_INPUT_EVENT) @@ -640,3 +673,62 @@ void GUIChatConsole::setVisible(bool visible) } } +void GUIChatConsole::middleClick(s32 col, s32 row) +{ + // Prevent accidental rapid clicking + static u64 s_oldtime = 0; + u64 newtime = porting::getTimeMs(); + + // 0.6 seconds should suffice + if (newtime - s_oldtime < 600) + return; + s_oldtime = newtime; + + const std::vector<ChatFormattedFragment> & + frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments; + std::string weblink = ""; // from frag meta + + // Identify targetted fragment, if exists + int indx = frags.size() - 1; + if (indx < 0) { + // Invalid row, frags is empty + return; + } + // Scan from right to left, offset by 1 font space because left margin + while (indx > -1 && (u32)col < frags[indx].column + 1) { + --indx; + } + if (indx > -1) { + weblink = frags[indx].weblink; + // Note if(indx < 0) then a frag somehow had a corrupt column field + } + + /* + // Debug help. Please keep this in case adjustments are made later. + std::string ws; + ws = "Middleclick: (" + std::to_string(col) + ',' + std::to_string(row) + ')' + " frags:"; + // show all frags <position>(<length>) for the clicked row + for (u32 i=0;i<frags.size();++i) { + if (indx == int(i)) + // tag the actual clicked frag + ws += '*'; + ws += std::to_string(frags.at(i).column) + '(' + + std::to_string(frags.at(i).text.size()) + "),"; + } + actionstream << ws << std::endl; + */ + + // User notification + if (weblink.size() != 0) { + std::ostringstream msg; + msg << " * "; + if (porting::open_url(weblink)) { + msg << gettext("Opening webpage"); + } + else { + msg << gettext("Failed to open webpage"); + } + msg << " '" << weblink << "'"; + m_chat_backend->addUnparsedMessage(utf8_to_wide(msg.str())); + } +} diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 1152f2b2d..32628f0d8 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -84,6 +84,9 @@ private: void drawText(); void drawPrompt(); + // If clicked fragment has a web url, send it to the system default web browser + void middleClick(s32 col, s32 row); + private: ChatBackend* m_chat_backend; Client* m_client; @@ -126,4 +129,9 @@ private: // font gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; + // Track if a ctrl key is currently held down + bool m_is_ctrl_down; }; diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp index ba548aa2d..8459107cd 100644 --- a/src/gui/guiEditBox.cpp +++ b/src/gui/guiEditBox.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "IGUIFont.h" #include "porting.h" +#include "util/string.h" GUIEditBox::~GUIEditBox() { @@ -232,10 +233,6 @@ bool GUIEditBox::OnEvent(const SEvent &event) bool GUIEditBox::processKey(const SEvent &event) { - if (!m_writable) { - return false; - } - if (!event.KeyInput.PressedDown) return false; @@ -521,8 +518,7 @@ void GUIEditBox::onKeyControlC(const SEvent &event) const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - core::stringc s; - s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + std::string s = stringw_to_utf8(Text.subString(realmbgn, realmend - realmbgn)); m_operator->copyToClipboard(s.c_str()); } @@ -531,6 +527,9 @@ bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_e // First copy to clipboard onKeyControlC(event); + if (!m_writable) + return false; + if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) return false; @@ -556,7 +555,7 @@ bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_e bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled()) + if (!isEnabled() || !m_writable) return false; // paste from the clipboard @@ -568,29 +567,28 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e // add new character if (const c8 *p = m_operator->getTextFromClipboard()) { + core::stringw inserted_text = utf8_to_stringw(p); if (m_mark_begin == m_mark_end) { // insert text core::stringw s = Text.subString(0, m_cursor_pos); - s.append(p); + s.append(inserted_text); s.append(Text.subString( m_cursor_pos, Text.size() - m_cursor_pos)); if (!m_max || s.size() <= m_max) { Text = s; - s = p; - m_cursor_pos += s.size(); + m_cursor_pos += inserted_text.size(); } } else { // replace text core::stringw s = Text.subString(0, realmbgn); - s.append(p); + s.append(inserted_text); s.append(Text.subString(realmend, Text.size() - realmend)); if (!m_max || s.size() <= m_max) { Text = s; - s = p; - m_cursor_pos = realmbgn + s.size(); + m_cursor_pos = realmbgn + inserted_text.size(); } } } @@ -602,7 +600,7 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled() || Text.empty()) + if (!isEnabled() || Text.empty() || !m_writable) return false; core::stringw s; @@ -640,7 +638,7 @@ bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) bool GUIEditBox::onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end) { - if (!isEnabled() || Text.empty()) + if (!isEnabled() || Text.empty() || !m_writable) return false; core::stringw s; diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index c72070787..fb4bc2a0b 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -620,6 +620,17 @@ void GUIEditBoxWithScrollBar::createVScrollBar() if (Environment) skin = Environment->getSkin(); + s32 fontHeight = 1; + + if (m_override_font) { + fontHeight = m_override_font->getDimension(L"Ay").Height; + } else { + IGUIFont *font; + if (skin && (font = skin->getFont())) { + fontHeight = font->getDimension(L"Ay").Height; + } + } + m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; irr::core::rect<s32> scrollbarrect = m_frame_rect; @@ -628,8 +639,8 @@ void GUIEditBoxWithScrollBar::createVScrollBar() scrollbarrect, false, true); m_vscrollbar->setVisible(false); - m_vscrollbar->setSmallStep(1); - m_vscrollbar->setLargeStep(1); + m_vscrollbar->setSmallStep(3 * fontHeight); + m_vscrollbar->setLargeStep(10 * fontHeight); } diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 694baf482..c39c3ee0d 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -437,9 +437,22 @@ void GUIEngine::drawBackground(video::IVideoDriver *driver) return; } + // Chop background image to the smaller screen dimension + v2u32 bg_size = screensize; + v2f32 scale( + (f32) bg_size.X / sourcesize.X, + (f32) bg_size.Y / sourcesize.Y); + if (scale.X < scale.Y) + bg_size.X = (int) (scale.Y * sourcesize.X); + else + bg_size.Y = (int) (scale.X * sourcesize.Y); + v2s32 offset = v2s32( + (s32) screensize.X - (s32) bg_size.X, + (s32) screensize.Y - (s32) bg_size.Y + ) / 2; /* Draw background texture */ draw2DImageFilterScaled(driver, texture, - core::rect<s32>(0, 0, screensize.X, screensize.Y), + core::rect<s32>(offset.X, offset.Y, bg_size.X + offset.X, bg_size.Y + offset.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, true); } @@ -614,10 +627,3 @@ void GUIEngine::stopSound(s32 handle) { m_sound_manager->stopSound(handle); } - -/******************************************************************************/ -unsigned int GUIEngine::queueAsync(const std::string &serialized_func, - const std::string &serialized_params) -{ - return m_script->queueAsync(serialized_func, serialized_params); -} diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index 70abce181..d7e6485ef 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -175,10 +175,6 @@ public: return m_scriptdir; } - /** pass async callback to scriptengine **/ - unsigned int queueAsync(const std::string &serialized_fct, - const std::string &serialized_params); - private: /** find and run the main menu script */ diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index c6435804f..797fd3ff6 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1577,11 +1577,10 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, } e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); - if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { - e->setDrawBackground(false); - } + bool border = style.getBool(StyleSpec::BORDER, true); + e->setDrawBorder(border); + e->setDrawBackground(border); e->setOverrideFont(style.getFont()); e->drop(); @@ -3934,9 +3933,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) } if (e != 0) { - std::stringstream ss; - ss << (e->getActiveTab() +1); - fields[name] = ss.str(); + fields[name] = itos(e->getActiveTab() + 1); } } else if (s.ftype == f_CheckBox) { // No dynamic cast possible due to some distributions shipped @@ -3962,12 +3959,10 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) e = static_cast<GUIScrollBar *>(element); if (e) { - std::stringstream os; - os << e->getPos(); if (s.fdefault == L"Changed") - fields[name] = "CHG:" + os.str(); + fields[name] = "CHG:" + itos(e->getPos()); else - fields[name] = "VAL:" + os.str(); + fields[name] = "VAL:" + itos(e->getPos()); } } else if (s.ftype == f_AnimatedImage) { // No dynamic cast possible due to some distributions shipped diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index f17cfa986..61ab758a1 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -93,11 +93,12 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 160 * s, 20 * s); rect = rect + v2s32(size.X / 2 - 80 * s, size.Y / 2 - 70 * s); - const wchar_t *text = wgettext("Sound Volume: "); + wchar_t text[100]; + const wchar_t *str = wgettext("Sound Volume: %d%%"); + swprintf(text, sizeof(text) / sizeof(wchar_t), str, volume); + delete[] str; core::stringw volume_text = text; - delete [] text; - volume_text += core::stringw(volume) + core::stringw("%"); Environment->addStaticText(volume_text.c_str(), rect, false, true, this, ID_soundText); } @@ -183,11 +184,13 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) g_settings->setFloat("sound_volume", (float) pos / 100); gui::IGUIElement *e = getElementFromId(ID_soundText); - const wchar_t *text = wgettext("Sound Volume: "); + wchar_t text[100]; + const wchar_t *str = wgettext("Sound Volume: %d%%"); + swprintf(text, sizeof(text) / sizeof(wchar_t), str, pos); + delete[] str; + core::stringw volume_text = text; - delete [] text; - volume_text += core::stringw(pos) + core::stringw("%"); e->setText(volume_text.c_str()); return true; } diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 0d3fb55f0..1016de389 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -268,7 +268,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) std::string label = wide_to_utf8(getLabelByID(hovered->getID())); if (label.empty()) label = "text"; - message += gettext(label) + ":"; + message += strgettext(label) + ":"; // single line text input int type = 2; diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 78b18c2d9..eb20b7e70 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -32,8 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <algorithm> -#include <ISceneCollisionManager.h> - using namespace irr::core; const char **button_imagenames = (const char *[]) { diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 6137782ff..1307bfec3 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -761,10 +761,12 @@ void httpfetch_cleanup() { verbosestream<<"httpfetch_cleanup: cleaning up"<<std::endl; - g_httpfetch_thread->stop(); - g_httpfetch_thread->requestWakeUp(); - g_httpfetch_thread->wait(); - delete g_httpfetch_thread; + if (g_httpfetch_thread) { + g_httpfetch_thread->stop(); + g_httpfetch_thread->requestWakeUp(); + g_httpfetch_thread->wait(); + delete g_httpfetch_thread; + } curl_global_cleanup(); } diff --git a/src/hud.cpp b/src/hud.cpp index 1791e04df..e4ad7940f 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -50,6 +50,7 @@ const struct EnumString es_HudElementStat[] = {HUD_STAT_SIZE, "size"}, {HUD_STAT_Z_INDEX, "z_index"}, {HUD_STAT_TEXT2, "text2"}, + {HUD_STAT_STYLE, "style"}, {0, NULL}, }; @@ -33,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_CORNER_LOWER 1 #define HUD_CORNER_CENTER 2 +#define HUD_STYLE_BOLD 1 +#define HUD_STYLE_ITALIC 2 +#define HUD_STYLE_MONO 4 + // Note that these visibility flags do not determine if the hud items are // actually drawn, but rather, whether to draw the item should the rest // of the game state permit it. @@ -78,6 +82,7 @@ enum HudElementStat { HUD_STAT_SIZE, HUD_STAT_Z_INDEX, HUD_STAT_TEXT2, + HUD_STAT_STYLE, }; enum HudCompassDir { @@ -102,6 +107,7 @@ struct HudElement { v2s32 size; s16 z_index = 0; std::string text2; + u32 style; }; extern const EnumString es_HudElementType[]; diff --git a/src/inventory.cpp b/src/inventory.cpp index fc1aaf371..da6517e62 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -460,7 +460,6 @@ void InventoryList::deSerialize(std::istream &is) std::getline(is, line, '\n'); std::istringstream iss(line); - //iss.imbue(std::locale("C")); std::string name; std::getline(iss, name, ' '); @@ -938,19 +937,16 @@ void Inventory::deSerialize(std::istream &is) InventoryList * Inventory::addList(const std::string &name, u32 size) { setModified(); + + // Remove existing lists s32 i = getListIndex(name); - if(i != -1) - { - if(m_lists[i]->getSize() != size) - { - delete m_lists[i]; - m_lists[i] = new InventoryList(name, size, m_itemdef); - m_lists[i]->setModified(); - } + if (i != -1) { + delete m_lists[i]; + m_lists[i] = new InventoryList(name, size, m_itemdef); + m_lists[i]->setModified(); return m_lists[i]; } - //don't create list with invalid name if (name.find(' ') != std::string::npos) return nullptr; diff --git a/src/inventory.h b/src/inventory.h index f36bc57cf..6c84f5fd1 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -298,6 +298,7 @@ public: void serialize(std::ostream &os, bool incremental = false) const; void deSerialize(std::istream &is); + // Creates a new list if none exists or truncates existing lists InventoryList * addList(const std::string &name, u32 size); InventoryList * getList(const std::string &name); const InventoryList * getList(const std::string &name) const; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 1e81c1dbc..a159bf786 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -273,7 +273,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame } if (!list_to) { infostream << "IMoveAction::apply(): FAIL: destination list not found: " - << "to_inv=\""<<to_inv.dump() << "\"" + << "to_inv=\"" << to_inv.dump() << "\"" << ", to_list=\"" << to_list << "\"" << std::endl; return; } @@ -322,12 +322,20 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame return; } - if ((u16)to_i > list_to->getSize()) { + if (from_i < 0 || list_from->getSize() <= (u32) from_i) { + infostream << "IMoveAction::apply(): FAIL: source index out of bounds: " + << "size of from_list=\"" << list_from->getSize() << "\"" + << ", from_index=\"" << from_i << "\"" << std::endl; + return; + } + + if (to_i < 0 || list_to->getSize() <= (u32) to_i) { infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: " - << "to_i=" << to_i - << ", size=" << list_to->getSize() << std::endl; + << "size of to_list=\"" << list_to->getSize() << "\"" + << ", to_index=\"" << to_i << "\"" << std::endl; return; } + /* Do not handle rollback if both inventories are that of the same player */ diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index 7a26fbb0e..529e0149f 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -60,7 +60,7 @@ bool ItemStackMetadata::setString(const std::string &name, const std::string &va void ItemStackMetadata::serialize(std::ostream &os) const { - std::ostringstream os2; + std::ostringstream os2(std::ios_base::binary); os2 << DESERIALIZE_START; for (const auto &stringvar : m_stringvars) { if (!stringvar.first.empty() || !stringvar.second.empty()) diff --git a/src/main.cpp b/src/main.cpp index 7f96836b5..1044b327a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "porting.h" #include "network/socket.h" +#include "mapblock.h" #if USE_CURSES #include "terminal_chat_console.h" #endif @@ -60,11 +61,8 @@ extern "C" { #endif } -#if !defined(SERVER) && \ - (IRRLICHT_VERSION_MAJOR == 1) && \ - (IRRLICHT_VERSION_MINOR == 8) && \ - (IRRLICHT_VERSION_REVISION == 2) - #error "Irrlicht 1.8.2 is known to be broken - please update Irrlicht to version >= 1.8.3" +#if !defined(__cpp_rtti) || !defined(__cpp_exceptions) +#error Minetest cannot be built without exceptions or RTTI #endif #define DEBUGFILE "debug.txt" @@ -91,6 +89,7 @@ static void list_worlds(bool print_name, bool print_path); static bool setup_log_params(const Settings &cmd_args); static bool create_userdata_path(); static bool init_common(const Settings &cmd_args, int argc, char *argv[]); +static void uninit_common(); static void startup_message(); static bool read_config_file(const Settings &cmd_args); static void init_log_streams(const Settings &cmd_args); @@ -110,6 +109,7 @@ static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr); /**********************************************************************/ @@ -201,6 +201,7 @@ int main(int argc, char *argv[]) errorstream << "Unittest support is not enabled in this binary. " << "If you want to enable it, compile project with BUILD_UNITTESTS=1 flag." << std::endl; + return 1; #endif } #endif @@ -236,9 +237,6 @@ int main(int argc, char *argv[]) print_modified_quicktune_values(); - // Stop httpfetch thread (if started) - httpfetch_cleanup(); - END_DEBUG_EXCEPTION_HANDLER return retval; @@ -303,9 +301,9 @@ static void set_allowed_options(OptionList *allowed_options) _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG, + _("Recompress the blocks of the given map database.")))); #ifndef SERVER - allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, - _("Show available video modes")))); allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, @@ -488,13 +486,14 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) startup_message(); set_default_settings(); - // Initialize sockets sockets_init(); - atexit(sockets_cleanup); // Initialize g_settings Settings::createLayer(SL_GLOBAL); + // Set cleanup callback(s) to run at process exit + atexit(uninit_common); + if (!read_config_file(cmd_args)) return false; @@ -513,6 +512,17 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) return true; } +static void uninit_common() +{ + httpfetch_cleanup(); + + sockets_cleanup(); + + // It'd actually be okay to leak these but we want to please valgrind... + for (int i = 0; i < (int)SL_TOTAL_COUNT; i++) + delete Settings::getLayer((SettingsLayer)i); +} + static void startup_message() { infostream << PROJECT_NAME << " " << _("with") @@ -866,7 +876,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } - // Database migration + // Database migration/compression if (cmd_args.exists("migrate")) return migrate_map_database(game_params, cmd_args); @@ -876,6 +886,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (cmd_args.exists("migrate-auth")) return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); + if (cmd_args.getFlag("recompress")) + return recompress_map_database(game_params, cmd_args, bind_addr); + if (cmd_args.exists("terminal")) { #if USE_CURSES bool name_ok = true; @@ -1025,3 +1038,67 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & return true; } + +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr) +{ + Settings world_mt; + const std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt at " << world_mt_path << std::endl; + return false; + } + const std::string &backend = world_mt.get("backend"); + Server server(game_params.world_path, game_params.game_spec, false, addr, false); + MapDatabase *db = ServerMap::createDatabase(backend, game_params.world_path, world_mt); + + u32 count = 0; + u64 last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); + const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; + + // This is ok because the server doesn't actually run + std::vector<v3s16> blocks; + db->listAllLoadableBlocks(blocks); + db->beginSave(); + std::istringstream iss(std::ios_base::binary); + std::ostringstream oss(std::ios_base::binary); + for (auto it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + std::string data; + db->loadBlock(*it, &data); + if (data.empty()) { + errorstream << "Failed to load block " << PP(*it) << std::endl; + return false; + } + + iss.str(data); + iss.clear(); + + MapBlock mb(nullptr, v3s16(0,0,0), &server); + u8 ver = readU8(iss); + mb.deSerialize(iss, ver, true); + + oss.str(""); + oss.clear(); + writeU8(oss, serialize_as_ver); + mb.serialize(oss, serialize_as_ver, true, -1); + + db->saveBlock(*it, oss.str()); + + count++; + if (count % 0xFF == 0 && porting::getTimeS() - last_update_time >= 1) { + std::cerr << " Recompressed " << count << " blocks, " + << (100.0f * count / blocks.size()) << "% completed.\r"; + db->endSave(); + db->beginSave(); + last_update_time = porting::getTimeS(); + } + } + std::cerr << std::endl; + db->endSave(); + + actionstream << "Done, " << count << " blocks were recompressed." << std::endl; + return true; +} diff --git a/src/map.cpp b/src/map.cpp index eeaf5c140..1648adec3 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -844,7 +844,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, m_transforming_liquid.push_back(iter); voxalgo::update_lighting_nodes(this, changed_nodes, modified_blocks); - + env->getScriptIface()->on_liquid_transformed(changed_nodes); /* ---------------------------------------------------------------------- * Manage the queue so that it does not grow indefinately @@ -1564,6 +1564,11 @@ MapBlock *ServerMap::getBlockOrEmerge(v3s16 p3d) return block; } +bool ServerMap::isBlockInQueue(v3s16 pos) +{ + return m_emerge && m_emerge->isBlockInQueue(pos); +} + // 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) @@ -367,6 +367,8 @@ public: */ MapBlock *getBlockOrEmerge(v3s16 p3d); + bool isBlockInQueue(v3s16 pos); + /* Database functions */ diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index 99e3cb0e6..7e86a9937 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -26,15 +26,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map_settings_manager.h" MapSettingsManager::MapSettingsManager(const std::string &map_meta_path): - m_map_meta_path(map_meta_path) + m_map_meta_path(map_meta_path), + m_hierarchy(g_settings) { - m_map_settings = Settings::createLayer(SL_MAP, "[end_of_params]"); - Mapgen::setDefaultSettings(Settings::getLayer(SL_DEFAULTS)); + /* + * We build our own hierarchy which falls back to the global one. + * It looks as follows: (lowest prio first) + * 0: whatever is picked up from g_settings (incl. engine defaults) + * 1: defaults set by scripts (override_meta = false) + * 2: settings present in map_meta.txt or overriden by scripts + */ + m_defaults = new Settings("", &m_hierarchy, 1); + m_map_settings = new Settings("[end_of_params]", &m_hierarchy, 2); } MapSettingsManager::~MapSettingsManager() { + delete m_defaults; delete m_map_settings; delete mapgen_params; } @@ -43,14 +52,13 @@ MapSettingsManager::~MapSettingsManager() bool MapSettingsManager::getMapSetting( const std::string &name, std::string *value_out) { - // Get from map_meta.txt, then try from all other sources + // Try getting it normally first if (m_map_settings->getNoEx(name, *value_out)) return true; - // Compatibility kludge + // If not we may have to resolve some compatibility kludges if (name == "seed") return Settings::getLayer(SL_GLOBAL)->getNoEx("fixed_map_seed", *value_out); - return false; } @@ -72,7 +80,7 @@ bool MapSettingsManager::setMapSetting( if (override_meta) m_map_settings->set(name, value); else - Settings::getLayer(SL_GLOBAL)->set(name, value); + m_defaults->set(name, value); return true; } @@ -87,7 +95,7 @@ bool MapSettingsManager::setMapSettingNoiseParams( if (override_meta) m_map_settings->setNoiseParams(name, *value); else - Settings::getLayer(SL_GLOBAL)->setNoiseParams(name, *value); + m_defaults->setNoiseParams(name, *value); return true; } @@ -146,15 +154,8 @@ MapgenParams *MapSettingsManager::makeMapgenParams() if (mapgen_params) return mapgen_params; - assert(m_map_settings != NULL); - - // At this point, we have (in order of precedence): - // 1). SL_MAP containing map_meta.txt settings or - // explicit overrides from scripts - // 2). SL_GLOBAL containing all user-specified config file - // settings - // 3). SL_DEFAULTS containing any low-priority settings from - // scripts, e.g. mods using Lua as an enhanced config file) + assert(m_map_settings); + assert(m_defaults); // Now, get the mapgen type so we can create the appropriate MapgenParams std::string mg_name; diff --git a/src/map_settings_manager.h b/src/map_settings_manager.h index 9258d3032..fa271268d 100644 --- a/src/map_settings_manager.h +++ b/src/map_settings_manager.h @@ -20,8 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include <string> +#include "settings.h" -class Settings; struct NoiseParams; struct MapgenParams; @@ -70,6 +70,8 @@ public: private: std::string m_map_meta_path; - // TODO: Rename to "m_settings" + + SettingsHierarchy m_hierarchy; + Settings *m_defaults; Settings *m_map_settings; }; diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 0ca71e643..4958d3a65 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -355,7 +355,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, } } -void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compression_level) +void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -365,6 +365,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error"); + std::ostringstream os_raw(std::ios_base::binary); + std::ostream &os = version >= 29 ? os_raw : os_compressed; + // First byte u8 flags = 0; if(is_underground) @@ -382,37 +385,52 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio Bulk node data */ NameIdMapping nimap; - if(disk) + SharedBuffer<u8> buf; + const u8 content_width = 2; + const u8 params_width = 2; + if(disk) { MapNode *tmp_nodes = new MapNode[nodecount]; - for(u32 i=0; i<nodecount; i++) - tmp_nodes[i] = data[i]; + memcpy(tmp_nodes, data, nodecount * sizeof(MapNode)); getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef()); - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, tmp_nodes, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, tmp_nodes, nodecount, + content_width, params_width); delete[] tmp_nodes; + + // write timestamp and node/id mapping first + if (version >= 29) { + writeU32(os, getTimestamp()); + + nimap.serialize(os); + } } else { - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, data, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, data, nodecount, + content_width, params_width); + } + + writeU8(os, content_width); + writeU8(os, params_width); + if (version >= 29) { + os.write(reinterpret_cast<char*>(*buf), buf.getSize()); + } else { + // prior to 29 node data was compressed individually + compress(buf, os, version, compression_level); } /* Node metadata */ - std::ostringstream oss(std::ios_base::binary); - m_node_metadata.serialize(oss, version, disk); - compressZlib(oss.str(), os, compression_level); + if (version >= 29) { + m_node_metadata.serialize(os, version, disk); + } else { + // use os_raw from above to avoid allocating another stream object + m_node_metadata.serialize(os_raw, version, disk); + // prior to 29 node data was compressed individually + compress(os_raw.str(), os, version, compression_level); + } /* Data that goes to disk, but not the network @@ -427,17 +445,24 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio // Static objects m_static_objects.serialize(os); - // Timestamp - writeU32(os, getTimestamp()); + if(version < 29){ + // Timestamp + writeU32(os, getTimestamp()); - // Write block-specific node definition id mapping - nimap.serialize(os); + // Write block-specific node definition id mapping + nimap.serialize(os); + } if(version >= 25){ // Node timers m_node_timers.serialize(os, version); } } + + if (version >= 29) { + // now compress the whole thing + compress(os_raw.str(), os_compressed, version, compression_level); + } } void MapBlock::serializeNetworkSpecific(std::ostream &os) @@ -449,7 +474,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os) writeU8(os, 2); // version } -void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) +void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -460,10 +485,16 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) if(version <= 21) { - deSerialize_pre22(is, version, disk); + deSerialize_pre22(in_compressed, version, disk); return; } + // Decompress the whole block (version >= 29) + std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + if (version >= 29) + decompress(in_compressed, in_raw, version); + std::istream &is = version >= 29 ? in_raw : in_compressed; + u8 flags = readU8(is); is_underground = (flags & 0x01) != 0; m_day_night_differs = (flags & 0x02) != 0; @@ -473,9 +504,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) m_lighting_complete = readU16(is); m_generated = (flags & 0x08) == 0; - /* - Bulk node data - */ + NameIdMapping nimap; + if (disk && version >= 29) { + // Timestamp + TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) + <<": Timestamp"<<std::endl); + setTimestampNoChangedFlag(readU32(is)); + m_disk_timestamp = m_timestamp; + + // Node/id mapping + TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) + <<": NameIdMapping"<<std::endl); + nimap.deSerialize(is); + } + TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) <<": Bulk node data"<<std::endl); u8 content_width = readU8(is); @@ -484,29 +526,44 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) throw SerializationError("MapBlock::deSerialize(): invalid content_width"); if(params_width != 2) throw SerializationError("MapBlock::deSerialize(): invalid params_width"); - MapNode::deSerializeBulk(is, version, data, nodecount, + + /* + Bulk node data + */ + if (version >= 29) { + MapNode::deSerializeBulk(is, version, data, nodecount, content_width, params_width); + } else { + // use in_raw from above to avoid allocating another stream object + decompress(is, in_raw, version); + MapNode::deSerializeBulk(in_raw, version, data, nodecount, + content_width, params_width); + } /* NodeMetadata */ TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) <<": Node metadata"<<std::endl); - // Ignore errors - try { - std::ostringstream oss(std::ios_base::binary); - decompressZlib(is, oss); - std::istringstream iss(oss.str(), std::ios_base::binary); - if (version >= 23) - m_node_metadata.deSerialize(iss, m_gamedef->idef()); - else - content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); - } catch(SerializationError &e) { - warningstream<<"MapBlock::deSerialize(): Ignoring an error" - <<" while deserializing node metadata at (" - <<PP(getPos())<<": "<<e.what()<<std::endl; + if (version >= 29) { + m_node_metadata.deSerialize(is, m_gamedef->idef()); + } else { + try { + // reuse in_raw + in_raw.str(""); + in_raw.clear(); + decompress(is, in_raw, version); + if (version >= 23) + m_node_metadata.deSerialize(in_raw, m_gamedef->idef()); + else + content_nodemeta_deserialize_legacy(in_raw, + &m_node_metadata, &m_node_timers, + m_gamedef->idef()); + } catch(SerializationError &e) { + warningstream<<"MapBlock::deSerialize(): Ignoring an error" + <<" while deserializing node metadata at (" + <<PP(getPos())<<": "<<e.what()<<std::endl; + } } /* @@ -530,17 +587,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) <<": Static objects"<<std::endl); m_static_objects.deSerialize(is); - // Timestamp - TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) - <<": Timestamp"<<std::endl); - setTimestampNoChangedFlag(readU32(is)); - m_disk_timestamp = m_timestamp; + if(version < 29) { + // Timestamp + TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) + <<": Timestamp"<<std::endl); + setTimestampNoChangedFlag(readU32(is)); + m_disk_timestamp = m_timestamp; + + // Node/id mapping + TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) + <<": NameIdMapping"<<std::endl); + nimap.deSerialize(is); + } // Dynamically re-set ids based on node names - TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) - <<": NameIdMapping"<<std::endl); - NameIdMapping nimap; - nimap.deSerialize(is); correctBlockNodeIds(&nimap, data, m_gamedef); if(version >= 25){ diff --git a/src/mapblock.h b/src/mapblock.h index 7b82301e9..8de631a29 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -140,7 +140,7 @@ public: //// Flags //// - inline bool isDummy() + inline bool isDummy() const { return !data; } @@ -473,7 +473,7 @@ public: // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_VER_LOWEST_WRITE - void serialize(std::ostream &os, u8 version, bool disk, int compression_level); + void serialize(std::ostream &result, u8 version, bool disk, int compression_level); // If disk == true: In addition to doing other things, will add // unknown blocks from id-name mapping to wndef void deSerialize(std::istream &is, u8 version, bool disk); diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 653bad4fe..b9ba70302 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -339,7 +339,9 @@ bool Schematic::deserializeFromMts(std::istream *is) delete []schemdata; schemdata = new MapNode[nodecount]; - MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, + std::stringstream d_ss(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + decompress(ss, d_ss, MTSCHEM_MAPNODE_SER_FMT_VER); + MapNode::deSerializeBulk(d_ss, MTSCHEM_MAPNODE_SER_FMT_VER, schemdata, nodecount, 2, 2); // Fix probability values for nodes that were ignore; removed in v2 @@ -384,8 +386,9 @@ bool Schematic::serializeToMts(std::ostream *os) const } // compressed bulk node data - MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, - schemdata, size.X * size.Y * size.Z, 2, 2, -1); + SharedBuffer<u8> buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER, + schemdata, size.X * size.Y * size.Z, 2, 2); + compress(buf, ss, MTSCHEM_MAPNODE_SER_FMT_VER); return true; } @@ -598,8 +601,9 @@ void Schematic::applyProbabilities(v3s16 p0, } for (size_t i = 0; i != splist->size(); i++) { - s16 y = (*splist)[i].first - p0.Y; - slice_probs[y] = (*splist)[i].second; + s16 slice = (*splist)[i].first; + if (slice < size.Y) + slice_probs[slice] = (*splist)[i].second; } } diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h index 5f64ea280..9189bb3a7 100644 --- a/src/mapgen/mg_schematic.h +++ b/src/mapgen/mg_schematic.h @@ -70,6 +70,7 @@ class Server; #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' #define MTSCHEM_FILE_VER_HIGHEST_READ 4 #define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 +#define MTSCHEM_MAPNODE_SER_FMT_VER 28 // Fixed serialization version for schematics since these still need to use Zlib #define MTSCHEM_PROB_MASK 0x7F diff --git a/src/mapnode.cpp b/src/mapnode.cpp index c885bfe1d..73bd620fb 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -161,7 +161,9 @@ u8 MapNode::getWallMounted(const NodeDefManager *nodemgr) const if (f.param_type_2 == CPT2_WALLMOUNTED || f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { return getParam2() & 0x07; - } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE) { + } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE || + f.drawtype == NDT_PLANTLIKE || + f.drawtype == NDT_PLANTLIKE_ROOTED) { return 1; } return 0; @@ -728,9 +730,10 @@ void MapNode::deSerialize(u8 *source, u8 version) } } } -void MapNode::serializeBulk(std::ostream &os, int version, + +SharedBuffer<u8> MapNode::serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level) + u8 content_width, u8 params_width) { if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); @@ -744,8 +747,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, throw SerializationError("MapNode::serializeBulk: serialization to " "version < 24 not possible"); - size_t databuf_size = nodecount * (content_width + params_width); - u8 *databuf = new u8[databuf_size]; + SharedBuffer<u8> databuf(nodecount * (content_width + params_width)); u32 start1 = content_width * nodecount; u32 start2 = (content_width + 1) * nodecount; @@ -756,14 +758,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, writeU8(&databuf[start1 + i], nodes[i].param1); writeU8(&databuf[start2 + i], nodes[i].param2); } - - /* - Compress data to output stream - */ - - compressZlib(databuf, databuf_size, os, compression_level); - - delete [] databuf; + return databuf; } // Deserialize bulk node data @@ -779,15 +774,10 @@ void MapNode::deSerializeBulk(std::istream &is, int version, || params_width != 2) FATAL_ERROR("Deserialize bulk node data error"); - // Uncompress or read data - u32 len = nodecount * (content_width + params_width); - std::ostringstream os(std::ios_base::binary); - decompressZlib(is, os); - std::string s = os.str(); - if(s.size() != len) - throw SerializationError("deSerializeBulkNodes: " - "decompress resulted in invalid size"); - const u8 *databuf = reinterpret_cast<const u8*>(s.c_str()); + // read data + const u32 len = nodecount * (content_width + params_width); + Buffer<u8> databuf(len); + is.read(reinterpret_cast<char*>(*databuf), len); // Deserialize content if(content_width == 1) diff --git a/src/mapnode.h b/src/mapnode.h index 28ff9e43d..afd3a96be 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "light.h" +#include "util/pointer.h" #include <string> #include <vector> @@ -293,9 +294,9 @@ struct MapNode // content_width = the number of bytes of content per node // params_width = the number of bytes of params per node // compressed = true to zlib-compress output - static void serializeBulk(std::ostream &os, int version, + static SharedBuffer<u8> serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level); + u8 content_width, u8 params_width); static void deSerializeBulk(std::istream &is, int version, MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width); diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 55cfdd4dc..a98a5e7d1 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -204,7 +204,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = null_command_factory, // 0x3e null_command_factory, // 0x3f { "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40 - null_command_factory, // 0x41 + { "TOSERVER_HAVE_MEDIA", 2, true }, // 0x41 null_command_factory, // 0x42 { "TOSERVER_CLIENT_READY", 1, true }, // 0x43 null_command_factory, // 0x44 diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 2fea03fbf..6497bb26a 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -90,7 +90,7 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) // This is only neccessary though when we actually want to add casing support if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { - // we recieved a TOCLIENT_HELLO while auth was already going on + // we received a TOCLIENT_HELLO while auth was already going on errorstream << "Client: TOCLIENT_HELLO while auth was already going on" << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; if (m_chosen_auth_mech == AUTH_MECHANISM_SRP || @@ -158,7 +158,7 @@ void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt) m_password = m_new_password; - verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; + verbosestream << "Client: Received TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; // send packet to actually set the password startAuth(AUTH_MECHANISM_FIRST_SRP); @@ -263,7 +263,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) return; std::istringstream is(pkt->readLongString(), std::ios::binary); - std::stringstream sstr; + std::stringstream sstr(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(is, sstr); NodeMetadataList meta_updates_list(false); @@ -684,21 +684,19 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addFile(name, sha1_raw); } - try { + { std::string str; - *pkt >> str; Strfnd sf(str); - while(!sf.at_end()) { + while (!sf.at_end()) { std::string baseurl = trim(sf.next(",")); - if (!baseurl.empty()) + if (!baseurl.empty()) { + m_remote_media_servers.emplace_back(baseurl); m_media_downloader->addRemoteServer(baseurl); + } } } - catch(SerializationError& e) { - // not supported by server or turned off - } m_media_downloader->step(this); } @@ -730,31 +728,38 @@ void Client::handleCommand_Media(NetworkPacket* pkt) if (num_files == 0) return; - if (!m_media_downloader || !m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "media has not been requested" : - "all media has been received already"; - errorstream << "Client: Received media but " - << problem << "! " - << " bunch " << bunch_i << "/" << num_bunches - << " files=" << num_files - << " size=" << pkt->getSize() << std::endl; - return; - } + bool init_phase = m_media_downloader && m_media_downloader->isStarted(); - // Mesh update thread must be stopped while - // updating content definitions - sanity_check(!m_mesh_update_thread.isRunning()); + if (init_phase) { + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.isRunning()); + } - for (u32 i=0; i < num_files; i++) { - std::string name; + for (u32 i = 0; i < num_files; i++) { + std::string name, data; *pkt >> name; + data = pkt->readLongString(); - std::string data = pkt->readLongString(); - - m_media_downloader->conventionalTransferDone( - name, data, this); + bool ok = false; + if (init_phase) { + ok = m_media_downloader->conventionalTransferDone(name, data, this); + } else { + // Check pending dynamic transfers, one of them must be it + for (const auto &it : m_pending_media_downloads) { + if (it.second->conventionalTransferDone(name, data, this)) { + ok = true; + break; + } + } + } + if (!ok) { + errorstream << "Client: Received media \"" << name + << "\" but no downloads pending. " << num_bunches << " bunches, " + << num_files << " in this one. (init_phase=" << init_phase + << ")" << std::endl; + } } } @@ -769,12 +774,11 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) // Decompress node definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_nodedef->deSerialize(tmp_is2); + m_nodedef->deSerialize(tmp_os); m_nodedef_received = true; } @@ -789,12 +793,11 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) // Decompress item definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); - std::ostringstream tmp_os; + std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); decompressZlib(tmp_is, tmp_os); // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_itemdef->deSerialize(tmp_is2); + m_itemdef->deSerialize(tmp_os); m_itemdef_received = true; } @@ -915,6 +918,11 @@ void Client::handleCommand_Privileges(NetworkPacket* pkt) m_privileges.insert(priv); infostream << priv << " "; } + + // Enable basic_debug on server versions before it was added + if (m_proto_ver < 40) + m_privileges.insert("basic_debug"); + infostream << std::endl; } @@ -1078,6 +1086,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) v2s32 size; s16 z_index = 0; std::string text2; + u32 style = 0; *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item >> dir >> align >> offset; @@ -1086,6 +1095,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) *pkt >> size; *pkt >> z_index; *pkt >> text2; + *pkt >> style; } catch(PacketError &e) {}; ClientEvent *event = new ClientEvent(); @@ -1106,6 +1116,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) event->hudadd->size = size; event->hudadd->z_index = z_index; event->hudadd->text2 = text2; + event->hudadd->style = style; m_client_event_queue.push(event); } @@ -1133,17 +1144,29 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt) *pkt >> server_id >> stat; - if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || - stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) - *pkt >> v2fdata; - else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2) - *pkt >> sdata; - else if (stat == HUD_STAT_WORLD_POS) - *pkt >> v3fdata; - else if (stat == HUD_STAT_SIZE ) - *pkt >> v2s32data; - else - *pkt >> intdata; + // Keep in sync with:server.cpp -> SendHUDChange + switch ((HudElementStat)stat) { + case HUD_STAT_POS: + case HUD_STAT_SCALE: + case HUD_STAT_ALIGN: + case HUD_STAT_OFFSET: + *pkt >> v2fdata; + break; + case HUD_STAT_NAME: + case HUD_STAT_TEXT: + case HUD_STAT_TEXT2: + *pkt >> sdata; + break; + case HUD_STAT_WORLD_POS: + *pkt >> v3fdata; + break; + case HUD_STAT_SIZE: + *pkt >> v2s32data; + break; + default: + *pkt >> intdata; + break; + } ClientEvent *event = new ClientEvent(); event->type = CE_HUDCHANGE; @@ -1507,46 +1530,72 @@ void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt) void Client::handleCommand_MediaPush(NetworkPacket *pkt) { std::string raw_hash, filename, filedata; + u32 token; bool cached; *pkt >> raw_hash >> filename >> cached; - filedata = pkt->readLongString(); + if (m_proto_ver >= 40) + *pkt >> token; + else + filedata = pkt->readLongString(); - if (raw_hash.size() != 20 || filedata.empty() || filename.empty() || + if (raw_hash.size() != 20 || filename.empty() || + (m_proto_ver < 40 && filedata.empty()) || !string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) { throw PacketError("Illegal filename, data or hash"); } - verbosestream << "Server pushes media file \"" << filename << "\" with " - << filedata.size() << " bytes of data (cached=" << cached - << ")" << std::endl; + verbosestream << "Server pushes media file \"" << filename << "\" "; + if (filedata.empty()) + verbosestream << "to be fetched "; + else + verbosestream << "with " << filedata.size() << " bytes "; + verbosestream << "(cached=" << cached << ")" << std::endl; if (m_media_pushed_files.count(filename) != 0) { - // Silently ignore for synchronization purposes + // Ignore (but acknowledge). Previously this was for sync purposes, + // but even in new versions media cannot be replaced at runtime. + if (m_proto_ver >= 40) + sendHaveMedia({ token }); return; } - // Compute and check checksum of data - std::string computed_hash; - { - SHA1 ctx; - ctx.addBytes(filedata.c_str(), filedata.size()); - unsigned char *buf = ctx.getDigest(); - computed_hash.assign((char*) buf, 20); - free(buf); - } - if (raw_hash != computed_hash) { - verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + if (!filedata.empty()) { + // LEGACY CODEPATH + // Compute and check checksum of data + std::string computed_hash; + { + SHA1 ctx; + ctx.addBytes(filedata.c_str(), filedata.size()); + unsigned char *buf = ctx.getDigest(); + computed_hash.assign((char*) buf, 20); + free(buf); + } + if (raw_hash != computed_hash) { + verbosestream << "Hash of file data mismatches, ignoring." << std::endl; + return; + } + + // Actually load media + loadMedia(filedata, filename, true); + m_media_pushed_files.insert(filename); + + // Cache file for the next time when this client joins the same server + if (cached) + clientMediaUpdateCache(raw_hash, filedata); return; } - // Actually load media - loadMedia(filedata, filename, true); m_media_pushed_files.insert(filename); - // Cache file for the next time when this client joins the same server - if (cached) - clientMediaUpdateCache(raw_hash, filedata); + // create a downloader for this file + auto downloader = new SingleMediaDownloader(cached); + m_pending_media_downloads.emplace_back(token, downloader); + downloader->addFile(filename, raw_hash); + for (const auto &baseurl : m_remote_media_servers) + downloader->addRemoteServer(baseurl); + + downloader->step(this); } /* diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 0ba8c36b2..a4970954f 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -200,17 +200,12 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum) return i; } -RPBSearchResult ReliablePacketBuffer::notFound() -{ - return m_list.end(); -} - bool ReliablePacketBuffer::getFirstSeqnum(u16& result) { MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) return false; - const BufferedPacket &p = *m_list.begin(); + const BufferedPacket &p = m_list.front(); result = readU16(&p.data[BASE_HEADER_SIZE + 1]); return true; } @@ -220,14 +215,14 @@ BufferedPacket ReliablePacketBuffer::popFirst() MutexAutoLock listlock(m_list_mutex); if (m_list.empty()) throw NotFoundException("Buffer is empty"); - BufferedPacket p = *m_list.begin(); - m_list.erase(m_list.begin()); + BufferedPacket p = std::move(m_list.front()); + m_list.pop_front(); if (m_list.empty()) { m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } @@ -241,15 +236,7 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) << " not found in reliable buffer"<<std::endl); throw NotFoundException("seqnum not found in buffer"); } - BufferedPacket p = *r; - - - RPBSearchResult next = r; - ++next; - if (next != notFound()) { - u16 s = readU16(&(next->data[BASE_HEADER_SIZE+1])); - m_oldest_non_answered_ack = s; - } + BufferedPacket p = std::move(*r); m_list.erase(r); @@ -257,12 +244,12 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) m_oldest_non_answered_ack = 0; } else { m_oldest_non_answered_ack = - readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]); + readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]); } return p; } -void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) +void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected) { MutexAutoLock listlock(m_list_mutex); if (p.data.getSize() < BASE_HEADER_SIZE + 3) { @@ -355,7 +342,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected) } /* update last packet number */ - m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]); + m_oldest_non_answered_ack = readU16(&m_list.front().data[BASE_HEADER_SIZE+1]); } void ReliablePacketBuffer::incrementTimeouts(float dtime) @@ -367,17 +354,19 @@ void ReliablePacketBuffer::incrementTimeouts(float dtime) } } -std::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout, - unsigned int max_packets) +std::list<BufferedPacket> + ReliablePacketBuffer::getTimedOuts(float timeout, u32 max_packets) { MutexAutoLock listlock(m_list_mutex); std::list<BufferedPacket> timed_outs; for (BufferedPacket &bufferedPacket : m_list) { if (bufferedPacket.time >= timeout) { + // caller will resend packet so reset time and increase counter + bufferedPacket.time = 0.0f; + bufferedPacket.resend_count++; + timed_outs.push_back(bufferedPacket); - //this packet will be sent right afterwards reset timeout here - bufferedPacket.time = 0.0f; if (timed_outs.size() >= max_packets) break; } @@ -1051,20 +1040,20 @@ bool UDPPeer::processReliableSendCommand( m_connection->GetProtocolID(), m_connection->GetPeerID(), c.channelnum); - toadd.push(p); + toadd.push(std::move(p)); } if (have_sequence_number) { volatile u16 pcount = 0; while (!toadd.empty()) { - BufferedPacket p = toadd.front(); + BufferedPacket p = std::move(toadd.front()); toadd.pop(); // LOG(dout_con<<connection->getDesc() // << " queuing reliable packet for peer_id: " << c.peer_id // << " channel: " << (c.channelnum&0xFF) // << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) // << std::endl) - chan.queued_reliables.push(p); + chan.queued_reliables.push(std::move(p)); pcount++; } sanity_check(chan.queued_reliables.size() < 0xFFFF); @@ -1208,12 +1197,19 @@ Connection::~Connection() } /* Internal stuff */ -void Connection::putEvent(ConnectionEvent &e) + +void Connection::putEvent(const ConnectionEvent &e) { assert(e.type != CONNEVENT_NONE); // Pre-condition m_event_queue.push_back(e); } +void Connection::putEvent(ConnectionEvent &&e) +{ + assert(e.type != CONNEVENT_NONE); // Pre-condition + m_event_queue.push_back(std::move(e)); +} + void Connection::TriggerSend() { m_sendThread->Trigger(); @@ -1299,7 +1295,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms) } } -void Connection::putCommand(ConnectionCommand &c) +void Connection::putCommand(const ConnectionCommand &c) { if (!m_shutting_down) { m_command_queue.push_back(c); @@ -1307,6 +1303,14 @@ void Connection::putCommand(ConnectionCommand &c) } } +void Connection::putCommand(ConnectionCommand &&c) +{ + if (!m_shutting_down) { + m_command_queue.push_back(std::move(c)); + m_sendThread->Trigger(); + } +} + void Connection::Serve(Address bind_addr) { ConnectionCommand c; @@ -1408,7 +1412,7 @@ void Connection::Send(session_t peer_id, u8 channelnum, ConnectionCommand c; c.send(peer_id, channelnum, pkt, reliable); - putCommand(c); + putCommand(std::move(c)); } Address Connection::GetPeerAddress(session_t peer_id) @@ -1508,12 +1512,12 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) << "createPeer(): giving peer_id=" << peer_id_new << std::endl); ConnectionCommand cmd; - SharedBuffer<u8> reply(4); + Buffer<u8> reply(4); writeU8(&reply[0], PACKET_TYPE_CONTROL); writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); writeU16(&reply[2], peer_id_new); cmd.createPeer(peer_id_new,reply); - putCommand(cmd); + putCommand(std::move(cmd)); // Create peer addition event ConnectionEvent e; @@ -1560,7 +1564,7 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) writeU16(&ack[2], seqnum); c.ack(peer_id, channelnum, ack); - putCommand(c); + putCommand(std::move(c)); m_sendThread->Trigger(); } diff --git a/src/network/connection.h b/src/network/connection.h index 24cd4fe4a..49bb65c3e 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_bloated.h" +#include "irrlichttypes.h" #include "peerhandler.h" #include "socket.h" #include "constants.h" @@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "networkprotocol.h" #include <iostream> -#include <fstream> #include <vector> #include <map> @@ -242,20 +241,19 @@ public: BufferedPacket popFirst(); BufferedPacket popSeqnum(u16 seqnum); - void insert(BufferedPacket &p, u16 next_expected); + void insert(const BufferedPacket &p, u16 next_expected); void incrementTimeouts(float dtime); - std::list<BufferedPacket> getTimedOuts(float timeout, - unsigned int max_packets); + std::list<BufferedPacket> getTimedOuts(float timeout, u32 max_packets); void print(); bool empty(); - RPBSearchResult notFound(); u32 size(); private: RPBSearchResult findPacket(u16 seqnum); // does not perform locking + inline RPBSearchResult notFound() { return m_list.end(); } std::list<BufferedPacket> m_list; @@ -329,18 +327,6 @@ struct ConnectionCommand bool raw = false; ConnectionCommand() = default; - ConnectionCommand &operator=(const ConnectionCommand &other) - { - type = other.type; - address = other.address; - peer_id = other.peer_id; - channelnum = other.channelnum; - // We must copy the buffer here to prevent race condition - data = SharedBuffer<u8>(*other.data, other.data.getSize()); - reliable = other.reliable; - raw = other.raw; - return *this; - } void serve(Address address_) { @@ -364,7 +350,7 @@ struct ConnectionCommand void send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, bool reliable_); - void ack(session_t peer_id_, u8 channelnum_, const SharedBuffer<u8> &data_) + void ack(session_t peer_id_, u8 channelnum_, const Buffer<u8> &data_) { type = CONCMD_ACK; peer_id = peer_id_; @@ -373,7 +359,7 @@ struct ConnectionCommand reliable = false; } - void createPeer(session_t peer_id_, const SharedBuffer<u8> &data_) + void createPeer(session_t peer_id_, const Buffer<u8> &data_) { type = CONCMD_CREATE_PEER; peer_id = peer_id_; @@ -707,7 +693,7 @@ struct ConnectionEvent ConnectionEvent() = default; - std::string describe() + const char *describe() const { switch(type) { case CONNEVENT_NONE: @@ -724,7 +710,7 @@ struct ConnectionEvent return "Invalid ConnectionEvent"; } - void dataReceived(session_t peer_id_, const SharedBuffer<u8> &data_) + void dataReceived(session_t peer_id_, const Buffer<u8> &data_) { type = CONNEVENT_DATA_RECEIVED; peer_id = peer_id_; @@ -763,7 +749,9 @@ public: /* Interface */ ConnectionEvent waitEvent(u32 timeout_ms); - void putCommand(ConnectionCommand &c); + // Warning: creates an unnecessary copy, prefer putCommand(T&&) if possible + void putCommand(const ConnectionCommand &c); + void putCommand(ConnectionCommand &&c); void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); @@ -802,11 +790,14 @@ protected: } UDPSocket m_udpSocket; + // Command queue: user -> SendThread MutexedQueue<ConnectionCommand> m_command_queue; bool Receive(NetworkPacket *pkt, u32 timeout); - void putEvent(ConnectionEvent &e); + // Warning: creates an unnecessary copy, prefer putEvent(T&&) if possible + void putEvent(const ConnectionEvent &e); + void putEvent(ConnectionEvent &&e); void TriggerSend(); @@ -815,6 +806,7 @@ protected: return getPeerNoEx(PEER_ID_SERVER) != nullptr; } private: + // Event queue: ReceiveThread -> user MutexedQueue<ConnectionEvent> m_event_queue; session_t m_peer_id = 0; diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index 7b62bc792..47678dac5 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -174,6 +174,11 @@ void ConnectionSendThread::runTimeouts(float dtime) std::vector<session_t> timeouted_peers; std::vector<session_t> peerIds = m_connection->getPeerIDs(); + const u32 numpeers = m_connection->m_peers.size(); + + if (numpeers == 0) + return; + for (session_t &peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); @@ -209,7 +214,6 @@ void ConnectionSendThread::runTimeouts(float dtime) float resend_timeout = udpPeer->getResendTimeout(); bool retry_count_exceeded = false; for (Channel &channel : udpPeer->channels) { - std::list<BufferedPacket> timed_outs; // Remove timed out incomplete unreliable split packets channel.incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout); @@ -217,13 +221,8 @@ void ConnectionSendThread::runTimeouts(float dtime) // Increment reliable packet times channel.outgoing_reliables_sent.incrementTimeouts(dtime); - unsigned int numpeers = m_connection->m_peers.size(); - - if (numpeers == 0) - return; - // Re-send timed out outgoing reliables - timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, + auto timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout, (m_max_data_packets_per_iteration / numpeers)); channel.UpdatePacketLossCounter(timed_outs.size()); @@ -231,16 +230,14 @@ void ConnectionSendThread::runTimeouts(float dtime) m_iteration_packets_avaialble -= timed_outs.size(); - for (std::list<BufferedPacket>::iterator k = timed_outs.begin(); - k != timed_outs.end(); ++k) { - session_t peer_id = readPeerId(*(k->data)); - u8 channelnum = readChannel(*(k->data)); - u16 seqnum = readU16(&(k->data[BASE_HEADER_SIZE + 1])); + for (const auto &k : timed_outs) { + session_t peer_id = readPeerId(*k.data); + u8 channelnum = readChannel(*k.data); + u16 seqnum = readU16(&(k.data[BASE_HEADER_SIZE + 1])); - channel.UpdateBytesLost(k->data.getSize()); - k->resend_count++; + channel.UpdateBytesLost(k.data.getSize()); - if (k->resend_count > MAX_RELIABLE_RETRY) { + if (k.resend_count > MAX_RELIABLE_RETRY) { retry_count_exceeded = true; timeouted_peers.push_back(peer->id); /* no need to check additional packets if a single one did timeout*/ @@ -249,14 +246,14 @@ void ConnectionSendThread::runTimeouts(float dtime) LOG(derr_con << m_connection->getDesc() << "RE-SENDING timed-out RELIABLE to " - << k->address.serializeString() + << k.address.serializeString() << "(t/o=" << resend_timeout << "): " << "from_peer_id=" << peer_id << ", channel=" << ((int) channelnum & 0xff) << ", seqnum=" << seqnum << std::endl); - rawSend(*k); + rawSend(k); // do not handle rtt here as we can't decide if this packet was // lost or really takes more time to transmit @@ -375,7 +372,7 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum, << " INFO: queueing reliable packet for peer_id: " << peer_id << " channel: " << (u32)channelnum << " seqnum: " << seqnum << std::endl); - channel->queued_reliables.push(p); + channel->queued_reliables.push(std::move(p)); return false; } @@ -717,13 +714,15 @@ void ConnectionSendThread::sendPackets(float dtime) channel.outgoing_reliables_sent.size() < channel.getWindowSize() && peer->m_increment_packets_remaining > 0) { - BufferedPacket p = channel.queued_reliables.front(); + BufferedPacket p = std::move(channel.queued_reliables.front()); channel.queued_reliables.pop(); + LOG(dout_con << m_connection->getDesc() << " INFO: sending a queued reliable packet " << " channel: " << i << ", seqnum: " << readU16(&p.data[BASE_HEADER_SIZE + 1]) << std::endl); + sendAsPacketReliable(p, &channel); peer->m_increment_packets_remaining--; } @@ -911,7 +910,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata, if (data_left) { ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } } catch (ProcessedSilentlyException &e) { @@ -1022,7 +1021,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata, ConnectionEvent e; e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + m_connection->putEvent(std::move(e)); } catch (ProcessedSilentlyException &e) { } diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index a71e26572..6b8b0f703 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -549,14 +549,11 @@ NetworkPacket& NetworkPacket::operator<<(video::SColor src) return *this; } -SharedBuffer<u8> NetworkPacket::oldForgePacket() +Buffer<u8> NetworkPacket::oldForgePacket() { - SharedBuffer<u8> sb(m_datasize + 2); + Buffer<u8> sb(m_datasize + 2); writeU16(&sb[0], m_command); + memcpy(&sb[2], m_data.data(), m_datasize); - u8* datas = getU8Ptr(0); - - if (datas != NULL) - memcpy(&sb[2], datas, m_datasize); return sb; } diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index c7ff03b8e..b1c44f055 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -115,7 +115,8 @@ public: NetworkPacket &operator<<(video::SColor src); // Temp, we remove SharedBuffer when migration finished - SharedBuffer<u8> oldForgePacket(); + // ^ this comment has been here for 4 years + Buffer<u8> oldForgePacket(); private: void checkReadOffset(u32 from_offset, u32 field_size); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 88a5ac177..8214cc5b1 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -205,6 +205,9 @@ with this program; if not, write to the Free Software Foundation, Inc., Updated set_sky packet Adds new sun, moon and stars packets Minimap modes + PROTOCOL VERSION 40: + Added 'basic_debug' privilege + TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added */ #define LATEST_PROTOCOL_VERSION 40 @@ -315,9 +318,8 @@ enum ToClientCommand /* std::string raw_hash std::string filename + u32 callback_token bool should_be_cached - u32 len - char filedata[len] */ // (oops, there is some gap here) @@ -936,7 +938,13 @@ enum ToServerCommand } */ - TOSERVER_RECEIVED_MEDIA = 0x41, // Obsolete + TOSERVER_HAVE_MEDIA = 0x41, + /* + u8 number of callback tokens + for each: + u32 token + */ + TOSERVER_BREATH = 0x42, // Obsolete TOSERVER_CLIENT_READY = 0x43, diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index aea5d7174..44b65e8da 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -89,7 +89,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = null_command_handler, // 0x3e null_command_handler, // 0x3f { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 - null_command_handler, // 0x41 + { "TOSERVER_HAVE_MEDIA", TOSERVER_STATE_INGAME, &Server::handleCommand_HaveMedia }, // 0x41 null_command_handler, // 0x42 { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 null_command_handler, // 0x44 @@ -167,7 +167,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29 { "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A { "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B - { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too) + { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too if legacy) null_command_factory, // 0x2D null_command_factory, // 0x2E { "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 708ddbf20..4c609644f 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -362,16 +362,15 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); infostream << "Sending " << numfiles << " files to " << getPlayerName(peer_id) << std::endl; - verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; + verbosestream << "TOSERVER_REQUEST_MEDIA: requested file(s)" << std::endl; for (u16 i = 0; i < numfiles; i++) { std::string name; *pkt >> name; - tosend.push_back(name); - verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " - << name << std::endl; + tosend.emplace_back(name); + verbosestream << " " << name << std::endl; } sendRequestedMedia(peer_id, tosend); @@ -510,10 +509,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setWantedRange(wanted_range); player->keyPressed = keyPressed; - player->control.up = (keyPressed & (0x1 << 0)); - player->control.down = (keyPressed & (0x1 << 1)); - player->control.left = (keyPressed & (0x1 << 2)); - player->control.right = (keyPressed & (0x1 << 3)); player->control.jump = (keyPressed & (0x1 << 4)); player->control.aux1 = (keyPressed & (0x1 << 5)); player->control.sneak = (keyPressed & (0x1 << 6)); @@ -832,7 +827,6 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); - SendPlayerHPOrDie(playersao, reason); } } @@ -1117,9 +1111,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) float time_from_last_punch = playersao->resetTimeFromLastPunch(); - u16 src_original_hp = pointed_object->getHP(); - u16 dst_origin_hp = playersao->getHP(); - u16 wear = pointed_object->punch(dir, &toolcap, playersao, time_from_last_punch); @@ -1129,18 +1120,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (changed) playersao->setWieldedItem(selected_item); - // If the object is a player and its HP changed - if (src_original_hp != pointed_object->getHP() && - pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - SendPlayerHPOrDie((PlayerSAO *)pointed_object, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); - } - - // If the puncher is a player and its HP changed - if (dst_origin_hp != playersao->getHP()) - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); - return; } // action == INTERACT_START_DIGGING @@ -1821,3 +1800,30 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt) broadcastModChannelMessage(channel_name, channel_msg, peer_id); } + +void Server::handleCommand_HaveMedia(NetworkPacket *pkt) +{ + std::vector<u32> tokens; + u8 numtokens; + + *pkt >> numtokens; + for (u16 i = 0; i < numtokens; i++) { + u32 n; + *pkt >> n; + tokens.emplace_back(n); + } + + const session_t peer_id = pkt->getPeerId(); + auto player = m_env->getPlayer(peer_id); + + for (const u32 token : tokens) { + auto it = m_pending_dyn_media.find(token); + if (it == m_pending_dyn_media.end()) + continue; + if (it->second.waiting_players.count(peer_id)) { + it->second.waiting_players.erase(peer_id); + if (player) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + } +} diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index f98732385..b5052c3b8 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -113,13 +113,13 @@ int NodeMetadata::countNonPrivate() const */ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, - bool absolute_pos) const + bool absolute_pos, bool include_empty) const { /* Version 0 is a placeholder for "nothing to see here; go away." */ - u16 count = countNonEmpty(); + u16 count = include_empty ? m_data.size() : countNonEmpty(); if (count == 0) { writeU8(os, 0); // version return; @@ -134,7 +134,7 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, i != m_data.end(); ++i) { v3s16 p = i->first; NodeMetadata *data = i->second; - if (data->empty()) + if (!include_empty && data->empty()) continue; if (absolute_pos) { diff --git a/src/nodemetadata.h b/src/nodemetadata.h index c028caf88..4b5b4d887 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -82,7 +82,7 @@ public: ~NodeMetadataList(); void serialize(std::ostream &os, u8 blockver, bool disk = true, - bool absolute_pos = false) const; + bool absolute_pos = false, bool include_empty = false) const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr, bool absolute_pos = false); diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 2eebc27d6..db06f8930 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -83,6 +83,39 @@ std::string ObjectProperties::dump() return os.str(); } +bool ObjectProperties::validate() +{ + const char *func = "ObjectProperties::validate(): "; + bool ret = true; + + // cf. where serializeString16 is used below + for (u32 i = 0; i < textures.size(); i++) { + if (textures[i].size() > U16_MAX) { + warningstream << func << "texture " << (i+1) << " has excessive length, " + "clearing it." << std::endl; + textures[i].clear(); + ret = false; + } + } + if (nametag.length() > U16_MAX) { + warningstream << func << "nametag has excessive length, clearing it." << std::endl; + nametag.clear(); + ret = false; + } + if (infotext.length() > U16_MAX) { + warningstream << func << "infotext has excessive length, clearing it." << std::endl; + infotext.clear(); + ret = false; + } + if (wield_item.length() > U16_MAX) { + warningstream << func << "wield_item has excessive length, clearing it." << std::endl; + wield_item.clear(); + ret = false; + } + + return ret; +} + void ObjectProperties::serialize(std::ostream &os) const { writeU8(os, 4); // PROTOCOL_VERSION >= 37 @@ -105,7 +138,6 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, is_visible); writeU8(os, makes_footstep_sound); writeF32(os, automatic_rotate); - // Added in protocol version 14 os << serializeString16(mesh); writeU16(os, colors.size()); for (video::SColor color : colors) { diff --git a/src/object_properties.h b/src/object_properties.h index db28eebfd..79866a22c 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -68,6 +68,8 @@ struct ObjectProperties ObjectProperties(); std::string dump(); + // check limits of some important properties (strings) that'd cause exceptions later on + bool validate(); void serialize(std::ostream &os) const; void deSerialize(std::istream &is); }; diff --git a/src/player.h b/src/player.h index 9a4a41fa6..8f7b0a3fe 100644 --- a/src/player.h +++ b/src/player.h @@ -49,10 +49,6 @@ struct PlayerControl PlayerControl() = default; PlayerControl( - bool a_up, - bool a_down, - bool a_left, - bool a_right, bool a_jump, bool a_aux1, bool a_sneak, @@ -61,14 +57,10 @@ struct PlayerControl bool a_place, float a_pitch, float a_yaw, - float a_sidew_move_joystick_axis, - float a_forw_move_joystick_axis + float a_movement_speed, + float a_movement_direction ) { - up = a_up; - down = a_down; - left = a_left; - right = a_right; jump = a_jump; aux1 = a_aux1; sneak = a_sneak; @@ -77,13 +69,9 @@ struct PlayerControl place = a_place; pitch = a_pitch; yaw = a_yaw; - sidew_move_joystick_axis = a_sidew_move_joystick_axis; - forw_move_joystick_axis = a_forw_move_joystick_axis; + movement_speed = a_movement_speed; + movement_direction = a_movement_direction; } - bool up = false; - bool down = false; - bool left = false; - bool right = false; bool jump = false; bool aux1 = false; bool sneak = false; @@ -92,8 +80,9 @@ struct PlayerControl bool place = false; float pitch = 0.0f; float yaw = 0.0f; - float sidew_move_joystick_axis = 0.0f; - float forw_move_joystick_axis = 0.0f; + // Note: These two are NOT available on the server + float movement_speed = 0.0f; + float movement_direction = 0.0f; }; struct PlayerSettings diff --git a/src/porting_android.cpp b/src/porting_android.cpp index f5870c174..29e95b8ca 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -190,6 +190,7 @@ void initializePathsAndroid() path_user = path_storage + DIR_DELIM + PROJECT_NAME_C; path_share = path_storage + DIR_DELIM + PROJECT_NAME_C; + path_locale = path_share + DIR_DELIM + "locale"; path_cache = getAndroidPath(nativeActivity, app_global->activity->clazz, mt_getAbsPath, "getCacheDir"); migrateCachePath(); diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 897ca2862..8ca3a722f 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -203,8 +203,6 @@ void read_object_properties(lua_State *L, int index, if (sao && prop->hp_max < sao->getHP()) { PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); sao->setHP(prop->hp_max, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - sao->getEnv()->getGameDef()->SendPlayerHPOrDie((PlayerSAO *)sao, reason); } } @@ -1407,26 +1405,28 @@ void read_inventory_list(lua_State *L, int tableindex, { if(tableindex < 0) tableindex = lua_gettop(L) + 1 + tableindex; + // If nil, delete list if(lua_isnil(L, tableindex)){ inv->deleteList(name); return; } - // Otherwise set list + + // Get Lua-specified items to insert into the list std::vector<ItemStack> items = read_items(L, tableindex,srv); - int listsize = (forcesize != -1) ? forcesize : items.size(); + size_t listsize = (forcesize >= 0) ? forcesize : items.size(); + + // Create or resize/clear list InventoryList *invlist = inv->addList(name, listsize); - int index = 0; - for(std::vector<ItemStack>::const_iterator - i = items.begin(); i != items.end(); ++i){ - if(forcesize != -1 && index == forcesize) - break; - invlist->changeItem(index, *i); - index++; + if (!invlist) { + luaL_error(L, "inventory list: cannot create list named '%s'", name); + return; } - while(forcesize != -1 && index < forcesize){ - invlist->deleteItem(index); - index++; + + for (size_t i = 0; i < items.size(); ++i) { + if (i == listsize) + break; // Truncate provided list of items + invlist->changeItem(i, items[i]); } } @@ -2013,6 +2013,8 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->world_pos = lua_istable(L, -1) ? read_v3f(L, -1) : v3f(); lua_pop(L, 1); + elem->style = getintfield_default(L, 2, "style", 0); + /* check for known deprecated element usage */ if ((elem->type == HUD_ELEM_STATBAR) && (elem->size == v2s32())) log_deprecated(L,"Deprecated usage of statbar without size!"); @@ -2067,17 +2069,22 @@ void push_hud_element(lua_State *L, HudElement *elem) lua_pushstring(L, elem->text2.c_str()); lua_setfield(L, -2, "text2"); + + lua_pushinteger(L, elem->style); + lua_setfield(L, -2, "style"); } -HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) +bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value) { - HudElementStat stat = HUD_STAT_NUMBER; - std::string statstr; - if (lua_isstring(L, 3)) { + std::string statstr = lua_tostring(L, 3); + { int statint; - statstr = lua_tostring(L, 3); - stat = string_to_enum(es_HudElementStat, statint, statstr) ? - (HudElementStat)statint : stat; + if (!string_to_enum(es_HudElementStat, statint, statstr)) { + script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); + return false; + } + + stat = (HudElementStat)statint; } switch (stat) { @@ -2135,8 +2142,13 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) elem->text2 = luaL_checkstring(L, 4); *value = &elem->text2; break; + case HUD_STAT_STYLE: + elem->style = luaL_checknumber(L, 4); + *value = &elem->style; + break; } - return stat; + + return true; } /******************************************************************************/ diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index dc8f19ef3..1aed7901e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -122,7 +122,7 @@ void push_object_properties (lua_State *L, void push_inventory (lua_State *L, Inventory *inventory); - + void push_inventory_list (lua_State *L, Inventory *inv, const char *name); @@ -197,14 +197,14 @@ void read_json_value (lua_State *L, Json::Value &root, void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm = false, bool hitpoint = false); -void push_objectRef (lua_State *L, const u16 id); +void push_objectRef (lua_State *L, const u16 id); -void read_hud_element (lua_State *L, HudElement *elem); +void read_hud_element (lua_State *L, HudElement *elem); -void push_hud_element (lua_State *L, HudElement *elem); +void push_hud_element (lua_State *L, HudElement *elem); -HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value); +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_collision_move_result(lua_State *L, const collisionMoveResult &res); -void push_physics_override (lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move); +void push_physics_override (lua_State *L, float speed, float jump, float gravity, bool sneak, bool sneak_glitch, bool new_move); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index c00401b58..19734b913 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -51,12 +51,32 @@ if (value < F1000_MIN || value > F1000_MAX) { \ #define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) +/** + * A helper which sets (if available) the vector metatable from builtin as 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); +} + + void push_float_string(lua_State *L, float value) { - std::stringstream ss; - std::string str; - ss << value; - str = ss.str(); + auto str = ftos(value); lua_pushstring(L, str.c_str()); } @@ -69,6 +89,7 @@ void push_v3f(lua_State *L, v3f p) lua_setfield(L, -2, "y"); lua_pushnumber(L, p.Z); lua_setfield(L, -2, "z"); + set_vector_metatable(L); } void push_v2f(lua_State *L, v2f p) @@ -281,6 +302,7 @@ void push_v3s16(lua_State *L, v3s16 p) lua_setfield(L, -2, "y"); lua_pushinteger(L, p.Z); lua_setfield(L, -2, "z"); + set_vector_metatable(L); } v3s16 read_v3s16(lua_State *L, int index) diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index ad5f836c5..df82dba14 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -18,10 +18,12 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "common/c_internal.h" +#include "util/numeric.h" #include "debug.h" #include "log.h" #include "porting.h" #include "settings.h" +#include <algorithm> // std::find std::string script_get_backtrace(lua_State *L) { @@ -99,60 +101,35 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f throw LuaError(err_msg); } -// Push the list of callbacks (a lua table). -// Then push nargs arguments. -// Then call this function, which -// - runs the callbacks -// - replaces the table and arguments with the return value, -// computed depending on mode -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn) -{ - FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments"); - - // Insert error handler - PUSH_ERROR_HANDLER(L); - int error_handler = lua_gettop(L) - nargs - 1; - lua_insert(L, error_handler); - - // Insert run_callbacks between error handler and table - lua_getglobal(L, "core"); - lua_getfield(L, -1, "run_callbacks"); - lua_remove(L, -2); - lua_insert(L, error_handler + 1); - - // Insert mode after table - lua_pushnumber(L, (int) mode); - lua_insert(L, error_handler + 3); - - // Stack now looks like this: - // ... <error handler> <run_callbacks> <table> <mode> <arg#1> <arg#2> ... <arg#n> - - int result = lua_pcall(L, nargs + 2, 1, error_handler); - if (result != 0) - script_error(L, result, NULL, fxn); - - lua_remove(L, error_handler); -} - -static void script_log(lua_State *L, const std::string &message, - std::ostream &log_to, bool do_error, int stack_depth) +static void script_log_add_source(lua_State *L, std::string &message, int stack_depth) { lua_Debug ar; - log_to << message << " "; if (lua_getstack(L, stack_depth, &ar)) { FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - log_to << "(at " << ar.short_src << ":" << ar.currentline << ")"; + message.append(" (at " + std::string(ar.short_src) + ":" + + std::to_string(ar.currentline) + ")"); } else { - log_to << "(at ?:?)"; + message.append(" (at ?:?)"); } - log_to << std::endl; +} - if (do_error) - script_error(L, LUA_ERRRUN, NULL, NULL); - else - infostream << script_get_backtrace(L) << std::endl; +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth) +{ + thread_local std::vector<u64> logged_messages; + + script_log_add_source(L, message, stack_depth); + u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE); + + if (std::find(logged_messages.begin(), logged_messages.end(), hash) + == logged_messages.end()) { + + logged_messages.emplace_back(hash); + log_to << message << std::endl; + return true; + } + return false; } DeprecatedHandlingMode get_deprecated_handling_mode() @@ -174,9 +151,18 @@ DeprecatedHandlingMode get_deprecated_handling_mode() return ret; } -void log_deprecated(lua_State *L, const std::string &message, int stack_depth) +void log_deprecated(lua_State *L, std::string message, int stack_depth) { DeprecatedHandlingMode mode = get_deprecated_handling_mode(); - if (mode != DeprecatedHandlingMode::Ignore) - script_log(L, message, warningstream, mode == DeprecatedHandlingMode::Error, stack_depth); + if (mode == DeprecatedHandlingMode::Ignore) + return; + + script_log_add_source(L, message, stack_depth); + warningstream << message << std::endl; + + if (mode == DeprecatedHandlingMode::Error) + script_error(L, LUA_ERRRUN, NULL, NULL); + else + infostream << script_get_backtrace(L) << std::endl; } + diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index 452c2dd5e..ab2d7b975 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -75,9 +75,6 @@ extern "C" { } \ } -#define script_run_callbacks(L, nargs, mode) \ - script_run_callbacks_f((L), (nargs), (mode), __FUNCTION__) - // What script_run_callbacks does with the return values of callbacks. // Regardless of the mode, if only one callback is defined, // its return value is the total return value. @@ -108,13 +105,17 @@ enum RunCallbacksMode // are converted by readParam<bool> to true or false, respectively. }; +// Gets a backtrace of the current execution point std::string script_get_backtrace(lua_State *L); +// Wrapper for CFunction calls that converts C++ exceptions to Lua errors int script_exception_wrapper(lua_State *L, lua_CFunction f); +// Takes an error from lua_pcall and throws it as a LuaError void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); -void script_run_callbacks_f(lua_State *L, int nargs, - RunCallbacksMode mode, const char *fxn); -enum class DeprecatedHandlingMode { +bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, + int stack_depth = 1); + +enum DeprecatedHandlingMode { Ignore, Log, Error @@ -134,5 +135,4 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * @param message The deprecation method * @param stack_depth How far on the stack to the first user function (ie: not builtin or core) */ -void log_deprecated(lua_State *L, const std::string &message, - int stack_depth=1); +void log_deprecated(lua_State *L, std::string message, int stack_depth = 1); diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 0619b32c0..dacdcd75a 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -32,20 +32,19 @@ extern "C" { #include "filesys.h" #include "porting.h" #include "common/c_internal.h" +#include "lua_api/l_base.h" /******************************************************************************/ AsyncEngine::~AsyncEngine() { - // Request all threads to stop for (AsyncWorkerThread *workerThread : workerThreads) { workerThread->stop(); } - // Wake up all threads - for (std::vector<AsyncWorkerThread *>::iterator it = workerThreads.begin(); - it != workerThreads.end(); ++it) { + for (auto it : workerThreads) { + (void)it; jobQueueCounter.post(); } @@ -68,6 +67,7 @@ AsyncEngine::~AsyncEngine() /******************************************************************************/ void AsyncEngine::registerStateInitializer(StateInitializer func) { + FATAL_ERROR_IF(initDone, "Initializer may not be registered after init"); stateInitializers.push_back(func); } @@ -85,36 +85,36 @@ void AsyncEngine::initialize(unsigned int numEngines) } /******************************************************************************/ -unsigned int AsyncEngine::queueAsyncJob(const std::string &func, - const std::string ¶ms) +u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &¶ms, + const std::string &mod_origin) { jobQueueMutex.lock(); - LuaJobInfo toAdd; - toAdd.id = jobIdCounter++; - toAdd.serializedFunction = func; - toAdd.serializedParams = params; + u32 jobId = jobIdCounter++; - jobQueue.push_back(toAdd); + jobQueue.emplace_back(); + auto &to_add = jobQueue.back(); + to_add.id = jobId; + to_add.function = std::move(func); + to_add.params = std::move(params); + to_add.mod_origin = mod_origin; jobQueueCounter.post(); - jobQueueMutex.unlock(); - - return toAdd.id; + return jobId; } /******************************************************************************/ -LuaJobInfo AsyncEngine::getJob() +bool AsyncEngine::getJob(LuaJobInfo *job) { jobQueueCounter.wait(); jobQueueMutex.lock(); - LuaJobInfo retval; + bool retval = false; if (!jobQueue.empty()) { - retval = jobQueue.front(); + *job = std::move(jobQueue.front()); jobQueue.pop_front(); - retval.valid = true; + retval = true; } jobQueueMutex.unlock(); @@ -122,10 +122,10 @@ LuaJobInfo AsyncEngine::getJob() } /******************************************************************************/ -void AsyncEngine::putJobResult(const LuaJobInfo &result) +void AsyncEngine::putJobResult(LuaJobInfo &&result) { resultQueueMutex.lock(); - resultQueue.push_back(result); + resultQueue.emplace_back(std::move(result)); resultQueueMutex.unlock(); } @@ -134,26 +134,30 @@ void AsyncEngine::step(lua_State *L) { int error_handler = PUSH_ERROR_HANDLER(L); lua_getglobal(L, "core"); - resultQueueMutex.lock(); + + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); + + MutexAutoLock autolock(resultQueueMutex); while (!resultQueue.empty()) { - LuaJobInfo jobDone = resultQueue.front(); + LuaJobInfo j = std::move(resultQueue.front()); resultQueue.pop_front(); lua_getfield(L, -1, "async_event_handler"); - - if (lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) FATAL_ERROR("Async event handler does not exist!"); - } - luaL_checktype(L, -1, LUA_TFUNCTION); - lua_pushinteger(L, jobDone.id); - lua_pushlstring(L, jobDone.serializedResult.data(), - jobDone.serializedResult.size()); + lua_pushinteger(L, j.id); + lua_pushlstring(L, j.result.data(), j.result.size()); - PCALL_RESL(L, lua_pcall(L, 2, 0, error_handler)); + // Call handler + const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str(); + script->setOriginDirect(origin); + int result = lua_pcall(L, 2, 0, error_handler); + if (result) + script_error(L, result, origin, "<async>"); } - resultQueueMutex.unlock(); + lua_pop(L, 2); // Pop core and error handler } @@ -168,8 +172,8 @@ void AsyncEngine::prepareEnvironment(lua_State* L, int top) /******************************************************************************/ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name) : - Thread(name), ScriptApiBase(ScriptingType::Async), + Thread(name), jobDispatcher(jobDispatcher) { lua_State *L = getStack(); @@ -196,9 +200,9 @@ void* AsyncWorkerThread::run() { lua_State *L = getStack(); - std::string script = getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua"; try { - loadScript(script); + 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; @@ -213,44 +217,44 @@ void* AsyncWorkerThread::run() } // Main loop + LuaJobInfo j; while (!stopRequested()) { // Wait for job - LuaJobInfo toProcess = jobDispatcher->getJob(); - - if (!toProcess.valid || stopRequested()) { + if (!jobDispatcher->getJob(&j) || stopRequested()) continue; - } lua_getfield(L, -1, "job_processor"); - if (lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) FATAL_ERROR("Unable to get async job processor!"); - } - luaL_checktype(L, -1, LUA_TFUNCTION); - // Call it - lua_pushlstring(L, - toProcess.serializedFunction.data(), - toProcess.serializedFunction.size()); - lua_pushlstring(L, - toProcess.serializedParams.data(), - toProcess.serializedParams.size()); + if (luaL_loadbuffer(L, j.function.data(), j.function.size(), "=(async)")) { + errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl; + lua_pushnil(L); + } + lua_pushlstring(L, j.params.data(), j.params.size()); + // Call it + setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str()); int result = lua_pcall(L, 2, 1, error_handler); if (result) { - PCALL_RES(result); - toProcess.serializedResult = ""; + try { + scriptError(result, "<async>"); + } catch (const ModError &e) { + errorstream << e.what() << std::endl; + } } else { // Fetch result size_t length; const char *retval = lua_tolstring(L, -1, &length); - toProcess.serializedResult = std::string(retval, length); + j.result.assign(retval, length); } lua_pop(L, 1); // Pop retval // Put job result - jobDispatcher->putJobResult(toProcess); + if (!j.result.empty()) + jobDispatcher->putJobResult(std::move(j)); } lua_pop(L, 2); // Pop core and error handler diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 99a4f891c..697cb0221 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <deque> -#include <map> #include "threading/semaphore.h" #include "threading/thread.h" @@ -39,26 +38,29 @@ struct LuaJobInfo { LuaJobInfo() = default; - // Function to be called in async environment - std::string serializedFunction = ""; - // Parameter to be passed to function - std::string serializedParams = ""; - // Result of function call - std::string serializedResult = ""; + // Function to be called in async environment (from string.dump) + std::string function; + // Parameter to be passed to function (serialized) + std::string params; + // Result of function call (serialized) + std::string result; + // Name of the mod who invoked this call + std::string mod_origin; // JobID used to identify a job and match it to callback - unsigned int id = 0; - - bool valid = false; + u32 id; }; // Asynchronous working environment -class AsyncWorkerThread : public Thread, public ScriptApiBase { +class AsyncWorkerThread : public Thread, virtual public ScriptApiBase { + friend class AsyncEngine; public: - AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name); virtual ~AsyncWorkerThread(); void *run(); +protected: + AsyncWorkerThread(AsyncEngine* jobDispatcher, const std::string &name); + private: AsyncEngine *jobDispatcher = nullptr; }; @@ -89,7 +91,8 @@ public: * @param params Serialized parameters * @return jobid The job is queued */ - unsigned int queueAsyncJob(const std::string &func, const std::string ¶ms); + u32 queueAsyncJob(std::string &&func, std::string &¶ms, + const std::string &mod_origin = ""); /** * Engine step to process finished jobs @@ -102,15 +105,16 @@ protected: /** * Get a Job from queue to be processed * this function blocks until a job is ready - * @return a job to be processed + * @param job a job to be processed + * @return whether a job was available */ - LuaJobInfo getJob(); + bool getJob(LuaJobInfo *job); /** * Put a Job result back to result queue * @param result result of completed job */ - void putJobResult(const LuaJobInfo &result); + void putJobResult(LuaJobInfo &&result); /** * Initialize environment with current registred functions @@ -129,11 +133,10 @@ private: std::vector<StateInitializer> stateInitializers; // Internal counter to create job IDs - unsigned int jobIdCounter = 0; + u32 jobIdCounter = 0; // Mutex to protect job queue std::mutex jobQueueMutex; - // Job queue std::deque<LuaJobInfo> jobQueue; diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 5711ccbfd..27d730b1d 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -332,13 +332,9 @@ void ScriptApiBase::setOriginDirect(const char *origin) void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) { -#ifdef SCRIPTAPI_DEBUG lua_State *L = getStack(); - m_last_run_mod = lua_istable(L, index) ? getstringfield_default(L, index, "mod_origin", "") : ""; - //printf(">>>> running %s for mod: %s\n", fxn, m_last_run_mod.c_str()); -#endif } /* diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index a7a2c7203..e49745f4e 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -39,7 +39,6 @@ extern "C" { #include "config.h" #define SCRIPTAPI_LOCK_DEBUG -#define SCRIPTAPI_DEBUG // MUST be an invalid mod name so that mods can't // use that name to bypass security! @@ -111,7 +110,9 @@ public: Game *getGame() { return m_game; } #endif - std::string getOrigin() { return m_last_run_mod; } + // IMPORTANT: these cannot be used for any security-related uses, they exist + // only to enrich error messages + const std::string &getOrigin() { return m_last_run_mod; } void setOriginDirect(const char *origin); void setOriginFromTableRaw(int index, const char *fxn); @@ -130,8 +131,11 @@ protected: lua_State* getStack() { return m_luastack; } + // Checks that stack size is sane void realityCheck(); + // Takes an error from lua_pcall and throws it as a LuaError void scriptError(int result, const char *fxn); + // Dumps stack contents for debugging void stackDump(std::ostream &o); void setGameDef(IGameDef* gamedef) { m_gamedef = gamedef; } diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 1ed273a30..5d20f547d 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -36,7 +36,11 @@ void ScriptApiClient::on_mods_loaded() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_mods_loaded"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_shutdown() @@ -47,7 +51,11 @@ void ScriptApiClient::on_shutdown() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_shutdown"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } bool ScriptApiClient::on_sending_message(const std::string &message) @@ -59,7 +67,12 @@ bool ScriptApiClient::on_sending_message(const std::string &message) lua_getfield(L, -1, "registered_on_sending_chat_message"); // Call callbacks lua_pushstring(L, message.c_str()); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } @@ -72,7 +85,12 @@ bool ScriptApiClient::on_receiving_message(const std::string &message) lua_getfield(L, -1, "registered_on_receiving_chat_message"); // Call callbacks lua_pushstring(L, message.c_str()); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } @@ -85,7 +103,11 @@ void ScriptApiClient::on_damage_taken(int32_t damage_amount) lua_getfield(L, -1, "registered_on_damage_taken"); // Call callbacks lua_pushinteger(L, damage_amount); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_hp_modification(int32_t newhp) @@ -97,7 +119,11 @@ void ScriptApiClient::on_hp_modification(int32_t newhp) lua_getfield(L, -1, "registered_on_hp_modification"); // Call callbacks lua_pushinteger(L, newhp); - runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::on_death() @@ -108,7 +134,11 @@ void ScriptApiClient::on_death() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_death"); // Call callbacks - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + try { + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } void ScriptApiClient::environment_step(float dtime) @@ -123,8 +153,7 @@ void ScriptApiClient::environment_step(float dtime) try { runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { - getClient()->setFatalError(std::string("Client environment_step: ") + e.what() + "\n" - + script_get_backtrace(L)); + getClient()->setFatalError(e); } } @@ -149,7 +178,11 @@ void ScriptApiClient::on_formspec_input(const std::string &formname, lua_pushlstring(L, value.c_str(), value.size()); lua_settable(L, -3); } - runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); + } catch (LuaError &e) { + getClient()->setFatalError(e); + } } bool ScriptApiClient::on_dignode(v3s16 p, MapNode node) @@ -167,7 +200,12 @@ bool ScriptApiClient::on_dignode(v3s16 p, MapNode node) pushnode(L, node, ndef); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return lua_toboolean(L, -1); } @@ -186,7 +224,12 @@ bool ScriptApiClient::on_punchnode(v3s16 p, MapNode node) pushnode(L, node, ndef); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } @@ -203,7 +246,12 @@ bool ScriptApiClient::on_placenode(const PointedThing &pointed, const ItemDefini push_item_definition(L, item); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } @@ -220,7 +268,12 @@ bool ScriptApiClient::on_item_use(const ItemStack &item, const PointedThing &poi push_pointed_thing(L, pointed, true); // Call functions - runCallbacks(2, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(2, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } @@ -348,7 +401,12 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) push_inventory(L, inventory); - runCallbacks(1, RUN_CALLBACKS_MODE_OR); + try { + runCallbacks(1, RUN_CALLBACKS_MODE_OR); + } catch (LuaError &e) { + getClient()->setFatalError(e); + return true; + } return readParam<bool>(L, -1); } diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 8da5debaa..874c37b6e 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mapgen.h" #include "lua_api/l_env.h" #include "server.h" +#include "script/common/c_content.h" + void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, u32 blockseed) @@ -51,13 +53,7 @@ void ScriptApiEnv::environment_Step(float dtime) lua_getfield(L, -1, "registered_globalsteps"); // Call callbacks lua_pushnumber(L, dtime); - try { - runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); - } catch (LuaError &e) { - getServer()->setAsyncFatalError( - std::string("environment_Step: ") + e.what() + "\n" - + script_get_backtrace(L)); - } + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &type) @@ -74,13 +70,7 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t // Call callbacks objectrefGetOrCreate(L, player); // player lua_pushstring(L,type.c_str()); // event type - try { - runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); - } catch (LuaError &e) { - getServer()->setAsyncFatalError( - std::string("player_event: ") + e.what() + "\n" - + script_get_backtrace(L) ); - } + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) @@ -150,13 +140,19 @@ 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); lua_getfield(L, current_abm, "action"); luaL_checktype(L, current_abm + 1, LUA_TFUNCTION); lua_pop(L, 1); LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, - trigger_interval, trigger_chance, simple_catch_up); + trigger_interval, trigger_chance, simple_catch_up, min_y, max_y); env->addActiveBlockModifier(abm); @@ -249,9 +245,8 @@ void ScriptApiEnv::on_emerge_area_completion( try { PCALL_RES(lua_pcall(L, 4, 0, error_handler)); } catch (LuaError &e) { - server->setAsyncFatalError( - std::string("on_emerge_area_completion: ") + e.what() + "\n" - + script_get_backtrace(L)); + // Note: don't throw here, we still need to run the cleanup code below + server->setAsyncFatalError(e); } lua_pop(L, 1); // Pop error handler @@ -261,3 +256,35 @@ void ScriptApiEnv::on_emerge_area_completion( luaL_unref(L, LUA_REGISTRYINDEX, state->args_ref); } } + +void ScriptApiEnv::on_liquid_transformed( + const std::vector<std::pair<v3s16, MapNode>> &list) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_liquid_transformed + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_liquid_transformed"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_remove(L, -2); + + // Skip converting list and calling hook if there are + // no registered callbacks. + if(lua_objlen(L, -1) < 1) return; + + // Convert the list to a pos array and a node array for lua + int index = 1; + const NodeDefManager *ndef = getEnv()->getGameDef()->ndef(); + lua_createtable(L, list.size(), 0); + lua_createtable(L, list.size(), 0); + for(std::pair<v3s16, MapNode> p : list) { + lua_pushnumber(L, index); + push_v3s16(L, p.first); + lua_rawset(L, -4); + lua_pushnumber(L, index++); + pushnode(L, p.second, ndef); + lua_rawset(L, -3); + } + + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); +} diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 232a08aaf..090858f17 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "irr_v3d.h" +#include "mapnode.h" +#include <vector> class ServerEnvironment; struct ScriptCallbackState; @@ -41,5 +43,8 @@ public: void on_emerge_area_completion(v3s16 blockpos, int action, ScriptCallbackState *state); + // Called after liquid transform changes + void on_liquid_transformed(const std::vector<std::pair<v3s16, MapNode>> &list); + void initializeEnvironment(ServerEnvironment *env); }; diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index 24955cefc..48dce14f3 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -29,6 +29,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventory.h" #include "inventorymanager.h" +#define WRAP_LUAERROR(e, detail) \ + LuaError(std::string(__FUNCTION__) + ": " + (e).what() + ". " detail) + bool ScriptApiItem::item_OnDrop(ItemStack &item, ServerActiveObject *dropper, v3f pos) { @@ -49,7 +52,7 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -81,7 +84,7 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -108,7 +111,7 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -133,7 +136,7 @@ bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -165,7 +168,7 @@ bool ScriptApiItem::item_OnCraft(ItemStack &item, ServerActiveObject *user, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler @@ -197,7 +200,7 @@ bool ScriptApiItem::item_CraftPredict(ItemStack &item, ServerActiveObject *user, try { item = read_item(L, -1, getServer()->idef()); } catch (LuaError &e) { - throw LuaError(std::string(e.what()) + ". item=" + item.name); + throw WRAP_LUAERROR(e, "item=" + item.name); } } lua_pop(L, 2); // Pop item and error handler diff --git a/src/script/cpp_api/s_mainmenu.h b/src/script/cpp_api/s_mainmenu.h index aef36ce39..470577a29 100644 --- a/src/script/cpp_api/s_mainmenu.h +++ b/src/script/cpp_api/s_mainmenu.h @@ -38,7 +38,7 @@ public: void handleMainMenuEvent(std::string text); /** - * process field data recieved from formspec + * process field data received from formspec * @param fields data in field format */ void handleMainMenuButtons(const StringMap &fields); diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp index c081e9fc4..7ab3757f3 100644 --- a/src/script/cpp_api/s_nodemeta.cpp +++ b/src/script/cpp_api/s_nodemeta.cpp @@ -43,7 +43,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_move", &ma.to_inv.p)) return count; @@ -58,7 +58,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove( PCALL_RES(lua_pcall(L, 7, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_move should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -81,7 +81,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_put", &ma.to_inv.p)) return stack.count; @@ -94,7 +94,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut( PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if(!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_put should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -117,7 +117,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( return 0; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "allow_metadata_inventory_take", &ma.from_inv.p)) return stack.count; @@ -130,7 +130,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake( PCALL_RES(lua_pcall(L, 5, 1, error_handler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_take should" - " return a number, guilty node: " + nodename); + " return a number. node=" + nodename); int num = luaL_checkinteger(L, -1); lua_pop(L, 2); // Pop integer and error handler return num; @@ -153,7 +153,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_move", &ma.from_inv.p)) return; @@ -186,7 +186,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_put", &ma.to_inv.p)) return; @@ -217,7 +217,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake( return; // Push callback function on stack - std::string nodename = ndef->get(node).name; + const auto &nodename = ndef->get(node).name; if (!getItemCallback(nodename.c_str(), "on_metadata_inventory_take", &ma.from_inv.p)) return; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 8fe5d2c5a..bd9c80e15 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "cpp_api/s_security.h" - +#include "lua_api/l_base.h" #include "filesys.h" #include "porting.h" #include "server.h" @@ -541,15 +541,8 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, if (!removed.empty()) abs_path += DIR_DELIM + removed; - // Get server from registry - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); - ScriptApiBase *script; -#if INDIRECT_SCRIPTAPI_RIDX - script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); -#else - script = (ScriptApiBase *) lua_touserdata(L, -1); -#endif - lua_pop(L, 1); + // Get gamedef from registry + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); const IGameDef *gamedef = script->getGameDef(); if (!gamedef) return false; @@ -672,13 +665,7 @@ int ScriptApiSecurity::sl_g_load(lua_State *L) int ScriptApiSecurity::sl_g_loadfile(lua_State *L) { #ifndef SERVER - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI); -#if INDIRECT_SCRIPTAPI_RIDX - ScriptApiBase *script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1)); -#else - ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); -#endif - lua_pop(L, 1); + ScriptApiBase *script = ModApiBase::getScriptApiBase(L); // Client implementation if (script->getType() == ScriptingType::Client) { diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 96cb28b28..6ddb2630d 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_server.h" #include "cpp_api/s_internal.h" #include "common/c_converter.h" +#include "util/numeric.h" // myrand bool ScriptApiServer::getAuth(const std::string &playername, std::string *dst_password, @@ -196,3 +197,68 @@ std::string ScriptApiServer::formatChatMessage(const std::string &name, return ret; } + +u32 ScriptApiServer::allocateDynamicMediaCallback(int f_idx) +{ + lua_State *L = getStack(); + + if (f_idx < 0) + f_idx = lua_gettop(L) + f_idx + 1; + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + + // Find a randomly generated token that doesn't exist yet + int tries = 100; + u32 token; + while (1) { + token = myrand(); + lua_rawgeti(L, -2, token); + bool is_free = lua_isnil(L, -1); + lua_pop(L, 1); + if (is_free) + break; + if (--tries < 0) + FATAL_ERROR("Ran out of callbacks IDs?!"); + } + + // core.dynamic_media_callbacks[token] = callback_func + lua_pushvalue(L, f_idx); + lua_rawseti(L, -2, token); + + lua_pop(L, 2); + + verbosestream << "allocateDynamicMediaCallback() = " << token << std::endl; + return token; +} + +void ScriptApiServer::freeDynamicMediaCallback(u32 token) +{ + lua_State *L = getStack(); + + verbosestream << "freeDynamicMediaCallback(" << token << ")" << std::endl; + + // core.dynamic_media_callbacks[token] = nil + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + lua_rawseti(L, -2, token); + lua_pop(L, 2); +} + +void ScriptApiServer::on_dynamic_media_added(u32 token, const char *playername) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "dynamic_media_callbacks"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_rawgeti(L, -1, token); + luaL_checktype(L, -1, LUA_TFUNCTION); + + lua_pushstring(L, playername); + PCALL_RES(lua_pcall(L, 1, 0, error_handler)); +} diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index d8639cba7..c5c3d5596 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -49,6 +49,12 @@ public: const std::string &password); bool setPassword(const std::string &playername, const std::string &password); + + /* dynamic media handling */ + u32 allocateDynamicMediaCallback(int f_idx); + void freeDynamicMediaCallback(u32 token); + void on_dynamic_media_added(u32 token, const char *playername); + private: void getAuthHandler(); void readPrivileges(int index, std::set<std::string> &result); diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index 908c766b0..45724e604 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -372,7 +372,7 @@ void LuaAreaStore::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (AreaStore()) diff --git a/src/script/lua_api/l_auth.cpp b/src/script/lua_api/l_auth.cpp index 0fc57ba3a..32d8a7411 100644 --- a/src/script/lua_api/l_auth.cpp +++ b/src/script/lua_api/l_auth.cpp @@ -32,8 +32,11 @@ AuthDatabase *ModApiAuth::getAuthDb(lua_State *L) { ServerEnvironment *server_environment = dynamic_cast<ServerEnvironment *>(getEnv(L)); - if (!server_environment) + if (!server_environment) { + luaL_error(L, "Attempt to access an auth function but the auth" + " system is yet not initialized. This causes bugs."); return nullptr; + } return server_environment->getAuthDatabase(); } diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp index 40251154c..d85d16283 100644 --- a/src/script/lua_api/l_camera.cpp +++ b/src/script/lua_api/l_camera.cpp @@ -219,7 +219,7 @@ void LuaCamera::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); } diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index d6dc79ca8..74db996a4 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -46,13 +46,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif -struct EnumString ModApiEnvMod::es_ClearObjectsMode[] = +const EnumString ModApiEnvMod::es_ClearObjectsMode[] = { {CLEAR_OBJECTS_MODE_FULL, "full"}, {CLEAR_OBJECTS_MODE_QUICK, "quick"}, {0, NULL}, }; +const EnumString ModApiEnvMod::es_BlockStatusType[] = +{ + {ServerEnvironment::BS_UNKNOWN, "unknown"}, + {ServerEnvironment::BS_EMERGING, "emerging"}, + {ServerEnvironment::BS_LOADED, "loaded"}, + {ServerEnvironment::BS_ACTIVE, "active"}, + {0, NULL}, +}; + /////////////////////////////////////////////////////////////////////////////// @@ -228,7 +237,7 @@ void LuaRaycast::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -1547,6 +1556,24 @@ int ModApiEnvMod::l_forceload_block(lua_State *L) return 0; } +// compare_block_status(nodepos) +int ModApiEnvMod::l_compare_block_status(lua_State *L) +{ + GET_ENV_PTR; + + 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 + + lua_pushboolean(L, status >= condition_i); + return 1; +} + + // forceload_free_block(blockpos) // blockpos = {x=num, y=num, z=num} int ModApiEnvMod::l_forceload_free_block(lua_State *L) @@ -1620,6 +1647,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(transforming_liquid_add); API_FCT(forceload_block); API_FCT(forceload_free_block); + API_FCT(compare_block_status); API_FCT(get_translated_string); } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 29044c0e8..a5ac21e21 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -207,6 +207,9 @@ private: // stops forceloading a position static int l_forceload_free_block(lua_State *L); + // compare_block_status(nodepos) + static int l_compare_block_status(lua_State *L); + // Get a string translated server side static int l_get_translated_string(lua_State * L); @@ -219,7 +222,8 @@ public: static void Initialize(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); - static struct EnumString es_ClearObjectsMode[]; + static const EnumString es_ClearObjectsMode[]; + static const EnumString es_BlockStatusType[]; }; class LuaABM : public ActiveBlockModifier { @@ -231,17 +235,21 @@ private: float m_trigger_interval; u32 m_trigger_chance; bool m_simple_catch_up; + s16 m_min_y; + s16 m_max_y; public: LuaABM(lua_State *L, int id, const std::vector<std::string> &trigger_contents, const std::vector<std::string> &required_neighbors, - float trigger_interval, u32 trigger_chance, bool simple_catch_up): + float trigger_interval, u32 trigger_chance, bool simple_catch_up, s16 min_y, s16 max_y): m_id(id), m_trigger_contents(trigger_contents), m_required_neighbors(required_neighbors), m_trigger_interval(trigger_interval), m_trigger_chance(trigger_chance), - m_simple_catch_up(simple_catch_up) + m_simple_catch_up(simple_catch_up), + m_min_y(min_y), + m_max_y(max_y) { } virtual const std::vector<std::string> &getTriggerContents() const @@ -264,6 +272,14 @@ public: { return m_simple_catch_up; } + virtual s16 getMinY() + { + return m_min_y; + } + virtual s16 getMaxY() + { + return m_max_y; + } virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider); }; diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp index 5ea3b3f99..751ec9837 100644 --- a/src/script/lua_api/l_http.cpp +++ b/src/script/lua_api/l_http.cpp @@ -42,12 +42,10 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) req.caller = httpfetch_caller_alloc_secure(); getstringfield(L, 1, "url", req.url); - lua_getfield(L, 1, "user_agent"); - if (lua_isstring(L, -1)) - req.useragent = getstringfield_default(L, 1, "user_agent", ""); - lua_pop(L, 1); + getstringfield(L, 1, "user_agent", req.useragent); req.multipart = getboolfield_default(L, 1, "multipart", false); - req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000; + if (getintfield(L, 1, "timeout", req.timeout)) + req.timeout *= 1000; lua_getfield(L, 1, "method"); if (lua_isstring(L, -1)) { diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp index 434d0a76c..0dd418462 100644 --- a/src/script/lua_api/l_inventory.cpp +++ b/src/script/lua_api/l_inventory.cpp @@ -456,7 +456,7 @@ void InvRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index b098eccf0..2ea2bd4f6 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -488,7 +488,7 @@ void LuaItemStack::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil)) diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index d1ba1bda4..739fb9221 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -114,7 +114,7 @@ void ItemStackMetaRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 4f57ee00f..769b3ef2b 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -295,16 +295,20 @@ int LuaLocalPlayer::l_get_control(lua_State *L) }; lua_createtable(L, 0, 12); - set("up", c.up); - set("down", c.down); - set("left", c.left); - set("right", c.right); - set("jump", c.jump); - set("aux1", c.aux1); + set("jump", c.jump); + set("aux1", c.aux1); set("sneak", c.sneak); - set("zoom", c.zoom); - set("dig", c.dig); + set("zoom", c.zoom); + set("dig", c.dig); set("place", c.place); + // Player movement in polar coordinates and non-binary speed + set("movement_speed", c.movement_speed); + set("movement_direction", c.movement_direction); + // Provide direction keys to ensure compatibility + set("up", player->keyPressed & (1 << 0)); // Up, down, left, and right were removed in favor of + set("down", player->keyPressed & (1 << 1)); // analog direction indicators and are therefore not + set("left", player->keyPressed & (1 << 2)); // available as booleans anymore. The corresponding values + set("right", player->keyPressed & (1 << 3)); // can still be read from the keyPressed bits though. return 1; } @@ -452,10 +456,11 @@ int LuaLocalPlayer::l_hud_change(lua_State *L) if (!element) return 0; + HudElementStat stat; void *unused; - read_hud_change(L, element, &unused); + bool ok = read_hud_change(L, stat, element, &unused); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } @@ -550,7 +555,7 @@ void LuaLocalPlayer::Register(lua_State *L) lua_pop(L, 1); // Drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // Drop methodtable } diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index d88bf31c1..8eb0e252a 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.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_content.h" #include "cpp_api/s_async.h" +#include "scripting_mainmenu.h" #include "gui/guiEngine.h" #include "gui/guiMainMenu.h" #include "gui/guiKeyChangeMenu.h" @@ -741,13 +742,12 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) lua_newtable(L); for (u32 i = 0; i != drivers.size(); i++) { - const char *name = RenderingEngine::getVideoDriverName(drivers[i]); - const char *fname = RenderingEngine::getVideoDriverFriendlyName(drivers[i]); + auto &info = RenderingEngine::getVideoDriverInfo(drivers[i]); lua_newtable(L); - lua_pushstring(L, name); + lua_pushstring(L, info.name.c_str()); lua_setfield(L, -2, "name"); - lua_pushstring(L, fname); + lua_pushstring(L, info.friendly_name.c_str()); lua_setfield(L, -2, "friendly_name"); lua_rawseti(L, -2, i + 1); @@ -757,28 +757,6 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) } /******************************************************************************/ -int ModApiMainMenu::l_get_video_modes(lua_State *L) -{ - std::vector<core::vector3d<u32> > videomodes - = RenderingEngine::getSupportedVideoModes(); - - lua_newtable(L); - for (u32 i = 0; i != videomodes.size(); i++) { - lua_newtable(L); - lua_pushnumber(L, videomodes[i].X); - lua_setfield(L, -2, "w"); - lua_pushnumber(L, videomodes[i].Y); - lua_setfield(L, -2, "h"); - lua_pushnumber(L, videomodes[i].Z); - lua_setfield(L, -2, "depth"); - - lua_rawseti(L, -2, i + 1); - } - - return 1; -} - -/******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { std::string text = strgettext(std::string(luaL_checkstring(L, 1))); @@ -843,20 +821,20 @@ int ModApiMainMenu::l_open_dir(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_do_async_callback(lua_State *L) { - GUIEngine* engine = getGuiEngine(L); + MainMenuScripting *script = getScriptApi<MainMenuScripting>(L); size_t func_length, param_length; const char* serialized_func_raw = luaL_checklstring(L, 1, &func_length); - const char* serialized_param_raw = luaL_checklstring(L, 2, ¶m_length); sanity_check(serialized_func_raw != NULL); sanity_check(serialized_param_raw != NULL); - std::string serialized_func = std::string(serialized_func_raw, func_length); - std::string serialized_param = std::string(serialized_param_raw, param_length); + u32 jobId = script->queueAsync( + std::string(serialized_func_raw, func_length), + std::string(serialized_param_raw, param_length)); - lua_pushinteger(L, engine->queueAsync(serialized_func, serialized_param)); + lua_pushinteger(L, jobId); return 1; } @@ -899,7 +877,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(download_file); API_FCT(gettext); API_FCT(get_video_drivers); - API_FCT(get_video_modes); API_FCT(get_screen_info); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 33ac9e721..ec2d20da2 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -140,8 +140,6 @@ private: static int l_get_video_drivers(lua_State *L); - static int l_get_video_modes(lua_State *L); - //version compatibility static int l_get_min_supp_proto(lua_State *L); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index eb3d49a5e..f173bd162 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -752,9 +752,7 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) lua_setfield(L, -2, "mgname"); settingsmgr->getMapSetting("seed", &value); - std::istringstream ss(value); - u64 seed; - ss >> seed; + u64 seed = from_string<u64>(value); lua_pushinteger(L, seed); lua_setfield(L, -2, "seed"); diff --git a/src/script/lua_api/l_minimap.cpp b/src/script/lua_api/l_minimap.cpp index 3bbb6e5e3..a135e0bd5 100644 --- a/src/script/lua_api/l_minimap.cpp +++ b/src/script/lua_api/l_minimap.cpp @@ -211,7 +211,7 @@ void LuaMinimap::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_modchannels.cpp b/src/script/lua_api/l_modchannels.cpp index 0485b276a..931c2749c 100644 --- a/src/script/lua_api/l_modchannels.cpp +++ b/src/script/lua_api/l_modchannels.cpp @@ -107,7 +107,7 @@ void ModChannelRef::Register(lua_State *L) lua_pop(L, 1); // Drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // Drop methodtable } diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 57052cb42..60d14f8f2 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -234,7 +234,7 @@ void NodeMetaRef::RegisterCommon(lua_State *L) void NodeMetaRef::Register(lua_State *L) { RegisterCommon(L); - luaL_openlib(L, 0, methodsServer, 0); // fill methodtable + luaL_register(L, nullptr, methodsServer); // fill methodtable lua_pop(L, 1); // drop methodtable } @@ -260,7 +260,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = { void NodeMetaRef::RegisterClient(lua_State *L) { RegisterCommon(L); - luaL_openlib(L, 0, methodsClient, 0); // fill methodtable + luaL_register(L, nullptr, methodsClient); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_nodetimer.cpp b/src/script/lua_api/l_nodetimer.cpp index c2df52c05..8a302149f 100644 --- a/src/script/lua_api/l_nodetimer.cpp +++ b/src/script/lua_api/l_nodetimer.cpp @@ -122,7 +122,7 @@ void NodeTimerRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Cannot be created from Lua diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index e0861126a..f43ba837a 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -122,7 +122,7 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -380,7 +380,7 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -485,7 +485,7 @@ void LuaPseudoRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -584,7 +584,7 @@ void LuaPcgRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); @@ -699,7 +699,7 @@ void LuaSecureRandom::Register(lua_State *L) lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); lua_register(L, className, create_object); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 8ae99b929..b7185f7ec 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -172,27 +172,11 @@ int ObjectRef::l_punch(lua_State *L) float time_from_last_punch = readParam<float>(L, 3, 1000000.0f); ToolCapabilities toolcap = read_tool_capabilities(L, 4); v3f dir = readParam<v3f>(L, 5, sao->getBasePosition() - puncher->getBasePosition()); - dir.normalize(); - u16 src_original_hp = sao->getHP(); - u16 dst_origin_hp = puncher->getHP(); u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch); lua_pushnumber(L, wear); - // If the punched is a player, and its HP changed - if (src_original_hp != sao->getHP() && - sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); - } - - // If the puncher is a player, and its HP changed - if (dst_origin_hp != puncher->getHP() && - puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, sao)); - } return 1; } @@ -238,8 +222,6 @@ int ObjectRef::l_set_hp(lua_State *L) } sao->setHP(hp, reason); - if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)sao, reason); if (reason.hasLuaReference()) luaL_unref(L, LUA_REGISTRYINDEX, reason.lua_reference); return 0; @@ -685,6 +667,7 @@ int ObjectRef::l_set_properties(lua_State *L) return 0; read_object_properties(L, 2, sao, prop, getServer(L)->idef()); + prop->validate(); sao->notifyObjectPropertiesModified(); return 0; } @@ -752,6 +735,7 @@ int ObjectRef::l_set_nametag_attributes(lua_State *L) std::string nametag = getstringfield_default(L, 2, "text", ""); prop->nametag = nametag; + prop->validate(); sao->notifyObjectPropertiesModified(); lua_pushboolean(L, true); return 1; @@ -1390,13 +1374,13 @@ int ObjectRef::l_get_player_control(lua_State *L) const PlayerControl &control = player->getPlayerControl(); lua_newtable(L); - lua_pushboolean(L, control.up); + lua_pushboolean(L, player->keyPressed & (1 << 0)); lua_setfield(L, -2, "up"); - lua_pushboolean(L, control.down); + lua_pushboolean(L, player->keyPressed & (1 << 1)); lua_setfield(L, -2, "down"); - lua_pushboolean(L, control.left); + lua_pushboolean(L, player->keyPressed & (1 << 2)); lua_setfield(L, -2, "left"); - lua_pushboolean(L, control.right); + lua_pushboolean(L, player->keyPressed & (1 << 3)); lua_setfield(L, -2, "right"); lua_pushboolean(L, control.jump); lua_setfield(L, -2, "jump"); @@ -1553,12 +1537,14 @@ int ObjectRef::l_hud_change(lua_State *L) if (elem == nullptr) return 0; + HudElementStat stat; void *value = nullptr; - HudElementStat stat = read_hud_change(L, elem, &value); + bool ok = read_hud_change(L, stat, elem, &value); - getServer(L)->hudChange(player, id, stat, value); + if (ok) + getServer(L)->hudChange(player, id, stat, value); - lua_pushboolean(L, true); + lua_pushboolean(L, ok); return 1; } @@ -2311,7 +2297,7 @@ void ObjectRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_playermeta.cpp b/src/script/lua_api/l_playermeta.cpp index 558672e38..2706c99df 100644 --- a/src/script/lua_api/l_playermeta.cpp +++ b/src/script/lua_api/l_playermeta.cpp @@ -97,7 +97,7 @@ void PlayerMetaRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); + luaL_register(L, nullptr, methods); lua_pop(L, 1); // Cannot be created from Lua diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 026f5282c..b4672fe2a 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "cpp_api/s_base.h" #include "cpp_api/s_security.h" +#include "scripting_server.h" #include "server.h" #include "environment.h" #include "remoteplayer.h" @@ -453,29 +454,37 @@ int ModApiServer::l_sound_fade(lua_State *L) } // dynamic_add_media(filepath) -int ModApiServer::l_dynamic_add_media_raw(lua_State *L) +int ModApiServer::l_dynamic_add_media(lua_State *L) { NO_MAP_LOCK_REQUIRED; if (!getEnv(L)) throw LuaError("Dynamic media cannot be added before server has started up"); + Server *server = getServer(L); - std::string filepath = readParam<std::string>(L, 1); - CHECK_SECURE_PATH(L, filepath.c_str(), false); + std::string filepath; + std::string to_player; + bool ephemeral = false; - std::vector<RemotePlayer*> sent_to; - bool ok = getServer(L)->dynamicAddMedia(filepath, sent_to); - if (ok) { - // (see wrapper code in builtin) - lua_createtable(L, sent_to.size(), 0); - int i = 0; - for (RemotePlayer *player : sent_to) { - lua_pushstring(L, player->getName()); - lua_rawseti(L, -2, ++i); - } + if (lua_istable(L, 1)) { + getstringfield(L, 1, "filepath", filepath); + getstringfield(L, 1, "to_player", to_player); + getboolfield(L, 1, "ephemeral", ephemeral); } else { - lua_pushboolean(L, false); + filepath = readParam<std::string>(L, 1); } + if (filepath.empty()) + luaL_typerror(L, 1, "non-empty string"); + luaL_checktype(L, 2, LUA_TFUNCTION); + + CHECK_SECURE_PATH(L, filepath.c_str(), false); + + u32 token = server->getScriptIface()->allocateDynamicMediaCallback(2); + + bool ok = server->dynamicAddMedia(filepath, token, to_player, ephemeral); + if (!ok) + server->getScriptIface()->freeDynamicMediaCallback(token); + lua_pushboolean(L, ok); return 1; } @@ -499,31 +508,6 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } -// get_last_run_mod() -int ModApiServer::l_get_last_run_mod(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); - std::string current_mod = readParam<std::string>(L, -1, ""); - if (current_mod.empty()) { - lua_pop(L, 1); - lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); - } - return 1; -} - -// set_last_run_mod(modname) -int ModApiServer::l_set_last_run_mod(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; -#ifdef SCRIPTAPI_DEBUG - const char *mod = lua_tostring(L, 1); - getScriptApiBase(L)->setOriginDirect(mod); - //printf(">>>> last mod set from Lua: %s\n", mod); -#endif - return 0; -} - void ModApiServer::Initialize(lua_State *L, int top) { API_FCT(request_shutdown); @@ -544,7 +528,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(sound_play); API_FCT(sound_stop); API_FCT(sound_fade); - API_FCT(dynamic_add_media_raw); + API_FCT(dynamic_add_media); API_FCT(get_player_information); API_FCT(get_player_privs); @@ -555,7 +539,4 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(kick_player); API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); - - API_FCT(get_last_run_mod); - API_FCT(set_last_run_mod); } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 2df180b17..c688e494b 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -71,7 +71,7 @@ private: static int l_sound_fade(lua_State *L); // dynamic_add_media(filepath) - static int l_dynamic_add_media_raw(lua_State *L); + static int l_dynamic_add_media(lua_State *L); // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); @@ -103,12 +103,6 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); - // get_last_run_mod() - static int l_get_last_run_mod(lua_State *L); - - // set_last_run_mod(modname) - static int l_set_last_run_mod(lua_State *L); - public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index a82073ed4..14398dda2 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -334,7 +334,7 @@ void LuaSettings::Register(lua_State* L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (Settings(filename)) diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index cba34fb63..978b315d5 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -110,7 +110,7 @@ void StorageRef::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 624828956..d575eb603 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "util/hex.h" #include "util/sha1.h" +#include "util/png.h" #include <algorithm> #include <cstdio> @@ -271,11 +272,11 @@ int ModApiUtil::l_compress(lua_State *L) const char *data = luaL_checklstring(L, 1, &size); int level = -1; - if (!lua_isnone(L, 3) && !lua_isnil(L, 3)) - level = readParam<float>(L, 3); + if (!lua_isnoneornil(L, 3)) + level = readParam<int>(L, 3); - std::ostringstream os; - compressZlib(std::string(data, size), os, level); + std::ostringstream os(std::ios_base::binary); + compressZlib(reinterpret_cast<const u8 *>(data), size, os, level); std::string out = os.str(); @@ -291,8 +292,8 @@ int ModApiUtil::l_decompress(lua_State *L) size_t size; const char *data = luaL_checklstring(L, 1, &size); - std::istringstream is(std::string(data, size)); - std::ostringstream os; + std::istringstream is(std::string(data, size), std::ios_base::binary); + std::ostringstream os(std::ios_base::binary); decompressZlib(is, os); std::string out = os.str(); @@ -497,6 +498,67 @@ int ModApiUtil::l_colorspec_to_colorstring(lua_State *L) return 0; } +// colorspec_to_bytes(colorspec) +int ModApiUtil::l_colorspec_to_bytes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + video::SColor color(0); + if (read_color(L, 1, &color)) { + u8 colorbytes[4] = { + (u8) color.getRed(), + (u8) color.getGreen(), + (u8) color.getBlue(), + (u8) color.getAlpha(), + }; + lua_pushlstring(L, (const char*) colorbytes, 4); + return 1; + } + + return 0; +} + +// encode_png(w, h, data, level) +int ModApiUtil::l_encode_png(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // The args are already pre-validated on the lua side. + u32 width = readParam<int>(L, 1); + u32 height = readParam<int>(L, 2); + const char *data = luaL_checklstring(L, 3, NULL); + s32 compression = readParam<int>(L, 4); + + std::string out = encodePNG((const u8*)data, width, height, compression); + + lua_pushlstring(L, out.data(), out.size()); + return 1; +} + +// get_last_run_mod() +int ModApiUtil::l_get_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + std::string current_mod = readParam<std::string>(L, -1, ""); + if (current_mod.empty()) { + lua_pop(L, 1); + lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); + } + return 1; +} + +// set_last_run_mod(modname) +int ModApiUtil::l_set_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + const char *mod = luaL_checkstring(L, 1); + getScriptApiBase(L)->setOriginDirect(mod); + return 0; +} + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(log); @@ -532,6 +594,12 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); 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); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); @@ -559,6 +627,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(get_version); 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"); @@ -590,6 +659,10 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(get_version); API_FCT(sha1); API_FCT(colorspec_to_colorstring); + API_FCT(colorspec_to_bytes); + + API_FCT(get_last_run_mod); + API_FCT(set_last_run_mod); LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 6943a6afb..cc91e8d39 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -104,6 +104,18 @@ private: // colorspec_to_colorstring(colorspec) static int l_colorspec_to_colorstring(lua_State *L); + // colorspec_to_bytes(colorspec) + static int l_colorspec_to_bytes(lua_State *L); + + // encode_png(w, h, data, level) + static int l_encode_png(lua_State *L); + + // get_last_run_mod() + static int l_get_last_run_mod(lua_State *L); + + // set_last_run_mod(modname) + static int l_set_last_run_mod(lua_State *L); + 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_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index b99b1d98c..e040e545b 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -450,7 +450,7 @@ void LuaVoxelManip::Register(lua_State *L) lua_pop(L, 1); // drop metatable - luaL_openlib(L, 0, methods, 0); // fill methodtable + luaL_register(L, nullptr, methods); // fill methodtable lua_pop(L, 1); // drop methodtable // Can be created from Lua (VoxelManip()) diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index b102a66a1..2a0cadb23 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -92,9 +92,9 @@ void MainMenuScripting::step() } /******************************************************************************/ -unsigned int MainMenuScripting::queueAsync(const std::string &serialized_func, - const std::string &serialized_param) +u32 MainMenuScripting::queueAsync(std::string &&serialized_func, + std::string &&serialized_param) { - return asyncEngine.queueAsyncJob(serialized_func, serialized_param); + return asyncEngine.queueAsyncJob(std::move(serialized_func), std::move(serialized_param)); } diff --git a/src/script/scripting_mainmenu.h b/src/script/scripting_mainmenu.h index 9e23bdc1b..3c329654a 100644 --- a/src/script/scripting_mainmenu.h +++ b/src/script/scripting_mainmenu.h @@ -38,8 +38,9 @@ public: void step(); // Pass async events from engine to async threads - unsigned int queueAsync(const std::string &serialized_func, - const std::string &serialized_params); + u32 queueAsync(std::string &&serialized_func, + std::string &&serialized_param); + private: void initializeModApi(lua_State *L, int top); static void registerLuaClasses(lua_State *L, int top); diff --git a/src/serialization.cpp b/src/serialization.cpp index 310604f54..b6ce3b37f 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -21,7 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" -#include "zlib.h" +#include <zlib.h> +#include <zstd.h> /* report a zlib or i/o error */ void zerr(int ret) @@ -197,27 +198,133 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) inflateEnd(&z); } -void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version) +struct ZSTD_Deleter { + void operator() (ZSTD_CStream* cstream) { + ZSTD_freeCStream(cstream); + } + + void operator() (ZSTD_DStream* dstream) { + ZSTD_freeDStream(dstream); + } +}; + +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr<ZSTD_CStream, ZSTD_Deleter> stream(ZSTD_createCStream()); + + ZSTD_initCStream(stream.get(), level); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + + ZSTD_inBuffer input = { data, data_size, 0 }; + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + + while (input.pos < input.size) { + size_t ret = ZSTD_compressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } + + size_t ret; + do { + ret = ZSTD_endStream(stream.get(), &output); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + +} + +void compressZstd(const std::string &data, std::ostream &os, int level) { + compressZstd((u8*)data.c_str(), data.size(), os, level); +} + +void decompressZstd(std::istream &is, std::ostream &os) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr<ZSTD_DStream, ZSTD_Deleter> stream(ZSTD_createDStream()); + + ZSTD_initDStream(stream.get()); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + char input_buffer[bufsize]; + + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + ZSTD_inBuffer input = { input_buffer, 0, 0 }; + size_t ret; + do + { + if (input.size == input.pos) { + is.read(input_buffer, bufsize); + input.size = is.gcount(); + input.pos = 0; + } + + ret = ZSTD_decompressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("decompressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + + // Unget all the data that ZSTD_decompressStream didn't take + is.clear(); // Just in case EOF is set + for (u32 i = 0; i < input.size - input.pos; i++) { + is.unget(); + if (is.fail() || is.bad()) + throw SerializationError("decompressZstd: unget failed"); + } +} + +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level) +{ + if(version >= 29) + { + // map the zlib levels [0,9] to [1,10]. -1 becomes 0 which indicates the default (currently 3) + compressZstd(data, size, os, level + 1); + return; + } + if(version >= 11) { - compressZlib(*data ,data.getSize(), os); + compressZlib(data, size, os, level); return; } - if(data.getSize() == 0) + if(size == 0) return; // Write length (u32) u8 tmp[4]; - writeU32(tmp, data.getSize()); + writeU32(tmp, size); os.write((char*)tmp, 4); // We will be writing 8-bit pairs of more_count and byte u8 more_count = 0; u8 current_byte = data[0]; - for(u32 i=1; i<data.getSize(); i++) + for(u32 i=1; i<size; i++) { if( data[i] != current_byte @@ -240,8 +347,24 @@ void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version) os.write((char*)¤t_byte, 1); } +void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version, int level) +{ + compress(*data, data.getSize(), os, version, level); +} + +void compress(const std::string &data, std::ostream &os, u8 version, int level) +{ + compress((u8*)data.c_str(), data.size(), os, version, level); +} + void decompress(std::istream &is, std::ostream &os, u8 version) { + if(version >= 29) + { + decompressZstd(is, os); + return; + } + if(version >= 11) { decompressZlib(is, os); diff --git a/src/serialization.h b/src/serialization.h index f399983c4..e83a8c179 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -63,13 +63,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 26: Never written; read the same as 25 27: Added light spreading flags to blocks 28: Added "private" flag to NodeMetadata + 29: Switched compression to zstd, a bit of reorganization */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 28 +#define SER_FMT_VER_HIGHEST_READ 29 // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 28 +#define SER_FMT_VER_HIGHEST_WRITE 29 // Lowest supported serialization version #define SER_FMT_VER_LOWEST_READ 0 // Lowest serialization version for writing @@ -89,7 +90,12 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level void compressZlib(const std::string &data, std::ostream &os, int level = -1); void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level = 0); +void compressZstd(const std::string &data, std::ostream &os, int level = 0); +void decompressZstd(std::istream &is, std::ostream &os); + // These choose between zlib and a self-made one according to version -void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version); -//void compress(const std::string &data, std::ostream &os, u8 version); +void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version, int level = -1); +void compress(const std::string &data, std::ostream &os, u8 version, int level = -1); +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level = -1); void decompress(std::istream &is, std::ostream &os, u8 version); diff --git a/src/server.cpp b/src/server.cpp index a8d452783..7fb9a78e9 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -103,7 +103,13 @@ void *ServerThread::run() * doesn't busy wait) and will process any remaining packets. */ - m_server->AsyncRunStep(true); + try { + m_server->AsyncRunStep(true); + } catch (con::ConnectionBindFailed &e) { + m_server->setAsyncFatalError(e.what()); + } catch (LuaError &e) { + m_server->setAsyncFatalError(e); + } while (!stopRequested()) { try { @@ -117,8 +123,7 @@ void *ServerThread::run() } catch (con::ConnectionBindFailed &e) { m_server->setAsyncFatalError(e.what()); } catch (LuaError &e) { - m_server->setAsyncFatalError( - "ServerThread::run Lua: " + std::string(e.what())); + m_server->setAsyncFatalError(e); } } @@ -665,6 +670,17 @@ void Server::AsyncRunStep(bool initial_step) } else { m_lag_gauge->increment(dtime/100); } + + { + float &counter = m_step_pending_dyn_media_timer; + counter += dtime; + if (counter >= 5.0f) { + stepPendingDynMediaCallbacks(counter); + counter = 0; + } + } + + #if USE_CURL // send masterserver announce { @@ -1079,8 +1095,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); else - SendPlayerHPOrDie(playersao, - PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); + SendPlayerHP(peer_id); // Send Breath SendPlayerBreath(playersao); @@ -1638,7 +1653,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir << form->align << form->offset << form->world_pos << form->size - << form->z_index << form->text2; + << form->z_index << form->text2 << form->style; Send(&pkt); } @@ -1673,10 +1688,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void case HUD_STAT_SIZE: pkt << *(v2s32 *) value; break; - case HUD_STAT_NUMBER: - case HUD_STAT_ITEM: - case HUD_STAT_DIR: - default: + default: // all other types pkt << *(u32 *) value; break; } @@ -2313,7 +2325,7 @@ void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far // Send the meta changes std::ostringstream os(std::ios::binary); - meta_updates_list.serialize(os, client->net_proto_version, false, true); + meta_updates_list.serialize(os, client->serialization_version, false, true, true); std::ostringstream oss(std::ios::binary); compressZlib(os.str(), oss); @@ -2442,9 +2454,8 @@ bool Server::addMediaFile(const std::string &filename, // If name is not in a supported format, ignore it const char *supported_ext[] = { ".png", ".jpg", ".bmp", ".tga", - ".pcx", ".ppm", ".psd", ".wal", ".rgb", ".ogg", - ".x", ".b3d", ".md2", ".obj", + ".x", ".b3d", ".obj", // Custom translation file format ".tr", NULL @@ -2532,6 +2543,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co std::string lang_suffix; lang_suffix.append(".").append(lang_code).append(".tr"); for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; media_sent++; @@ -2540,6 +2553,8 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co pkt << media_sent; for (const auto &i : m_media) { + if (i.second.no_announce) + continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; pkt << i.first << i.second.sha1_digest; @@ -2558,11 +2573,9 @@ struct SendableMedia std::string path; std::string data; - SendableMedia(const std::string &name_="", const std::string &path_="", - const std::string &data_=""): - name(name_), - path(path_), - data(data_) + SendableMedia(const std::string &name, const std::string &path, + std::string &&data): + name(name), path(path), data(std::move(data)) {} }; @@ -2589,40 +2602,19 @@ void Server::sendRequestedMedia(session_t peer_id, continue; } - //TODO get path + name - std::string tpath = m_media[name].path; + const auto &m = m_media[name]; // Read data - std::ifstream fis(tpath.c_str(), std::ios_base::binary); - if(!fis.good()){ - errorstream<<"Server::sendRequestedMedia(): Could not open \"" - <<tpath<<"\" for reading"<<std::endl; + std::string data; + if (!fs::ReadFile(m.path, data)) { + errorstream << "Server::sendRequestedMedia(): Failed to read \"" + << name << "\"" << std::endl; continue; } - std::ostringstream tmp_os(std::ios_base::binary); - bool bad = false; - for(;;) { - char buf[1024]; - fis.read(buf, 1024); - std::streamsize len = fis.gcount(); - tmp_os.write(buf, len); - file_size_bunch_total += len; - if(fis.eof()) - break; - if(!fis.good()) { - bad = true; - break; - } - } - if (bad) { - errorstream<<"Server::sendRequestedMedia(): Failed to read \"" - <<name<<"\""<<std::endl; - continue; - } - /*infostream<<"Server::sendRequestedMedia(): Loaded \"" - <<tname<<"\""<<std::endl;*/ + file_size_bunch_total += data.size(); + // Put in list - file_bunches[file_bunches.size()-1].emplace_back(name, tpath, tmp_os.str()); + file_bunches.back().emplace_back(name, m.path, std::move(data)); // Start next bunch if got enough data if(file_size_bunch_total >= bytes_per_bunch) { @@ -2665,6 +2657,33 @@ void Server::sendRequestedMedia(session_t peer_id, } } +void Server::stepPendingDynMediaCallbacks(float dtime) +{ + MutexAutoLock lock(m_env_mutex); + + for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) { + it->second.expiry_timer -= dtime; + bool del = it->second.waiting_players.empty() || it->second.expiry_timer < 0; + + if (!del) { + it++; + continue; + } + + const auto &name = it->second.filename; + if (!name.empty()) { + assert(m_media.count(name)); + // if no_announce isn't set we're definitely deleting the wrong file! + sanity_check(m_media[name].no_announce); + + fs::DeleteSingleFileOrEmptyDirectory(m_media[name].path); + m_media.erase(name); + } + getScriptIface()->freeDynamicMediaCallback(it->first); + it = m_pending_dyn_media.erase(it); + } +} + void Server::SendMinimapModes(session_t peer_id, std::vector<MinimapMode> &modes, size_t wanted_mode) { @@ -3001,6 +3020,9 @@ std::wstring Server::handleChat(const std::string &name, } auto message = trim(wide_to_utf8(wmessage)); + if (message.empty()) + return L""; + if (message.find_first_of("\n\r") != std::wstring::npos) { return L"Newlines are not permitted in chat messages"; } @@ -3459,14 +3481,18 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id) SendDeleteParticleSpawner(peer_id, id); } -bool Server::dynamicAddMedia(const std::string &filepath, - std::vector<RemotePlayer*> &sent_to) +bool Server::dynamicAddMedia(std::string filepath, + const u32 token, const std::string &to_player, bool ephemeral) { std::string filename = fs::GetFilenameFromPath(filepath.c_str()); - if (m_media.find(filename) != m_media.end()) { - errorstream << "Server::dynamicAddMedia(): file \"" << filename - << "\" already exists in media cache" << std::endl; - return false; + auto it = m_media.find(filename); + if (it != m_media.end()) { + // Allow the same path to be "added" again in certain conditions + if (ephemeral || it->second.path != filepath) { + errorstream << "Server::dynamicAddMedia(): file \"" << filename + << "\" already exists in media cache" << std::endl; + return false; + } } // Load the file and add it to our media cache @@ -3475,35 +3501,91 @@ bool Server::dynamicAddMedia(const std::string &filepath, if (!ok) return false; + if (ephemeral) { + // Create a copy of the file and swap out the path, this removes the + // requirement that mods keep the file accessible at the original path. + filepath = fs::CreateTempFile(); + bool ok = ([&] () -> bool { + if (filepath.empty()) + return false; + std::ofstream os(filepath.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << filedata; + os.close(); + return !os.fail(); + })(); + if (!ok) { + errorstream << "Server: failed to create a copy of media file " + << "\"" << filename << "\"" << std::endl; + m_media.erase(filename); + return false; + } + verbosestream << "Server: \"" << filename << "\" temporarily copied to " + << filepath << std::endl; + + m_media[filename].path = filepath; + m_media[filename].no_announce = true; + // stepPendingDynMediaCallbacks will clean this up later. + } else if (!to_player.empty()) { + m_media[filename].no_announce = true; + } + // Push file to existing clients NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << (bool) true; - pkt.putLongString(filedata); + pkt << raw_hash << filename << (bool)ephemeral; + NetworkPacket legacy_pkt = pkt; + + // Newer clients get asked to fetch the file (asynchronous) + pkt << token; + // Older clients have an awful hack that just throws the data at them + 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) continue; - if (pair.second->net_proto_version < 39) + const auto proto_ver = pair.second->net_proto_version; + if (proto_ver < 39) continue; - if (auto player = m_env->getPlayer(pair.second->peer_id)) - sent_to.emplace_back(player); - /* - FIXME: this is a very awful hack - The network layer only guarantees ordered delivery inside a channel. - Since the very next packet could be one that uses the media, we have - to push the media over ALL channels to ensure it is processed before - it is used. - In practice this means we have to send it twice: - - channel 1 (HUD) - - channel 0 (everything else: e.g. play_sound, object messages) - */ - m_clients.send(pair.second->peer_id, 1, &pkt, true); - m_clients.send(pair.second->peer_id, 0, &pkt, true); + 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); + } } m_clients.unlock(); + // Run callback for players that already had the file delivered (legacy-only) + for (session_t peer_id : delivered) { + if (auto player = m_env->getPlayer(peer_id)) + getScriptIface()->on_dynamic_media_added(token, player->getName()); + } + + // Save all others in our pending state + auto &state = m_pending_dyn_media[token]; + state.waiting_players = std::move(waiting); + // regardless of success throw away the callback after a while + state.expiry_timer = 60.0f; + if (ephemeral) + state.filename = filename; + return true; } diff --git a/src/server.h b/src/server.h index 9857215d0..c5db0fdfb 100644 --- a/src/server.h +++ b/src/server.h @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <list> #include <map> #include <vector> +#include <unordered_set> class ChatEvent; struct ChatEventChat; @@ -81,12 +82,14 @@ enum ClientDeletionReason { struct MediaInfo { std::string path; - std::string sha1_digest; + std::string sha1_digest; // base64-encoded + bool no_announce; // true: not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) MediaInfo(const std::string &path_="", const std::string &sha1_digest_=""): path(path_), - sha1_digest(sha1_digest_) + sha1_digest(sha1_digest_), + no_announce(false) { } }; @@ -197,6 +200,7 @@ public: void handleCommand_FirstSrp(NetworkPacket* pkt); void handleCommand_SrpBytesA(NetworkPacket* pkt); void handleCommand_SrpBytesM(NetworkPacket* pkt); + void handleCommand_HaveMedia(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); @@ -257,7 +261,8 @@ public: void deleteParticleSpawner(const std::string &playername, u32 id); - bool dynamicAddMedia(const std::string &filepath, std::vector<RemotePlayer*> &sent_to); + bool dynamicAddMedia(std::string filepath, u32 token, + const std::string &to_player, bool ephemeral); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); @@ -295,6 +300,10 @@ public: inline void setAsyncFatalError(const std::string &error) { m_async_fatal_error.set(error); } + inline void setAsyncFatalError(const LuaError &e) + { + setAsyncFatalError(std::string("Lua: ") + e.what()); + } bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); Map & getMap() { return m_env->getMap(); } @@ -395,6 +404,12 @@ private: float m_timer = 0.0f; }; + struct PendingDynamicMediaCallback { + std::string filename; // only set if media entry and file is to be deleted + float expiry_timer; + std::unordered_set<session_t> waiting_players; + }; + void init(); void SendMovement(session_t peer_id); @@ -466,6 +481,7 @@ private: void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendRequestedMedia(session_t peer_id, const std::vector<std::string> &tosend); + void stepPendingDynMediaCallbacks(float dtime); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, @@ -650,6 +666,10 @@ private: // media files known to server std::unordered_map<std::string, MediaInfo> m_media; + // pending dynamic media callbacks, clients inform the server when they have a file fetched + std::unordered_map<u32, PendingDynamicMediaCallback> m_pending_dyn_media; + float m_step_pending_dyn_media_timer = 0.0f; + /* Sounds */ diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 83fa12da9..609d8c346 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -61,12 +61,8 @@ void ServerModManager::loadMods(ServerScripting *script) infostream << std::endl; // Load and run "mod" scripts for (const ModSpec &mod : m_sorted_mods) { - if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { - throw ModError("Error loading mod \"" + mod.name + - "\": Mod name does not follow naming " - "conventions: " - "Only characters [a-z0-9_] are allowed."); - } + mod.checkAndLog(); + std::string script_path = mod.path + DIR_DELIM + "init.lua"; auto t = porting::getTimeMs(); script->loadMod(script_path, mod.name); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 0d31f2e0b..d4d036726 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -167,7 +167,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (m_breath == 0) { PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); setHP(m_hp - c.drowning, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } } @@ -216,7 +215,6 @@ void PlayerSAO::step(float dtime, bool send_recommended) s32 newhp = (s32)m_hp - (s32)damage_per_second; PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); setHP(newhp, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } @@ -491,6 +489,8 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) // Update properties on death if ((hp == 0) != (oldhp == 0)) m_properties_sent = false; + + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } void PlayerSAO::setBreath(const u16 breath, bool send) diff --git a/src/server/serverinventorymgr.cpp b/src/server/serverinventorymgr.cpp index 2a80c9bbe..3aee003b4 100644 --- a/src/server/serverinventorymgr.cpp +++ b/src/server/serverinventorymgr.cpp @@ -157,8 +157,8 @@ bool ServerInventoryManager::removeDetachedInventory(const std::string &name) m_env->getGameDef()->sendDetachedInventory( nullptr, name, player->getPeerId()); - } else { - // Notify all players about the change + } else if (m_env) { + // Notify all players about the change as soon ServerEnv exists m_env->getGameDef()->sendDetachedInventory( nullptr, name, PEER_ID_INEXISTENT); } diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index fa6c8f0f4..acbdd478a 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -124,6 +124,19 @@ void UnitSAO::sendOutdatedData() void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation, bool force_visible) { + auto *obj = parent_id ? m_env->getActiveObject(parent_id) : nullptr; + if (obj) { + // Do checks to avoid circular references + // The chain of wanted parent must not refer or contain "this" + for (obj = obj->getParent(); obj; obj = obj->getParent()) { + if (obj == this) { + warningstream << "Mod bug: Attempted to attach object " << m_id << " to parent " + << parent_id << " but former is an (in)direct parent of latter." << std::endl; + return; + } + } + } + // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. // Attachments are still sent to clients at an interval so players might see them diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index cd5ac0753..b8ba25235 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -729,6 +729,8 @@ struct ActiveABM int chance; std::vector<content_t> required_neighbors; bool check_required_neighbors; // false if required_neighbors is known to be empty + s16 min_y; + s16 max_y; }; class ABMHandler @@ -773,6 +775,9 @@ public: } else { aabm.chance = chance; } + // y limits + aabm.min_y = abm->getMinY(); + aabm.max_y = abm->getMaxY(); // Trigger neighbors const std::vector<std::string> &required_neighbors_s = @@ -885,6 +890,9 @@ public: v3s16 p = p0 + block->getPosRelative(); for (ActiveABM &aabm : *m_aabms[c]) { + if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y)) + continue; + if (myrand() % aabm.chance != 0) continue; @@ -1542,6 +1550,21 @@ void ServerEnvironment::step(float dtime) m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true); } +ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos) +{ + if (m_active_blocks.contains(blockpos)) + return BS_ACTIVE; + + const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos); + if (block && !block->isDummy()) + return BS_LOADED; + + if (m_map->isBlockInQueue(blockpos)) + return BS_EMERGING; + + return BS_UNKNOWN; +} + u32 ServerEnvironment::addParticleSpawner(float exptime) { // Timers with lifetime 0 do not expire diff --git a/src/serverenvironment.h b/src/serverenvironment.h index a11c814ed..8733c2dd2 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -67,6 +67,10 @@ public: virtual u32 getTriggerChance() = 0; // Whether to modify chance to simulate time lost by an unnattended block virtual bool getSimpleCatchUp() = 0; + // get min Y for apply abm + virtual s16 getMinY() = 0; + // get max Y for apply abm + virtual s16 getMaxY() = 0; // This is called usually at interval for 1/chance of the nodes virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, @@ -342,7 +346,16 @@ public: void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; } float getMaxLagEstimate() { return m_max_lag_estimate; } - std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }; + std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; } + + // Sorted by how ready a mapblock is + enum BlockStatus { + BS_UNKNOWN, + BS_EMERGING, + BS_LOADED, + BS_ACTIVE // always highest value + }; + BlockStatus getBlockStatus(v3s16 blockpos); // Sets the static object status all the active objects in the specified block // This is only really needed for deleting blocks from the map diff --git a/src/settings.cpp b/src/settings.cpp index cff393e5f..f4de5bec9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -33,35 +33,90 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cctype> #include <algorithm> -Settings *g_settings = nullptr; // Populated in main() +Settings *g_settings = nullptr; +static SettingsHierarchy g_hierarchy; std::string g_settings_path; -Settings *Settings::s_layers[SL_TOTAL_COUNT] = {0}; // Zeroed by compiler std::unordered_map<std::string, const FlagDesc *> Settings::s_flags; +/* Settings hierarchy implementation */ -Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) +SettingsHierarchy::SettingsHierarchy(Settings *fallback) +{ + layers.push_back(fallback); +} + + +Settings *SettingsHierarchy::getLayer(int layer) const { - if ((int)sl < 0 || sl >= SL_TOTAL_COUNT) + if (layer < 0 || layer >= (int)layers.size()) throw BaseException("Invalid settings layer"); + return layers[layer]; +} - Settings *&pos = s_layers[(size_t)sl]; + +Settings *SettingsHierarchy::getParent(int layer) const +{ + assert(layer >= 0 && layer < (int)layers.size()); + // iterate towards the origin (0) to find the next fallback layer + for (int i = layer - 1; i >= 0; --i) { + if (layers[i]) + return layers[i]; + } + + return nullptr; +} + + +void SettingsHierarchy::onLayerCreated(int layer, Settings *obj) +{ + if (layer < 0) + throw BaseException("Invalid settings layer"); + if ((int)layers.size() < layer + 1) + layers.resize(layer + 1); + + Settings *&pos = layers[layer]; if (pos) - throw BaseException("Setting layer " + std::to_string(sl) + " already exists"); + throw BaseException("Setting layer " + itos(layer) + " already exists"); + + pos = obj; + // This feels bad + if (this == &g_hierarchy && layer == (int)SL_GLOBAL) + g_settings = obj; +} + + +void SettingsHierarchy::onLayerRemoved(int layer) +{ + assert(layer >= 0 && layer < layers.size()); + layers[layer] = nullptr; + if (this == &g_hierarchy && layer == (int)SL_GLOBAL) + g_settings = nullptr; +} - pos = new Settings(end_tag); - pos->m_settingslayer = sl; +/* Settings implementation */ - if (sl == SL_GLOBAL) - g_settings = pos; - return pos; +Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) +{ + return new Settings(end_tag, &g_hierarchy, (int)sl); } Settings *Settings::getLayer(SettingsLayer sl) { sanity_check((int)sl >= 0 && sl < SL_TOTAL_COUNT); - return s_layers[(size_t)sl]; + return g_hierarchy.layers[(int)sl]; +} + + +Settings::Settings(const std::string &end_tag, SettingsHierarchy *h, + int settings_layer) : + m_end_tag(end_tag), + m_hierarchy(h), + m_settingslayer(settings_layer) +{ + if (m_hierarchy) + m_hierarchy->onLayerCreated(m_settingslayer, this); } @@ -69,12 +124,8 @@ Settings::~Settings() { MutexAutoLock lock(m_mutex); - if (m_settingslayer < SL_TOTAL_COUNT) - s_layers[(size_t)m_settingslayer] = nullptr; - - // Compatibility - if (m_settingslayer == SL_GLOBAL) - g_settings = nullptr; + if (m_hierarchy) + m_hierarchy->onLayerRemoved(m_settingslayer); clearNoLock(); } @@ -86,8 +137,8 @@ Settings & Settings::operator = (const Settings &other) return *this; // TODO: Avoid copying Settings objects. Make this private. - FATAL_ERROR_IF(m_settingslayer != SL_TOTAL_COUNT && other.m_settingslayer != SL_TOTAL_COUNT, - ("Tried to copy unique Setting layer " + std::to_string(m_settingslayer)).c_str()); + FATAL_ERROR_IF(m_hierarchy || other.m_hierarchy, + "Cannot copy or overwrite Settings object that belongs to a hierarchy"); MutexAutoLock lock(m_mutex); MutexAutoLock lock2(other.m_mutex); @@ -410,18 +461,7 @@ bool Settings::parseCommandLine(int argc, char *argv[], Settings *Settings::getParent() const { - // If the Settings object is within the hierarchy structure, - // iterate towards the origin (0) to find the next fallback layer - if (m_settingslayer >= SL_TOTAL_COUNT) - return nullptr; - - for (int i = (int)m_settingslayer - 1; i >= 0; --i) { - if (s_layers[i]) - return s_layers[i]; - } - - // No parent - return nullptr; + return m_hierarchy ? m_hierarchy->getParent(m_settingslayer) : nullptr; } @@ -497,11 +537,8 @@ float Settings::getFloat(const std::string &name) const u64 Settings::getU64(const std::string &name) const { - u64 value = 0; std::string s = get(name); - std::istringstream ss(s); - ss >> value; - return value; + return from_string<u64>(s); } @@ -715,6 +752,15 @@ bool Settings::getS16NoEx(const std::string &name, s16 &val) const } } +bool Settings::getU32NoEx(const std::string &name, u32 &val) const +{ + try { + val = getU32(name); + return true; + } catch (SettingNotFoundException &e) { + return false; + } +} bool Settings::getS32NoEx(const std::string &name, s32 &val) const { @@ -823,6 +869,8 @@ bool Settings::set(const std::string &name, const std::string &value) // TODO: Remove this function bool Settings::setDefault(const std::string &name, const std::string &value) { + FATAL_ERROR_IF(m_hierarchy != &g_hierarchy, "setDefault is only valid on " + "global settings"); return getLayer(SL_DEFAULTS)->set(name, value); } diff --git a/src/settings.h b/src/settings.h index e22d949d3..4e32a3488 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "util/string.h" +#include "util/basic_macros.h" #include <string> #include <list> #include <set> @@ -60,14 +61,36 @@ enum SettingsParseEvent { SPE_MULTILINE, }; +// Describes the global setting layers, SL_GLOBAL is where settings are read from enum SettingsLayer { SL_DEFAULTS, SL_GAME, SL_GLOBAL, - SL_MAP, SL_TOTAL_COUNT }; +// Implements the hierarchy a settings object may be part of +class SettingsHierarchy { +public: + /* + * A settings object that may be part of another hierarchy can + * occupy the index 0 as a fallback. If not set you can use 0 on your own. + */ + SettingsHierarchy(Settings *fallback = nullptr); + + DISABLE_CLASS_COPY(SettingsHierarchy) + + Settings *getLayer(int layer) const; + +private: + friend class Settings; + Settings *getParent(int layer) const; + void onLayerCreated(int layer, Settings *obj); + void onLayerRemoved(int layer); + + std::vector<Settings*> layers; +}; + struct ValueSpec { ValueSpec(ValueType a_type, const char *a_help=NULL) { @@ -100,13 +123,15 @@ typedef std::unordered_map<std::string, SettingsEntry> SettingEntries; class Settings { public: + /* These functions operate on the global hierarchy! */ static Settings *createLayer(SettingsLayer sl, const std::string &end_tag = ""); static Settings *getLayer(SettingsLayer sl); - SettingsLayer getLayerType() const { return m_settingslayer; } + /**/ Settings(const std::string &end_tag = "") : m_end_tag(end_tag) {} + Settings(const std::string &end_tag, SettingsHierarchy *h, int settings_layer); ~Settings(); Settings & operator += (const Settings &other); @@ -161,6 +186,7 @@ public: bool getFlag(const std::string &name) const; bool getU16NoEx(const std::string &name, u16 &val) const; bool getS16NoEx(const std::string &name, s16 &val) const; + bool getU32NoEx(const std::string &name, u32 &val) const; bool getS32NoEx(const std::string &name, s32 &val) const; bool getU64NoEx(const std::string &name, u64 &val) const; bool getFloatNoEx(const std::string &name, float &val) const; @@ -200,9 +226,9 @@ public: // remove a setting bool remove(const std::string &name); - /************** - * Miscellany * - **************/ + /***************** + * Miscellaneous * + *****************/ void setDefault(const std::string &name, const FlagDesc *flagdesc, u32 flags); const FlagDesc *getFlagDescFallback(const std::string &name) const; @@ -214,6 +240,10 @@ public: void removeSecureSettings(); + // Returns the settings layer this object is. + // If within the global hierarchy you can cast this to enum SettingsLayer + inline int getLayer() const { return m_settingslayer; } + private: /*********************** * Reading and writing * @@ -257,7 +287,8 @@ private: // All methods that access m_settings/m_defaults directly should lock this. mutable std::mutex m_mutex; - static Settings *s_layers[SL_TOTAL_COUNT]; - SettingsLayer m_settingslayer = SL_TOTAL_COUNT; + SettingsHierarchy *m_hierarchy = nullptr; + int m_settingslayer = -1; + static std::unordered_map<std::string, const FlagDesc *> s_flags; }; diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index 317186e94..49591d1ee 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -11,7 +11,7 @@ fake_function() { 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 \"special\" key).\nThis requires the \"fast\" privilege on the server."); + 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"); @@ -24,12 +24,12 @@ fake_function() { gettext("Invert vertical mouse movement."); gettext("Mouse sensitivity"); gettext("Mouse sensitivity multiplier."); - gettext("Special key for climbing/descending"); - gettext("If enabled, \"special\" key instead of \"sneak\" key is used for climbing down and\ndescending."); + 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("If disabled, \"special\" key is used to fly fast if both fly and fast mode are\nenabled."); + 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."); gettext("Automatic jumping"); @@ -44,8 +44,8 @@ fake_function() { 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 aux button"); - gettext("(Android) Use virtual joystick to trigger \"aux\" button.\nIf enabled, virtual joystick will also tap \"aux\" button when out of main circle."); + 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"); @@ -76,7 +76,7 @@ fake_function() { 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("Special key"); + 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"); @@ -233,9 +233,9 @@ fake_function() { gettext("Trilinear filtering"); gettext("Use trilinear filtering when scaling textures."); gettext("Clean transparent textures"); - gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, sometimes resulting in a dark or\nlight edge to transparent textures. Apply this filter to clean that up\nat texture load time."); + gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, often resulting in dark or\nlight edges to transparent textures. Apply a filter to clean that up\nat texture load time. This is automatically enabled if mipmapping is enabled."); gettext("Minimum texture size"); - 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. Setting this higher than 1 may not\nhave a visible effect unless bilinear/trilinear/anisotropic filtering is\nenabled.\nThis is also used as the base node texture size for world-aligned\ntexture autoscaling."); + 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 applies 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"); @@ -261,6 +261,29 @@ fake_function() { 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 map max distance in nodes to render shadows"); + gettext("Maximum distance to render shadows."); + gettext("Shadow map texture size"); + gettext("Texture size to render the shadow map on.\nThis must be a power of two.\nBigger numbers create better shadowsbut it is also more expensive."); + gettext("Shadow map texture in 32 bits"); + gettext("Sets shadow texture quality to 32 bits.\nOn false, 16 bits texture will be used.\nThis can cause much more artifacts in the shadow."); + gettext("Poisson filtering"); + gettext("Enable poisson disk filtering.\nOn true uses poisson disk to make \"soft shadows\". Otherwise uses PCF filtering."); + gettext("Shadow filter quality"); + gettext("Define shadow filtering quality\nThis simulates the soft shadows effect by applying a PCF or poisson disk\nbut also uses more resources."); + gettext("Colored shadows"); + gettext("Enable colored shadows. \nOn true translucent nodes cast colored shadows. This is expensive."); + gettext("Map update time"); + gettext("Set the shadow update time.\nLower value means shadows and map updates faster, but it consume more resources.\nMinimun value 0.001 seconds max value 0.2 seconds"); + gettext("Soft shadow radius"); + gettext("Set the soft shadow radius size.\nLower values mean sharper shadows bigger values softer.\nMinimun value 1.0 and max value 10.0"); + gettext("Sky Body Orbit Tilt"); + gettext("Set the tilt of Sun/Moon orbit in degrees\nValue of 0 means no tilt / vertical orbit.\nMinimun value 0.0 and max value 60.0"); gettext("Advanced"); gettext("Arm inertia"); gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); @@ -275,15 +298,13 @@ fake_function() { 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."); + gettext("Width component of the initial window size. Ignored in fullscreen mode."); gettext("Screen height"); - gettext("Height component of the initial window size."); + 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("Full screen BPP"); - gettext("Bits per pixel (aka color depth) in fullscreen mode."); gettext("VSync"); gettext("Vertical screen synchronization."); gettext("Field of view"); @@ -303,7 +324,7 @@ fake_function() { gettext("Texture path"); gettext("Path to texture directory. All textures are first searched from here."); gettext("Video driver"); - gettext("The rendering back-end for Irrlicht.\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("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"); @@ -407,12 +428,6 @@ fake_function() { gettext("Bold monospace font path"); gettext("Italic monospace font path"); gettext("Bold and italic monospace font path"); - gettext("Fallback font size"); - gettext("Font size of the fallback font in point (pt)."); - gettext("Fallback font shadow"); - gettext("Shadow offset (in pixels) of the fallback font. If 0, then shadow will not be drawn."); - gettext("Fallback font shadow alpha"); - gettext("Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255."); gettext("Fallback font path"); gettext("Path of the fallback font.\nIf “freetype” setting is enabled: Must be a TrueType font.\nIf “freetype” setting is disabled: Must be a bitmap or XML vectors font.\nThis font will be used for certain languages or if the default font is unavailable."); gettext("Chat font size"); @@ -542,6 +557,8 @@ fake_function() { 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"); @@ -679,14 +696,12 @@ fake_function() { gettext("IPv6"); gettext("Enable IPv6 support (for both client and server).\nRequired for IPv6 connections to work at all."); gettext("Advanced"); - gettext("cURL timeout"); - gettext("Default timeout for cURL, stated in milliseconds.\nOnly has an effect if compiled with 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 in ms a file download (e.g. a mod download) may take."); - gettext("High-precision FPU"); - gettext("Makes DirectX work with LuaJIT. Disable if it causes troubles."); + 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"); diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 86e455b9f..1160ec68f 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -37,6 +37,7 @@ void StaticObject::serialize(std::ostream &os) // data os<<serializeString16(data); } + void StaticObject::deSerialize(std::istream &is, u8 version) { // type @@ -49,6 +50,29 @@ void StaticObject::deSerialize(std::istream &is, u8 version) void StaticObjectList::serialize(std::ostream &os) { + // Check for problems first + auto problematic = [] (StaticObject &obj) -> bool { + if (obj.data.size() > U16_MAX) { + errorstream << "StaticObjectList::serialize(): " + "object has excessive static data (" << obj.data.size() << + "), deleting it." << std::endl; + return true; + } + return false; + }; + for (auto it = m_stored.begin(); it != m_stored.end(); ) { + if (problematic(*it)) + it = m_stored.erase(it); + else + it++; + } + for (auto it = m_active.begin(); it != m_active.end(); ) { + if (problematic(it->second)) + it = m_active.erase(it); + else + it++; + } + // version u8 version = 0; writeU8(os, version); diff --git a/src/translation.cpp b/src/translation.cpp index 55c958fa2..1e43b0894 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -64,7 +64,13 @@ void Translations::loadTranslation(const std::string &data) line.resize(line.length() - 1); if (str_starts_with(line, "# textdomain:")) { - textdomain = utf8_to_wide(trim(str_split(line, ':')[1])); + auto parts = str_split(line, ':'); + if (parts.size() < 2) { + errorstream << "Invalid textdomain translation line \"" << line + << "\"" << std::endl; + continue; + } + textdomain = utf8_to_wide(trim(parts[1])); } if (line.empty() || line[0] == '#') continue; diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp index 691cd69d2..2af3ca90c 100644 --- a/src/unittest/test_areastore.cpp +++ b/src/unittest/test_areastore.cpp @@ -135,7 +135,7 @@ void TestAreaStore::testSerialization() b.data = "Area BB"; store.insertArea(&b); - std::ostringstream os; + std::ostringstream os(std::ios_base::binary); store.serialize(os); std::string str = os.str(); @@ -157,7 +157,7 @@ void TestAreaStore::testSerialization() UASSERTEQ(const std::string &, str, str_wanted); - std::istringstream is(str); + std::istringstream is(str, std::ios_base::binary); store.deserialize(is); // deserialize() doesn't clear the store diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp index dfcadd4b2..a96282f58 100644 --- a/src/unittest/test_compression.cpp +++ b/src/unittest/test_compression.cpp @@ -37,6 +37,7 @@ public: void testRLECompression(); void testZlibCompression(); void testZlibLargeData(); + void testZstdLargeData(); void testZlibLimit(); void _testZlibLimit(u32 size, u32 limit); }; @@ -48,6 +49,7 @@ void TestCompression::runTests(IGameDef *gamedef) TEST(testRLECompression); TEST(testZlibCompression); TEST(testZlibLargeData); + TEST(testZstdLargeData); TEST(testZlibLimit); } @@ -111,7 +113,7 @@ void TestCompression::testZlibCompression() fromdata[3]=1; std::ostringstream os(std::ios_base::binary); - compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); + compressZlib(*fromdata, fromdata.getSize(), os); std::string str_out = os.str(); @@ -124,7 +126,7 @@ void TestCompression::testZlibCompression() std::istringstream is(str_out, std::ios_base::binary); std::ostringstream os2(std::ios_base::binary); - decompress(is, os2, SER_FMT_VER_HIGHEST_READ); + decompressZlib(is, os2); std::string str_out2 = os2.str(); infostream << "decompress: "; @@ -174,6 +176,42 @@ void TestCompression::testZlibLargeData() } } +void TestCompression::testZstdLargeData() +{ + infostream << "Test: Testing zstd wrappers with a large amount " + "of pseudorandom data" << std::endl; + + u32 size = 500000; + infostream << "Test: Input size of large compressZstd is " + << size << std::endl; + + std::string data_in; + data_in.resize(size); + PseudoRandom pseudorandom(9420); + for (u32 i = 0; i < size; i++) + data_in[i] = pseudorandom.range(0, 255); + + std::ostringstream os_compressed(std::ios::binary); + compressZstd(data_in, os_compressed, 0); + infostream << "Test: Output size of large compressZstd is " + << os_compressed.str().size()<<std::endl; + + std::istringstream is_compressed(os_compressed.str(), std::ios::binary); + std::ostringstream os_decompressed(std::ios::binary); + decompressZstd(is_compressed, os_decompressed); + infostream << "Test: Output size of large decompressZstd is " + << os_decompressed.str().size() << std::endl; + + std::string str_decompressed = os_decompressed.str(); + UASSERTEQ(size_t, str_decompressed.size(), data_in.size()); + + for (u32 i = 0; i < size && i < str_decompressed.size(); i++) { + UTEST(str_decompressed[i] == data_in[i], + "index out[%i]=%i differs from in[%i]=%i", + i, str_decompressed[i], i, data_in[i]); + } +} + void TestCompression::testZlibLimit() { // edge cases diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index c3aacc536..23b7e9105 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -88,7 +88,7 @@ void TestConnection::testNetworkPacketSerialize() }; if (sizeof(wchar_t) == 2) - warningstream << __func__ << " may fail on this platform." << std::endl; + warningstream << __FUNCTION__ << " may fail on this platform." << std::endl; { NetworkPacket pkt(123, 0); @@ -96,7 +96,7 @@ void TestConnection::testNetworkPacketSerialize() // serializing wide strings should do surrogate encoding, we test that here pkt << std::wstring(L"\U00020b9a"); - SharedBuffer<u8> buf = pkt.oldForgePacket(); + auto buf = pkt.oldForgePacket(); UASSERTEQ(int, buf.getSize(), sizeof(expected)); UASSERT(!memcmp(expected, &buf[0], buf.getSize())); } @@ -280,7 +280,7 @@ void TestConnection::testConnectSendReceive() NetworkPacket pkt; pkt.putRawPacket((u8*) "Hello World !", 14, 0); - SharedBuffer<u8> sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); infostream<<"** running client.Send()"<<std::endl; client.Send(PEER_ID_SERVER, 0, &pkt, true); @@ -295,7 +295,7 @@ void TestConnection::testConnectSendReceive() << ", data=" << (const char*)pkt.getU8Ptr(0) << std::endl; - SharedBuffer<u8> recvdata = pkt.oldForgePacket(); + auto recvdata = pkt.oldForgePacket(); UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0); } @@ -324,13 +324,13 @@ void TestConnection::testConnectSendReceive() infostream << "..."; infostream << std::endl; - SharedBuffer<u8> sentdata = pkt.oldForgePacket(); + auto sentdata = pkt.oldForgePacket(); server.Send(peer_id_client, 0, &pkt, true); //sleep_ms(3000); - SharedBuffer<u8> recvdata; + Buffer<u8> recvdata; infostream << "** running client.Receive()" << std::endl; session_t peer_id = 132; u16 size = 0; diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp index aa857ff46..3484f1514 100644 --- a/src/unittest/test_irrptr.cpp +++ b/src/unittest/test_irrptr.cpp @@ -91,6 +91,12 @@ void TestIrrPtr::testRefCounting() obj->getReferenceCount()); } +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #pragma GCC diagnostic ignored "-Wself-move" +#endif + void TestIrrPtr::testSelfAssignment() { irr_ptr<IReferenceCounted> p1{new IReferenceCounted()}; @@ -129,3 +135,7 @@ void TestIrrPtr::testNullHandling() UASSERT(!p2); UASSERT(!p3); } + +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 81ca68705..17c31fe79 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -148,6 +148,11 @@ void TestMapSettingsManager::testMapSettingsManager() check_noise_params(&dummy, &script_np_factor); } + // The settings manager MUST leave user settings alone + mgr.setMapSetting("testname", "1"); + mgr.setMapSetting("testname", "1", true); + UASSERT(!Settings::getLayer(SL_GLOBAL)->exists("testname")); + // Now make our Params and see if the values are correctly sourced MapgenParams *params = mgr.makeMapgenParams(); UASSERT(params->mgtype == MAPGEN_V5); diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 93ba3f844..039110d54 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/enriched_string.h" #include "util/numeric.h" #include "util/string.h" +#include "util/base64.h" class TestUtilities : public TestBase { public: @@ -56,6 +57,7 @@ public: void testMyround(); void testStringJoin(); void testEulerConversion(); + void testBase64(); }; static TestUtilities g_test_instance; @@ -87,6 +89,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testMyround); TEST(testStringJoin); TEST(testEulerConversion); + TEST(testBase64); } //////////////////////////////////////////////////////////////////////////////// @@ -537,3 +540,93 @@ void TestUtilities::testEulerConversion() setPitchYawRoll(m2, v2); UASSERT(within(m1, m2, tolL)); } + +void TestUtilities::testBase64() +{ + // Test character set + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/") == true); + UASSERT(base64_is_valid("/+9876543210" + "zyxwvutsrqponmlkjihgfedcba" + "ZYXWVUTSRQPONMLKJIHGFEDCBA") == true); + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+.") == false); + UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789 /") == false); + + // Test empty string + UASSERT(base64_is_valid("") == true); + + // Test different lengths, with and without padding, + // with correct and incorrect padding + UASSERT(base64_is_valid("A") == false); + UASSERT(base64_is_valid("AA") == true); + UASSERT(base64_is_valid("AAA") == true); + UASSERT(base64_is_valid("AAAA") == true); + UASSERT(base64_is_valid("AAAAA") == false); + UASSERT(base64_is_valid("AAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAAA") == true); + UASSERT(base64_is_valid("A===") == false); + UASSERT(base64_is_valid("AA==") == true); + UASSERT(base64_is_valid("AAA=") == true); + UASSERT(base64_is_valid("AAAA") == true); + UASSERT(base64_is_valid("AAAA====") == false); + UASSERT(base64_is_valid("AAAAA===") == false); + UASSERT(base64_is_valid("AAAAAA==") == true); + UASSERT(base64_is_valid("AAAAAAA=") == true); + UASSERT(base64_is_valid("AAAAAAA==") == false); + UASSERT(base64_is_valid("AAAAAAA===") == false); + UASSERT(base64_is_valid("AAAAAAA====") == false); + UASSERT(base64_is_valid("AAAAAAAA") == true); + UASSERT(base64_is_valid("AAAAAAAA=") == false); + UASSERT(base64_is_valid("AAAAAAAA==") == false); + UASSERT(base64_is_valid("AAAAAAAA===") == false); + UASSERT(base64_is_valid("AAAAAAAA====") == false); + + // Test if canonical encoding + // Last character limitations, length % 4 == 3 + UASSERT(base64_is_valid("AAB") == false); + UASSERT(base64_is_valid("AAE") == true); + UASSERT(base64_is_valid("AAQ") == true); + UASSERT(base64_is_valid("AAB=") == false); + UASSERT(base64_is_valid("AAE=") == true); + UASSERT(base64_is_valid("AAQ=") == true); + UASSERT(base64_is_valid("AAAAAAB=") == false); + UASSERT(base64_is_valid("AAAAAAE=") == true); + UASSERT(base64_is_valid("AAAAAAQ=") == true); + // Last character limitations, length % 4 == 2 + UASSERT(base64_is_valid("AB") == false); + UASSERT(base64_is_valid("AE") == false); + UASSERT(base64_is_valid("AQ") == true); + UASSERT(base64_is_valid("AB==") == false); + UASSERT(base64_is_valid("AE==") == false); + UASSERT(base64_is_valid("AQ==") == true); + UASSERT(base64_is_valid("AAAAAB==") == false); + UASSERT(base64_is_valid("AAAAAE==") == false); + UASSERT(base64_is_valid("AAAAAQ==") == true); + + // Extraneous character present + UASSERT(base64_is_valid(".") == false); + UASSERT(base64_is_valid("A.") == false); + UASSERT(base64_is_valid("AA.") == false); + UASSERT(base64_is_valid("AAA.") == false); + UASSERT(base64_is_valid("AAAA.") == false); + UASSERT(base64_is_valid("AAAAA.") == false); + UASSERT(base64_is_valid("A.A") == false); + UASSERT(base64_is_valid("AA.A") == false); + UASSERT(base64_is_valid("AAA.A") == false); + UASSERT(base64_is_valid("AAAA.A") == false); + UASSERT(base64_is_valid("AAAAA.A") == false); + UASSERT(base64_is_valid("\xE1""AAA") == false); + + // Padding in wrong position + UASSERT(base64_is_valid("A=A") == false); + UASSERT(base64_is_valid("AA=A") == false); + UASSERT(base64_is_valid("AAA=A") == false); + UASSERT(base64_is_valid("AAAA=A") == false); + UASSERT(base64_is_valid("AAAAA=A") == false); +}
\ No newline at end of file diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index cd2e468d1..6bc97915f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -15,4 +15,5 @@ set(UTIL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/png.cpp PARENT_SCOPE) diff --git a/src/util/base64.cpp b/src/util/base64.cpp index 6e1584410..0c2455222 100644 --- a/src/util/base64.cpp +++ b/src/util/base64.cpp @@ -33,18 +33,39 @@ static const std::string base64_chars = "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; +static const std::string base64_chars_padding_1 = "AEIMQUYcgkosw048"; +static const std::string base64_chars_padding_2 = "AQgw"; static inline bool is_base64(unsigned char c) { - return isalnum(c) || c == '+' || c == '/' || c == '='; + return (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || c == '+' || c == '/'; } bool base64_is_valid(std::string const& s) { - for (char i : s) - if (!is_base64(i)) + size_t i = 0; + for (; i < s.size(); ++i) + if (!is_base64(s[i])) + break; + unsigned char padding = 3 - ((i + 3) % 4); + if ((padding == 1 && base64_chars_padding_1.find(s[i - 1]) == std::string::npos) + || (padding == 2 && base64_chars_padding_2.find(s[i - 1]) == std::string::npos) + || padding == 3) + return false; + int actual_padding = s.size() - i; + // omission of padding characters is allowed + if (actual_padding == 0) + return true; + + // remaining characters (max. 2) may only be padding + for (; i < s.size(); ++i) + if (s[i] != '=') return false; - return true; + // number of padding characters needs to match + return padding == actual_padding; } std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { diff --git a/src/util/container.h b/src/util/container.h index 1c4a219f0..001066563 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -140,6 +140,13 @@ public: m_signal.post(); } + void push_back(T &&t) + { + MutexAutoLock lock(m_mutex); + m_queue.push_back(std::move(t)); + m_signal.post(); + } + /* this version of pop_front returns a empty element of T on timeout. * Make sure default constructor of T creates a recognizable "empty" element */ diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 99e4cfb5c..702ddce95 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -159,7 +159,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, return true; } -s16 adjustDist(s16 dist, float zoom_fov) +inline float adjustDist(float dist, float zoom_fov) { // 1.775 ~= 72 * PI / 180 * 1.4, the default FOV on the client. // The heuristic threshold for zooming is half of that. @@ -167,8 +167,13 @@ s16 adjustDist(s16 dist, float zoom_fov) if (zoom_fov < 0.001f || zoom_fov > threshold_fov) return dist; - return std::round(dist * std::cbrt((1.0f - std::cos(threshold_fov)) / - (1.0f - std::cos(zoom_fov / 2.0f)))); + return dist * std::cbrt((1.0f - std::cos(threshold_fov)) / + (1.0f - std::cos(zoom_fov / 2.0f))); +} + +s16 adjustDist(s16 dist, float zoom_fov) +{ + return std::round(adjustDist((float)dist, zoom_fov)); } void setPitchYawRollRad(core::matrix4 &m, const v3f &rot) diff --git a/src/util/png.cpp b/src/util/png.cpp new file mode 100755 index 000000000..7ac2e94a1 --- /dev/null +++ b/src/util/png.cpp @@ -0,0 +1,68 @@ +/* +Minetest +Copyright (C) 2021 hecks + +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 "png.h" +#include <string> +#include <sstream> +#include <zlib.h> +#include <cassert> +#include "util/serialize.h" +#include "serialization.h" +#include "irrlichttypes.h" + +static void writeChunk(std::ostringstream &target, const std::string &chunk_str) +{ + assert(chunk_str.size() >= 4); + assert(chunk_str.size() - 4 < U32_MAX); + writeU32(target, chunk_str.size() - 4); // Write length minus the identifier + target << chunk_str; + writeU32(target, crc32(0,(const u8*)chunk_str.data(), chunk_str.size())); +} + +std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression) +{ + auto file = std::ostringstream(std::ios::binary); + file << "\x89PNG\r\n\x1a\n"; + + { + auto IHDR = std::ostringstream(std::ios::binary); + IHDR << "IHDR"; + writeU32(IHDR, width); + writeU32(IHDR, height); + // 8 bpp, color type 6 (RGBA) + IHDR.write("\x08\x06\x00\x00\x00", 5); + writeChunk(file, IHDR.str()); + } + + { + auto IDAT = std::ostringstream(std::ios::binary); + IDAT << "IDAT"; + auto scanlines = std::ostringstream(std::ios::binary); + for(u32 i = 0; i < height; i++) { + scanlines.write("\x00", 1); // Null predictor + scanlines.write((const char*) data + width * 4 * i, width * 4); + } + compressZlib(scanlines.str(), IDAT, compression); + writeChunk(file, IDAT.str()); + } + + file.write("\x00\x00\x00\x00IEND\xae\x42\x60\x82", 12); + + return file.str(); +} diff --git a/src/util/png.h b/src/util/png.h new file mode 100755 index 000000000..92387aef0 --- /dev/null +++ b/src/util/png.h @@ -0,0 +1,27 @@ +/* +Minetest +Copyright (C) 2021 hecks + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include <string> +#include "irrlichttypes.h" + +/* Simple PNG encoder. Encodes an RGBA image with no predictors. + Returns a binary string. */ +std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression); diff --git a/src/util/pointer.h b/src/util/pointer.h index d29ec8739..7fc5de551 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -51,6 +51,19 @@ public: else data = NULL; } + Buffer(Buffer &&buffer) + { + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + } + // Copies whole buffer Buffer(const T *t, unsigned int size) { m_size = size; @@ -62,10 +75,12 @@ public: else data = NULL; } + ~Buffer() { drop(); } + Buffer& operator=(const Buffer &buffer) { if(this == &buffer) @@ -81,6 +96,23 @@ public: data = NULL; return *this; } + Buffer& operator=(Buffer &&buffer) + { + if(this == &buffer) + return *this; + drop(); + m_size = buffer.m_size; + if(m_size != 0) + { + data = buffer.data; + buffer.data = nullptr; + buffer.m_size = 0; + } + else + data = nullptr; + return *this; + } + T & operator[](unsigned int i) const { return data[i]; @@ -89,10 +121,12 @@ public: { return data; } + unsigned int getSize() const { return m_size; } + private: void drop() { diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index d770101f2..281061229 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -248,7 +248,7 @@ std::string serializeJsonStringIfNeeded(const std::string &s) std::string deSerializeJsonStringIfNeeded(std::istream &is) { - std::ostringstream tmp_os; + 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; @@ -280,8 +280,7 @@ std::string deSerializeJsonStringIfNeeded(std::istream &is) expect_initial_quote = false; } if (is_json) { - std::istringstream tmp_is(tmp_os.str(), std::ios::binary); - return deSerializeJsonString(tmp_is); + return deSerializeJsonString(tmp_os); } return tmp_os.str(); |