diff options
Diffstat (limited to 'src')
154 files changed, 7947 insertions, 3279 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70a5ab3c8..6afa5b8fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,7 +108,8 @@ if(BUILD_CLIENT) if(ENABLE_GLES) find_package(OpenGLES2 REQUIRED) else() - if(NOT WIN32) # Unix probably + # transitive dependency from Irrlicht (see longer explanation below) + if(NOT WIN32) set(OPENGL_GL_PREFERENCE "LEGACY" CACHE STRING "See CMake Policy CMP0072 for reference. GLVND is broken on some nvidia setups") set(OpenGL_GL_PREFERENCE ${OPENGL_GL_PREFERENCE}) @@ -274,24 +275,35 @@ if(WIN32) set(VORBISFILE_DLL "" CACHE FILEPATH "Path to libvorbisfile.dll for installation (optional)") endif() if(USE_LUAJIT) - set(LUA_DLL "" CACHE FILEPATH "Path to lua51.dll for installation (optional)") + set(LUA_DLL "" CACHE FILEPATH "Path to luajit-5.1.dll for installation (optional)") endif() endif() else() # Unix probably if(BUILD_CLIENT) - if(NOT HAIKU) + if(NOT HAIKU AND NOT APPLE) find_package(X11 REQUIRED) - endif(NOT HAIKU) + endif(NOT HAIKU AND NOT APPLE) + + ## + # 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(BZip2 REQUIRED) find_package(PNG REQUIRED) if(APPLE) - find_library(CARBON_LIB Carbon) - find_library(COCOA_LIB Cocoa) - find_library(IOKIT_LIB IOKit) + find_library(CARBON_LIB Carbon REQUIRED) + find_library(COCOA_LIB Cocoa REQUIRED) + find_library(IOKIT_LIB IOKit REQUIRED) mark_as_advanced( CARBON_LIB COCOA_LIB @@ -299,7 +311,9 @@ else() ) 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) @@ -311,13 +325,6 @@ else() endif(HAVE_LIBRT) endif(APPLE) - 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) - # Prefer local iconv if installed find_library(ICONV_LIBRARY iconv) mark_as_advanced(ICONV_LIBRARY) @@ -489,7 +496,6 @@ include_directories( ${PROJECT_SOURCE_DIR} ${IRRLICHT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} - ${CMAKE_BUILD_TYPE} ${PNG_INCLUDE_DIR} ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} @@ -535,16 +541,9 @@ if(BUILD_CLIENT) ${PLATFORM_LIBS} ${CLIENT_PLATFORM_LIBS} ) - if(APPLE) - target_link_libraries( - ${client_LIBS} - ${ICONV_LIBRARY} - ) - else() - target_link_libraries( - ${client_LIBS} - ) - endif() + target_link_libraries( + ${client_LIBS} + ) if(ENABLE_GLES) target_link_libraries( ${PROJECT_NAME} @@ -646,8 +645,6 @@ set(GETTEXT_BLACKLISTED_LOCALES he ko ky - zh_CN - zh_TW ) option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE) @@ -691,21 +688,20 @@ if(MSVC) # /MD = dynamically link to MSVCRxxx.dll set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MD") else() + # GCC or compatible compilers such as Clang set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - # Probably GCC - if(APPLE) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000" ) - endif() if(WARN_ALL) set(RELEASE_WARNING_FLAGS "-Wall") else() set(RELEASE_WARNING_FLAGS "") endif() - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # clang does not understand __extern_always_inline but libc headers use it - set(OTHER_FLAGS "${OTHER_FLAGS} \"-D__extern_always_inline=extern __always_inline\"") - set(OTHER_FLAGS "${OTHER_FLAGS} -Wsign-compare") + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(WARNING_FLAGS "${WARNING_FLAGS} -Wsign-compare") + endif() + if(APPLE AND USE_LUAJIT) + # required per http://luajit.org/install.html + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000") endif() if(MINGW) @@ -717,7 +713,14 @@ else() if(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os") else() - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -ffast-math -fomit-frame-pointer") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fomit-frame-pointer") + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_VERSION MATCHES "^9\\.") + # Clang 9 has broken -ffast-math on glibc + else() + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffast-math") + endif() endif(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)") set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}") set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}") @@ -751,7 +754,7 @@ if(WIN32) FILES_MATCHING PATTERN "*.dll") install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/MinSizeRel/ DESTINATION ${BINDIR} - CONFIGURATIONS RelWithDebInfo + CONFIGURATIONS MinSizeRel FILES_MATCHING PATTERN "*.dll") else() # Use the old-style way to install dll's diff --git a/src/activeobject.h b/src/activeobject.h index a319ef904..4a2de92cd 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -27,13 +27,13 @@ with this program; if not, write to the Free Software Foundation, Inc., enum ActiveObjectType { ACTIVEOBJECT_TYPE_INVALID = 0, ACTIVEOBJECT_TYPE_TEST = 1, -// Deprecated stuff +// Obsolete stuff ACTIVEOBJECT_TYPE_ITEM = 2, // ACTIVEOBJECT_TYPE_RAT = 3, // ACTIVEOBJECT_TYPE_OERKKI1 = 4, // ACTIVEOBJECT_TYPE_FIREFLY = 5, ACTIVEOBJECT_TYPE_MOBV2 = 6, -// End deprecated stuff +// End obsolete stuff ACTIVEOBJECT_TYPE_LUAENTITY = 7, // Special type, not stored as a static object ACTIVEOBJECT_TYPE_PLAYER = 100, diff --git a/src/client/camera.cpp b/src/client/camera.cpp index d1e76026d..871ea709d 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "debug.h" #include "client.h" +#include "config.h" #include "map.h" #include "clientmap.h" // MapDrawControl #include "player.h" @@ -39,6 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CAMERA_OFFSET_STEP 200 #define WIELDMESH_OFFSET_X 55.0f #define WIELDMESH_OFFSET_Y -35.0f +#define WIELDMESH_AMPLITUDE_X 7.0f +#define WIELDMESH_AMPLITUDE_Y 10.0f Camera::Camera(MapDrawControl &draw_control, Client *client): m_draw_control(draw_control), @@ -234,7 +237,8 @@ void Camera::addArmInertia(f32 player_yaw) m_last_cam_pos.X = player_yaw; m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X, - WIELDMESH_OFFSET_X - 7.0f, WIELDMESH_OFFSET_X + 7.0f); + WIELDMESH_OFFSET_X - (WIELDMESH_AMPLITUDE_X * 0.5f), + WIELDMESH_OFFSET_X + (WIELDMESH_AMPLITUDE_X * 0.5f)); } if (m_cam_vel.Y > 1.0f) { @@ -249,7 +253,8 @@ void Camera::addArmInertia(f32 player_yaw) m_last_cam_pos.Y = m_camera_direction.Y; m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y, - WIELDMESH_OFFSET_Y - 10.0f, WIELDMESH_OFFSET_Y + 5.0f); + WIELDMESH_OFFSET_Y - (WIELDMESH_AMPLITUDE_Y * 0.5f), + WIELDMESH_OFFSET_Y + (WIELDMESH_AMPLITUDE_Y * 0.5f)); } m_arm_dir = dir(m_wieldmesh_offset); @@ -259,10 +264,10 @@ void Camera::addArmInertia(f32 player_yaw) following a vector, with a smooth deceleration factor. */ - f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f + + f32 dec_X = 0.35f * (std::min(15.0f, m_cam_vel_old.X) * (1.0f + (1.0f - m_arm_dir.X))) * (gap_X / 20.0f); - f32 dec_Y = 0.06f * (m_cam_vel_old.Y * (1.0f + + f32 dec_Y = 0.25f * (std::min(15.0f, m_cam_vel_old.Y) * (1.0f + (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f); if (gap_X < 0.1f) @@ -285,9 +290,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r // Smooth the movement when walking up stairs v3f old_player_position = m_playernode->getPosition(); v3f player_position = player->getPosition(); - if (player->isAttached && player->parent) - player_position = player->parent->getPosition(); - //if(player->touching_ground && player_position.Y > old_player_position.Y) + + // This is worse than `LocalPlayer::getPosition()` but + // mods expect the player head to be at the parent's position + // plus eye height. + if (player->getParent()) + player_position = player->getParent()->getPosition(); + if(player->touching_ground && player_position.Y > old_player_position.Y) { @@ -561,10 +570,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r void Camera::updateViewingRange() { f32 viewing_range = g_settings->getFloat("viewing_range"); - f32 near_plane = g_settings->getFloat("near_plane"); + + // Ignore near_plane setting on all other platforms to prevent abuse +#if ENABLE_GLES + m_cameranode->setNearValue(rangelim( + g_settings->getFloat("near_plane"), 0.0f, 0.25f) * BS); +#else + m_cameranode->setNearValue(0.1f * BS); +#endif m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); - m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS); if (m_draw_control.range_all) { m_cameranode->setFarValue(100000.0); return; @@ -592,7 +607,7 @@ void Camera::wield(const ItemStack &item) void Camera::drawWieldedTool(irr::core::matrix4* translation) { - // Clear Z buffer so that the wielded tool stay in front of world geometry + // Clear Z buffer so that the wielded tool stays in front of world geometry m_wieldmgr->getVideoDriver()->clearZBuffer(); // Draw the wielded node (in a separate scene manager) diff --git a/src/client/client.cpp b/src/client/client.cpp index caa3cc78c..c6d28ce80 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -155,6 +155,7 @@ void Client::loadMods() // complain about mods with unsatisfied dependencies if (!modconf.isConsistent()) { modconf.printUnsatisfiedModsError(); + return; } // Print mods @@ -200,14 +201,30 @@ 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) { - std::string filename = j.name; if (j.dir) { - scanModSubfolder(mod_name, mod_path, mod_subpath - + filename + DIR_DELIM); + scanModSubfolder(mod_name, mod_path, mod_subpath + j.name + DIR_DELIM); continue; } - std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); - m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path + filename; + std::replace(mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/'); + + std::string real_path = full_path + j.name; + std::string vfs_path = mod_name + ":" + mod_subpath + j.name; + infostream << "Client::scanModSubfolder(): Loading \"" << real_path + << "\" as \"" << vfs_path << "\"." << std::endl; + + std::ifstream is(real_path, std::ios::binary | std::ios::ate); + if(!is.good()) { + errorstream << "Client::scanModSubfolder(): Can't read file \"" + << real_path << "\"." << std::endl; + continue; + } + auto size = is.tellg(); + std::string contents(size, '\0'); + is.seekg(0); + is.read(&contents[0], size); + + infostream << " size: " << size << " bytes" << std::endl; + m_mod_vfs.emplace(vfs_path, contents); } } @@ -294,6 +311,7 @@ void Client::connect(Address address, bool is_local_server) { initLocalMapSaving(address, m_address_name, is_local_server); + // Since we use TryReceive() a timeout here would be ineffective anyway m_con->SetTimeoutMs(0); m_con->Connect(address); } @@ -764,11 +782,20 @@ void Client::initLocalMapSaving(const Address &address, return; } - const std::string world_path = porting::path_user - + DIR_DELIM + "worlds" - + DIR_DELIM + "server_" + std::string world_path; +#define set_world_path(hostname) \ + world_path = porting::path_user \ + + DIR_DELIM + "worlds" \ + + DIR_DELIM + "server_" \ + hostname + "_" + std::to_string(address.getPort()); + set_world_path(hostname); + if (!fs::IsDir(world_path)) { + std::string hostname_escaped = hostname; + str_replace(hostname_escaped, ':', '_'); + set_world_path(hostname_escaped); + } +#undef set_world_path fs::CreateAllDirs(world_path); m_localdb = new MapDatabaseSQLite3(world_path); @@ -778,36 +805,31 @@ void Client::initLocalMapSaving(const Address &address, void Client::ReceiveAll() { + NetworkPacket pkt; u64 start_ms = porting::getTimeMs(); - for(;;) - { + const u64 budget = 100; + for(;;) { // Limit time even if there would be huge amounts of data to // process - if(porting::getTimeMs() > start_ms + 100) + if (porting::getTimeMs() > start_ms + budget) { + infostream << "Client::ReceiveAll(): " + "Packet processing budget exceeded." << std::endl; break; + } + pkt.clear(); try { - Receive(); - g_profiler->graphAdd("client_received_packets", 1); - } - catch(con::NoIncomingDataException &e) { - break; - } - catch(con::InvalidIncomingDataException &e) { - infostream<<"Client::ReceiveAll(): " + if (!m_con->TryReceive(&pkt)) + break; + ProcessData(&pkt); + } catch (const con::InvalidIncomingDataException &e) { + infostream << "Client::ReceiveAll(): " "InvalidIncomingDataException: what()=" - <<e.what()<<std::endl; + << e.what() << std::endl; } } } -void Client::Receive() -{ - NetworkPacket pkt; - m_con->Receive(&pkt); - ProcessData(&pkt); -} - inline void Client::handleCommand(NetworkPacket* pkt) { const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; @@ -824,6 +846,7 @@ void Client::ProcessData(NetworkPacket *pkt) //infostream<<"Client: received command="<<command<<std::endl; m_packetcounter.add((u16)command); + g_profiler->graphAdd("client_received_packets", 1); /* If this check is removed, be sure to change the queue @@ -1083,7 +1106,7 @@ void Client::sendRemovedSounds(std::vector<s32> &soundList) pkt << (u16) (server_ids & 0xFFFF); - for (int sound_id : soundList) + for (s32 sound_id : soundList) pkt << sound_id; Send(&pkt); @@ -1296,7 +1319,7 @@ void Client::removeNode(v3s16 p) * @param is_valid_position * @return */ -MapNode Client::getNode(v3s16 p, bool *is_valid_position) +MapNode Client::CSMGetNode(v3s16 p, bool *is_valid_position) { if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); @@ -1308,6 +1331,31 @@ MapNode Client::getNode(v3s16 p, bool *is_valid_position) return m_env.getMap().getNode(p, is_valid_position); } +int Client::CSMClampRadius(v3s16 pos, int radius) +{ + if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) + return radius; + // This is approximate and will cause some allowed nodes to be excluded + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + u32 distance = ppos.getDistanceFrom(pos); + if (distance >= m_csm_restriction_noderange) + return 0; + return std::min<int>(radius, m_csm_restriction_noderange - distance); +} + +v3s16 Client::CSMClampPos(v3s16 pos) +{ + if (!checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) + return pos; + v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS); + const int range = m_csm_restriction_noderange; + return v3s16( + core::clamp<int>(pos.X, (int)ppos.X - range, (int)ppos.X + range), + core::clamp<int>(pos.Y, (int)ppos.Y - range, (int)ppos.Y + range), + core::clamp<int>(pos.Z, (int)ppos.Z - range, (int)ppos.Z + range) + ); +} + void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) { //TimeTaker timer1("Client::addNode()"); @@ -1812,7 +1860,7 @@ ITextureSource* Client::getTextureSource() { return m_tsrc; } -IShaderSource* Client::getShaderSource() +IWritableShaderSource* Client::getShaderSource() { return m_shsrc; } @@ -1864,14 +1912,20 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache) return mesh; } -const std::string* Client::getModFile(const std::string &filename) +const std::string* Client::getModFile(std::string filename) { - StringMap::const_iterator it = m_mod_files.find(filename); - if (it == m_mod_files.end()) { - errorstream << "Client::getModFile(): File not found: \"" << filename - << "\"" << std::endl; - return NULL; - } + // strip dir delimiter from beginning of path + auto pos = filename.find_first_of(':'); + if (pos == std::string::npos) + return nullptr; + pos++; + auto pos2 = filename.find_first_not_of('/', pos); + if (pos2 > pos) + filename.erase(pos, pos2 - pos); + + StringMap::const_iterator it = m_mod_vfs.find(filename); + if (it == m_mod_vfs.end()) + return nullptr; return &it->second; } diff --git a/src/client/client.h b/src/client/client.h index e3c931837..1291b944c 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -218,6 +218,9 @@ public: void handleCommand_HudSetFlags(NetworkPacket* pkt); void handleCommand_HudSetParam(NetworkPacket* pkt); void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_HudSetSun(NetworkPacket* pkt); + void handleCommand_HudSetMoon(NetworkPacket* pkt); + void handleCommand_HudSetStars(NetworkPacket* pkt); void handleCommand_CloudParams(NetworkPacket* pkt); void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); @@ -261,14 +264,11 @@ public: // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) void removeNode(v3s16 p); - /** - * Helper function for Client Side Modding - * CSM restrictions are applied there, this should not be used for core engine - * @param p - * @param is_valid_position - * @return - */ - MapNode getNode(v3s16 p, bool *is_valid_position); + // helpers to enforce CSM restrictions + MapNode CSMGetNode(v3s16 p, bool *is_valid_position); + int CSMClampRadius(v3s16 pos, int radius); + v3s16 CSMClampPos(v3s16 pos); + void addNode(v3s16 p, MapNode n, bool remove_metadata = true); void setPlayerControl(PlayerControl &control); @@ -370,7 +370,7 @@ public: const NodeDefManager* getNodeDefManager() override; ICraftDefManager* getCraftDefManager() override; ITextureSource* getTextureSource(); - virtual IShaderSource* getShaderSource(); + virtual IWritableShaderSource* getShaderSource(); u16 allocateUnknownNodeId(const std::string &name) override; virtual ISoundManager* getSoundManager(); MtEventManager* getEventManager(); @@ -378,7 +378,7 @@ public: bool checkLocalPrivilege(const std::string &priv) { return checkPrivilege(priv); } virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); - const std::string* getModFile(const std::string &filename); + const std::string* getModFile(std::string filename); std::string getModStoragePath() const override; bool registerModStorage(ModMetadata *meta) override; @@ -413,6 +413,11 @@ public: return m_address_name; } + inline u64 getCSMRestrictionFlags() const + { + return m_csm_restriction_flags; + } + inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const { return m_csm_restriction_flags & flag; @@ -451,7 +456,6 @@ private: bool is_local_server); void ReceiveAll(); - void Receive(); void sendPlayerPos(); @@ -560,7 +564,7 @@ private: std::unordered_map<s32, int> m_sounds_server_to_client; // And the other way! std::unordered_map<int, s32> m_sounds_client_to_server; - // And relations to objects + // Relation of client id to object id std::unordered_map<int, u16> m_sounds_to_objects; // Map server hud ids to client hud ids @@ -576,8 +580,6 @@ private: // Storage for mesh data for creating multiple instances of the same mesh StringMap m_mesh_data; - StringMap m_mod_files; - // own state LocalClientState m_state; @@ -588,11 +590,13 @@ private: IntervalLimiter m_localdb_save_interval; u16 m_cache_save_interval; + // Client modding ClientScripting *m_script = nullptr; bool m_modding_enabled; std::unordered_map<std::string, ModMetadata *> m_mod_storages; float m_mod_storage_save_timer = 10.0f; std::vector<ModSpec> m_mods; + StringMap m_mod_vfs; bool m_shutdown = false; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 5eb033302..52d133781 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -32,11 +32,66 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "raycast.h" #include "voxelalgorithms.h" #include "settings.h" +#include "shader.h" #include "content_cao.h" #include <algorithm> #include "client/renderingengine.h" /* + CAOShaderConstantSetter +*/ + +//! Shader constant setter for passing material emissive color to the CAO object_shader +class CAOShaderConstantSetter : public IShaderConstantSetter +{ +public: + CAOShaderConstantSetter(): + m_emissive_color_setting("emissiveColor") + {} + + ~CAOShaderConstantSetter() override = default; + + void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) override + { + if (!is_highlevel) + return; + + // Ambient color + video::SColorf emissive_color(m_emissive_color); + + float as_array[4] = { + emissive_color.r, + emissive_color.g, + emissive_color.b, + emissive_color.a, + }; + m_emissive_color_setting.set(as_array, services); + } + + void onSetMaterial(const video::SMaterial& material) override + { + m_emissive_color = material.EmissiveColor; + } + +private: + video::SColor m_emissive_color; + CachedPixelShaderSetting<float, 4> m_emissive_color_setting; +}; + +class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ +public: + CAOShaderConstantSetterFactory() + {} + + virtual IShaderConstantSetter* create() + { + return new CAOShaderConstantSetter(); + } +}; + +/* ClientEnvironment */ @@ -47,6 +102,8 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, m_texturesource(texturesource), m_client(client) { + auto *shdrsrc = m_client->getShaderSource(); + shdrsrc->addShaderConstantSetterFactory(new CAOShaderConstantSetterFactory()); } ClientEnvironment::~ClientEnvironment() diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 2a44717ce..f5689c25b 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include "irrlichttypes_bloated.h" #include "hud.h" +#include "skyparams.h" enum ClientEventType : u8 { @@ -38,6 +39,9 @@ enum ClientEventType : u8 CE_HUDRM, CE_HUDCHANGE, CE_SET_SKY, + CE_SET_SUN, + CE_SET_MOON, + CE_SET_STARS, CE_OVERRIDE_DAY_NIGHT_RATIO, CE_CLOUD_PARAMS, CLIENTEVENT_MAX, @@ -131,6 +135,7 @@ struct ClientEvent v2f *offset; v3f *world_pos; v2s32 *size; + s16 z_index; } hudadd; struct { @@ -146,13 +151,7 @@ struct ClientEvent v3f *v3fdata; v2s32 *v2s32data; } hudchange; - struct - { - video::SColor *bgcolor; - std::string *type; - std::vector<std::string> *params; - bool clouds; - } set_sky; + SkyboxParams *set_sky; struct { bool do_override; @@ -168,5 +167,8 @@ struct ClientEvent f32 speed_x; f32 speed_y; } cloud_params; + SunParams *sun_params; + MoonParams *moon_params; + StarParams *star_params; }; }; diff --git a/src/client/clientobject.h b/src/client/clientobject.h index c673fff9a..12e0db35b 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -49,8 +49,10 @@ public: virtual bool getSelectionBox(aabb3f *toset) const { return false; } virtual bool collideWithObjects() const { return false; } virtual const v3f getPosition() const { return v3f(0.0f); } - virtual scene::ISceneNode *getSceneNode() { return NULL; } - virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; } + virtual scene::ISceneNode *getSceneNode() const + { return NULL; } + virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const + { return NULL; } virtual bool isLocalPlayer() const { return false; } virtual ClientActiveObject *getParent() const { return nullptr; }; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 5521a6cf1..d148df522 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "wieldmesh.h" #include <algorithm> #include <cmath> +#include "client/shader.h" class Settings; struct ToolCapabilities; @@ -352,6 +353,8 @@ void GenericCAO::initialize(const std::string &data) player->setCAO(this); } } + + m_enable_shaders = g_settings->getBool("enable_shaders"); } void GenericCAO::processInitData(const std::string &data) @@ -403,13 +406,17 @@ bool GenericCAO::getSelectionBox(aabb3f *toset) const const v3f GenericCAO::getPosition() const { - if (getParent() != nullptr) { - if (m_matrixnode) - return m_matrixnode->getAbsolutePosition(); + if (!getParent()) + return pos_translator.val_current; - return m_position; + // Calculate real position in world based on MatrixNode + if (m_matrixnode) { + v3s16 camera_offset = m_env->getCameraOffset(); + return m_matrixnode->getAbsolutePosition() + + intToFloat(camera_offset, BS); } - return pos_translator.val_current; + + return m_position; } const bool GenericCAO::isImmortal() @@ -417,7 +424,7 @@ const bool GenericCAO::isImmortal() return itemgroup_get(getGroups(), "immortal"); } -scene::ISceneNode* GenericCAO::getSceneNode() +scene::ISceneNode *GenericCAO::getSceneNode() const { if (m_meshnode) { return m_meshnode; @@ -437,7 +444,7 @@ scene::ISceneNode* GenericCAO::getSceneNode() return NULL; } -scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode() +scene::IAnimatedMeshSceneNode *GenericCAO::getAnimatedMeshSceneNode() const { return m_animated_meshnode; } @@ -573,25 +580,47 @@ void GenericCAO::addToScene(ITextureSource *tsrc) return; } - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + if (m_enable_shaders) { + IShaderSource *shader_source = m_client->getShaderSource(); + u32 shader_id = shader_source->getShader( + "object_shader", + TILE_MATERIAL_BASIC, + NDT_NORMAL); + m_material_type = shader_source->getShaderInfo(shader_id).material; + } else { + m_material_type = (m_prop.use_texture_alpha) ? + video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + } - if (m_prop.visual == "sprite") { - infostream<<"GenericCAO::addToScene(): single_sprite"<<std::endl; + auto grabMatrixNode = [this] { + infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; m_matrixnode = RenderingEngine::get_scene_manager()-> addDummyTransformationSceneNode(); m_matrixnode->grab(); + }; + + auto setSceneNodeMaterial = [this] (scene::ISceneNode *node) { + node->setMaterialFlag(video::EMF_LIGHTING, false); + node->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + node->setMaterialFlag(video::EMF_FOG_ENABLE, true); + node->setMaterialType(m_material_type); + + if (m_enable_shaders) { + node->setMaterialFlag(video::EMF_GOURAUD_SHADING, false); + node->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); + } + }; + + if (m_prop.visual == "sprite") { + grabMatrixNode(); m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode( m_matrixnode, v2f(1, 1), v3f(0,0,0), -1); m_spritenode->grab(); m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh("unknown_node.png")); - m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); - m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_spritenode->setMaterialType(material_type); - m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); - u8 li = m_last_light; - m_spritenode->setColor(video::SColor(255,li,li,li)); + + setSceneNodeMaterial(m_spritenode); + m_spritenode->setSize(v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); { @@ -601,19 +630,19 @@ void GenericCAO::addToScene(ITextureSource *tsrc) txs, tys, 0, 0); } } else if (m_prop.visual == "upright_sprite") { + grabMatrixNode(); scene::SMesh *mesh = new scene::SMesh(); double dx = BS * m_prop.visual_size.X / 2; double dy = BS * m_prop.visual_size.Y / 2; - u8 li = m_last_light; - video::SColor c(255, li, li, li); + video::SColor c(0xFFFFFFFF); { // Front scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1), - video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 1,0), + video::S3DVertex(-dx, -dy, 0, 0,0,1, c, 1,1), + video::S3DVertex( dx, -dy, 0, 0,0,1, c, 0,1), + video::S3DVertex( dx, dy, 0, 0,0,1, c, 0,0), + video::S3DVertex(-dx, dy, 0, 0,0,1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) @@ -626,7 +655,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc) buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + buf->getMaterial().MaterialType = m_material_type; + + if (m_enable_shaders) { + buf->getMaterial().EmissiveColor = c; + buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + } + // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); @@ -634,10 +670,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc) { // Back scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::S3DVertex vertices[4] = { - video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1), - video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1), - video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0), - video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0), + video::S3DVertex( dx,-dy, 0, 0,0,-1, c, 1,1), + video::S3DVertex(-dx,-dy, 0, 0,0,-1, c, 0,1), + video::S3DVertex(-dx, dy, 0, 0,0,-1, c, 0,0), + video::S3DVertex( dx, dy, 0, 0,0,-1, c, 1,0), }; if (m_is_player) { // Move minimal Y position to 0 (feet position) @@ -650,14 +686,18 @@ void GenericCAO::addToScene(ITextureSource *tsrc) buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + buf->getMaterial().MaterialType = m_material_type; + + if (m_enable_shaders) { + buf->getMaterial().EmissiveColor = c; + buf->getMaterial().setFlag(video::EMF_GOURAUD_SHADING, false); + buf->getMaterial().setFlag(video::EMF_NORMALIZE_NORMALS, true); + } + // Add to mesh mesh->addMeshBuffer(buf); buf->drop(); } - m_matrixnode = RenderingEngine::get_scene_manager()-> - addDummyTransformationSceneNode(); - m_matrixnode->grab(); m_meshnode = RenderingEngine::get_scene_manager()-> addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); @@ -666,55 +706,41 @@ void GenericCAO::addToScene(ITextureSource *tsrc) // This is needed for changing the texture in the future m_meshnode->setReadOnlyMaterials(true); } else if (m_prop.visual == "cube") { - infostream<<"GenericCAO::addToScene(): cube"<<std::endl; + grabMatrixNode(); scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); - m_matrixnode = RenderingEngine::get_scene_manager()-> - addDummyTransformationSceneNode(nullptr); - m_matrixnode->grab(); m_meshnode = RenderingEngine::get_scene_manager()-> addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); mesh->drop(); m_meshnode->setScale(m_prop.visual_size); - u8 li = m_last_light; - setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); - m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); - m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_meshnode->setMaterialType(material_type); - m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + setSceneNodeMaterial(m_meshnode); } else if (m_prop.visual == "mesh") { - infostream<<"GenericCAO::addToScene(): mesh"<<std::endl; + grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { - m_matrixnode = RenderingEngine::get_scene_manager()-> - addDummyTransformationSceneNode(nullptr); - m_matrixnode->grab(); m_animated_meshnode = RenderingEngine::get_scene_manager()-> addAnimatedMeshSceneNode(mesh, m_matrixnode); m_animated_meshnode->grab(); mesh->drop(); // The scene node took hold of it m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); - u8 li = m_last_light; // set vertex colors to ensure alpha is set - setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li)); + setMeshColor(m_animated_meshnode->getMesh(), video::SColor(0xFFFFFFFF)); + + setAnimatedMeshColor(m_animated_meshnode, video::SColor(0xFFFFFFFF)); - setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li)); + setSceneNodeMaterial(m_animated_meshnode); - m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true); - m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); - m_animated_meshnode->setMaterialType(material_type); - m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling); } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl; } else if (m_prop.visual == "wielditem" || m_prop.visual == "item") { + grabMatrixNode(); ItemStack item; - infostream << "GenericCAO::addToScene(): wielditem" << std::endl; if (m_prop.wield_item.empty()) { // Old format, only textures are specified. infostream << "textures: " << m_prop.textures.size() << std::endl; @@ -728,18 +754,13 @@ void GenericCAO::addToScene(ITextureSource *tsrc) infostream << "serialized form: " << m_prop.wield_item << std::endl; item.deSerialize(m_prop.wield_item, m_client->idef()); } - m_matrixnode = RenderingEngine::get_scene_manager()-> - addDummyTransformationSceneNode(nullptr); - m_matrixnode->grab(); m_wield_meshnode = new WieldMeshSceneNode( RenderingEngine::get_scene_manager(), -1); - m_wield_meshnode->setParent(m_matrixnode); m_wield_meshnode->setItem(item, m_client, (m_prop.visual == "wielditem")); m_wield_meshnode->setScale(m_prop.visual_size / 2.0f); - u8 li = m_last_light; - m_wield_meshnode->setColor(video::SColor(255, li, li, li)); + m_wield_meshnode->setColor(video::SColor(0xFFFFFFFF)); } else { infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual <<"\" not supported"<<std::endl; @@ -751,6 +772,9 @@ void GenericCAO::addToScene(ITextureSource *tsrc) scene::ISceneNode *node = getSceneNode(); + if (node && m_matrixnode) + node->setParent(m_matrixnode); + if (node && !m_prop.nametag.empty() && !m_is_local_player) { // Add nametag v3f pos; @@ -764,6 +788,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc) updateAnimation(); updateBonePosition(); updateAttachments(); + setNodeLight(m_last_light); } void GenericCAO::updateLight(u8 light_at_pos) @@ -790,15 +815,46 @@ void GenericCAO::updateLightNoCheck(u8 light_at_pos) return; u8 li = decode_light(light_at_pos + m_glow); + if (li != m_last_light) { m_last_light = li; - video::SColor color(255,li,li,li); + setNodeLight(li); + } +} + +void GenericCAO::setNodeLight(u8 light) +{ + video::SColor color(255, light, light, light); + + if (m_prop.visual == "wielditem" || m_prop.visual == "item") { + // Since these types of visuals are using their own shader + // they should be handled separately + if (m_wield_meshnode) + m_wield_meshnode->setColor(color); + } else if (m_enable_shaders) { + scene::ISceneNode *node = getSceneNode(); + + if (node == nullptr) + return; + + if (m_prop.visual == "upright_sprite") { + scene::IMesh *mesh = m_meshnode->getMesh(); + for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); + video::SMaterial &material = buf->getMaterial(); + material.EmissiveColor = color; + } + } else { + for (u32 i = 0; i < node->getMaterialCount(); ++i) { + video::SMaterial &material = node->getMaterial(i); + material.EmissiveColor = color; + } + } + } else { if (m_meshnode) { setMeshColor(m_meshnode->getMesh(), color); } else if (m_animated_meshnode) { setAnimatedMeshColor(m_animated_meshnode, color); - } else if (m_wield_meshnode) { - m_wield_meshnode->setColor(color); } else if (m_spritenode) { m_spritenode->setColor(color); } @@ -884,7 +940,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // Apply animations if input detected and not attached // or set idle animation - if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) { + if ((new_anim.X + new_anim.Y) > 0 && !getParent()) { allow_update = true; m_animation_range = new_anim; m_animation_speed = new_speed; @@ -946,12 +1002,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); pos_translator.val_current = m_position; - - if(m_is_local_player) // Update local player attachment position - { - LocalPlayer *player = m_env->getLocalPlayer(); - player->overridePosition = getParent()->getPosition(); - } + pos_translator.val_target = m_position; } else { rot_translator.translate(dtime); v3f lastpos = pos_translator.val_current; @@ -975,16 +1026,14 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) bool is_end_position = moveresult.collides; pos_translator.update(m_position, is_end_position, dtime); - pos_translator.translate(dtime); - updateNodePos(); } else { m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; m_velocity += dtime * m_acceleration; pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); - pos_translator.translate(dtime); - updateNodePos(); } + pos_translator.translate(dtime); + updateNodePos(); float moved = lastpos.getDistanceFrom(pos_translator.val_current); m_step_distance_counter += moved; @@ -1109,16 +1158,13 @@ void GenericCAO::updateTextures(std::string mod) m_current_texture_modifier = mod; m_glow = m_prop.glow; - video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ? - video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - if (m_spritenode) { if (m_prop.visual == "sprite") { std::string texturestring = "unknown_node.png"; if (!m_prop.textures.empty()) texturestring = m_prop.textures[0]; texturestring += mod; - m_spritenode->getMaterial(0).MaterialType = material_type; + m_spritenode->getMaterial(0).MaterialType = m_material_type; m_spritenode->getMaterial(0).MaterialTypeParam = 0.5f; m_spritenode->setMaterialTexture(0, tsrc->getTextureForMesh(texturestring)); @@ -1154,7 +1200,7 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial& material = m_animated_meshnode->getMaterial(i); - material.MaterialType = material_type; + material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.TextureLayer[0].Texture = texture; material.setFlag(video::EMF_LIGHTING, true); @@ -1201,7 +1247,7 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial& material = m_meshnode->getMaterial(i); - material.MaterialType = material_type; + material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); @@ -1348,7 +1394,8 @@ void GenericCAO::updateAttachments() if (!parent) { // Detach or don't attach if (m_matrixnode) { - v3f old_pos = m_matrixnode->getAbsolutePosition(); + v3f old_pos = getPosition(); + m_matrixnode->setParent(m_smgr->getRootSceneNode()); getPosRotMatrix().setTranslation(old_pos); m_matrixnode->updateAbsolutePosition(); @@ -1372,11 +1419,6 @@ void GenericCAO::updateAttachments() m_matrixnode->updateAbsolutePosition(); } } - if (m_is_local_player) { - LocalPlayer *player = m_env->getLocalPlayer(); - player->isAttached = parent; - player->parent = parent; - } } void GenericCAO::processMessage(const std::string &data) diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 2c2d11077..7c29cbf17 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -125,6 +125,10 @@ private: u8 m_last_light = 255; bool m_is_visible = false; s8 m_glow = 0; + // Material + video::E_MATERIAL_TYPE m_material_type; + // Settings + bool m_enable_shaders = false; public: GenericCAO(Client *client, ClientEnvironment *env); @@ -165,9 +169,9 @@ public: const bool isImmortal(); - scene::ISceneNode *getSceneNode(); + scene::ISceneNode *getSceneNode() const; - scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(); + scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const; // m_matrixnode controls the position and rotation of the child node // for all scene nodes, as a workaround for an Irrlicht problem with @@ -234,6 +238,8 @@ public: void updateLightNoCheck(u8 light_at_pos); + void setNodeLight(u8 light); + v3s16 getLightPosition(); void updateNodePos(); diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 4a0df6171..9b4fd221e 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -556,17 +556,24 @@ void MapblockMeshGenerator::drawLiquidSides() for (int j = 0; j < 4; j++) { const UV &vertex = base_vertices[j]; const v3s16 &base = face.p[vertex.u]; + float v = vertex.v; + v3f pos; - pos.X = (base.X - 0.5) * BS; - pos.Z = (base.Z - 0.5) * BS; - if (vertex.v) - pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS; - else - pos.Y = !top_is_same_liquid ? corner_levels[base.Z][base.X] : 0.5 * BS; + pos.X = (base.X - 0.5f) * BS; + pos.Z = (base.Z - 0.5f) * BS; + if (vertex.v) { + pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS; + } else if (top_is_same_liquid) { + pos.Y = 0.5f * BS; + } else { + pos.Y = corner_levels[base.Z][base.X]; + v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS; + } + if (data->m_smooth_lighting) color = blendLightColor(pos); pos += origin; - vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v); + vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v); }; collector->append(tile_liquid, vertices, 4, quad_indices, 6); } @@ -860,17 +867,27 @@ void MapblockMeshGenerator::drawTorchlikeNode() for (v3f &vertex : vertices) { switch (wall) { case DWM_YP: - vertex.rotateXZBy(-45); break; + vertex.Y += -size + BS/2; + vertex.rotateXZBy(-45); + break; case DWM_YN: - vertex.rotateXZBy( 45); break; + vertex.Y += size - BS/2; + vertex.rotateXZBy(45); + break; case DWM_XP: - vertex.rotateXZBy( 0); break; + vertex.X += -size + BS/2; + break; case DWM_XN: - vertex.rotateXZBy(180); break; + vertex.X += -size + BS/2; + vertex.rotateXZBy(180); + break; case DWM_ZP: - vertex.rotateXZBy( 90); break; + vertex.X += -size + BS/2; + vertex.rotateXZBy(90); + break; case DWM_ZN: - vertex.rotateXZBy(-90); break; + vertex.X += -size + BS/2; + vertex.rotateXZBy(-90); } } drawQuad(vertices); diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 858d6780e..2b5841cd8 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -59,7 +59,12 @@ FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) : if (m_currentMode == FM_Standard) { m_settings->registerChangedCallback("font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_bold", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_italic", font_setting_changed, NULL); m_settings->registerChangedCallback("font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_path_bold", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_path_italic", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_path_bolditalic", font_setting_changed, NULL); m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); } @@ -96,36 +101,45 @@ void FontEngine::cleanCache() } /******************************************************************************/ -irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode) +irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec) { - if (mode == FM_Unspecified) { - mode = m_currentMode; + if (spec.mode == FM_Unspecified) { + spec.mode = m_currentMode; } else if (m_currentMode == FM_Simple) { // Freetype disabled -> Force simple mode - mode = (mode == FM_Mono || mode == FM_SimpleMono) ? - FM_SimpleMono : FM_Simple; + spec.mode = (spec.mode == FM_Mono || + spec.mode == FM_SimpleMono) ? + FM_SimpleMono : FM_Simple; + // Support for those could be added, but who cares? + spec.bold = false; + spec.italic = false; } // Fallback to default size - if (font_size == FONT_SIZE_UNSPECIFIED) - font_size = m_default_size[mode]; - - const auto &cache = m_font_cache[mode]; - if (cache.find(font_size) == cache.end()) { - if (mode == FM_Simple || mode == FM_SimpleMono) - initSimpleFont(font_size, mode); - else - initFont(font_size, mode); - } + if (spec.size == FONT_SIZE_UNSPECIFIED) + spec.size = m_default_size[spec.mode]; + + const auto &cache = m_font_cache[spec.getHash()]; + auto it = cache.find(spec.size); + if (it != cache.end()) + return it->second; + + // Font does not yet exist + gui::IGUIFont *font = nullptr; + if (spec.mode == FM_Simple || spec.mode == FM_SimpleMono) + font = initSimpleFont(spec); + else + font = initFont(spec); - const auto &font = cache.find(font_size); - return font != cache.end() ? font->second : nullptr; + m_font_cache[spec.getHash()][spec.size] = font; + + return font; } /******************************************************************************/ -unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) +unsigned int FontEngine::getTextHeight(const FontSpec &spec) { - irr::gui::IGUIFont* font = getFont(font_size, mode); + irr::gui::IGUIFont *font = getFont(spec); // use current skin font as fallback if (font == NULL) { @@ -137,10 +151,9 @@ unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) } /******************************************************************************/ -unsigned int FontEngine::getTextWidth(const std::wstring& text, - unsigned int font_size, FontMode mode) +unsigned int FontEngine::getTextWidth(const std::wstring &text, const FontSpec &spec) { - irr::gui::IGUIFont* font = getFont(font_size, mode); + irr::gui::IGUIFont *font = getFont(spec); // use current skin font as fallback if (font == NULL) { @@ -153,9 +166,9 @@ unsigned int FontEngine::getTextWidth(const std::wstring& text, /** get line height for a specific font (including empty room between lines) */ -unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode) +unsigned int FontEngine::getLineHeight(const FontSpec &spec) { - irr::gui::IGUIFont* font = getFont(font_size, mode); + irr::gui::IGUIFont *font = getFont(spec); // use current skin font as fallback if (font == NULL) { @@ -181,8 +194,20 @@ void FontEngine::readSettings() m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + /*~ DO NOT TRANSLATE THIS LITERALLY! + This is a special string. Put either "no" or "yes" + into the translation field (literally). + Choose "yes" if the language requires use of the fallback + font, "no" otherwise. + The fallback font is (normally) required for languages with + non-Latin script, like Chinese. + When in doubt, test your translation. */ m_currentMode = is_yes(gettext("needs_fallback_font")) ? FM_Fallback : FM_Standard; + + m_default_bold = m_settings->getBool("font_bold"); + m_default_italic = m_settings->getBool("font_italic"); + } else { m_currentMode = FM_Simple; } @@ -226,18 +251,14 @@ void FontEngine::updateFontCache() } /******************************************************************************/ -void FontEngine::initFont(unsigned int basesize, FontMode mode) +gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) { - assert(mode != FM_Unspecified); - assert(basesize != FONT_SIZE_UNSPECIFIED); - - if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end()) - return; - + assert(spec.mode != FM_Unspecified); + assert(spec.size != FONT_SIZE_UNSPECIFIED); std::string setting_prefix = ""; - switch (mode) { + switch (spec.mode) { case FM_Fallback: setting_prefix = "fallback_"; break; @@ -249,8 +270,15 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) break; } + std::string setting_suffix = ""; + if (spec.bold) + setting_suffix.append("_bold"); + if (spec.italic) + setting_suffix.append("_italic"); + u32 size = std::floor(RenderingEngine::getDisplayDensity() * - m_settings->getFloat("gui_scaling") * basesize); + m_settings->getFloat("gui_scaling") * spec.size); + if (size == 0) { errorstream << "FontEngine: attempt to use font size 0" << std::endl; errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl; @@ -260,10 +288,14 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) u16 font_shadow = 0; u16 font_shadow_alpha = 0; g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow); - g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", font_shadow_alpha); + g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", + font_shadow_alpha); + + std::string wanted_font_path; + wanted_font_path = g_settings->get(setting_prefix + "font_path" + setting_suffix); std::string fallback_settings[] = { - m_settings->get(setting_prefix + "font_path"), + wanted_font_path, m_settings->get("fallback_font_path"), m_settings->getDefault(setting_prefix + "font_path") }; @@ -274,10 +306,8 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) font_path.c_str(), size, true, true, font_shadow, font_shadow_alpha); - if (font) { - m_font_cache[mode][basesize] = font; - return; - } + if (font) + return font; errorstream << "FontEngine: Cannot load '" << font_path << "'. Trying to fall back to another path." << std::endl; @@ -296,12 +326,13 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) } /** initialize a font without freetype */ -void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) +gui::IGUIFont *FontEngine::initSimpleFont(const FontSpec &spec) { - assert(mode == FM_Simple || mode == FM_SimpleMono); + assert(spec.mode == FM_Simple || spec.mode == FM_SimpleMono); + assert(spec.size != FONT_SIZE_UNSPECIFIED); const std::string &font_path = m_settings->get( - (mode == FM_SimpleMono) ? "mono_font_path" : "font_path"); + (spec.mode == FM_SimpleMono) ? "mono_font_path" : "font_path"); size_t pos_dot = font_path.find_last_of('.'); std::string basename = font_path; @@ -310,19 +341,16 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) if (ending == ".ttf") { errorstream << "FontEngine: Found font \"" << font_path << "\" but freetype is not available." << std::endl; - return; + return nullptr; } if (ending == ".xml" || ending == ".png") basename = font_path.substr(0, pos_dot); - if (basesize == FONT_SIZE_UNSPECIFIED) - basesize = DEFAULT_FONT_SIZE; - u32 size = std::floor( RenderingEngine::getDisplayDensity() * m_settings->getFloat("gui_scaling") * - basesize); + spec.size); irr::gui::IGUIFont *font = nullptr; std::string font_extensions[] = { ".png", ".xml" }; @@ -340,7 +368,7 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) path.str(""); // Clear path << basename << "_" << (size + offset * sign) << ext; - if (!fs::PathExists(path.str())) + if (!fs::PathExists(path.str())) continue; font = m_env->getFont(path.str().c_str()); @@ -364,6 +392,5 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) } } - if (font) - m_font_cache[mode][basesize] = font; + return font; } diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 62aa71897..53f14c45f 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF -enum FontMode { +enum FontMode : u8 { FM_Standard = 0, FM_Mono, FM_Fallback, @@ -39,6 +39,24 @@ enum FontMode { FM_Unspecified }; +struct FontSpec { + FontSpec(unsigned int font_size, FontMode mode, bool bold, bool italic) : + size(font_size), + mode(mode), + bold(bold), + italic(italic) {} + + u16 getHash() + { + return (mode << 2) | (bold << 1) | italic; + } + + unsigned int size; + FontMode mode; + bool bold; + bool italic; +}; + class FontEngine { public: @@ -47,30 +65,61 @@ public: ~FontEngine(); - /** get Font */ - irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); + // Get best possible font specified by FontSpec + irr::gui::IGUIFont *getFont(FontSpec spec); + + irr::gui::IGUIFont *getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified) + { + FontSpec spec(font_size, mode, m_default_bold, m_default_italic); + return getFont(spec); + } /** get text height for a specific font */ - unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); + unsigned int getTextHeight(const FontSpec &spec); /** get text width if a text for a specific font */ - unsigned int getTextWidth(const std::string& text, + unsigned int getTextHeight( unsigned int font_size=FONT_SIZE_UNSPECIFIED, FontMode mode=FM_Unspecified) { - return getTextWidth(utf8_to_wide(text)); + FontSpec spec(font_size, mode, m_default_bold, m_default_italic); + return getTextHeight(spec); } + unsigned int getTextWidth(const std::wstring &text, const FontSpec &spec); + /** get text width if a text for a specific font */ unsigned int getTextWidth(const std::wstring& text, unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); + FontMode mode=FM_Unspecified) + { + FontSpec spec(font_size, mode, m_default_bold, m_default_italic); + return getTextWidth(text, spec); + } + + unsigned int getTextWidth(const std::string &text, const FontSpec &spec) + { + return getTextWidth(utf8_to_wide(text), spec); + } + + unsigned int getTextWidth(const std::string& text, + unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified) + { + FontSpec spec(font_size, mode, m_default_bold, m_default_italic); + return getTextWidth(utf8_to_wide(text), spec); + } /** get line height for a specific font (including empty room between lines) */ + unsigned int getLineHeight(const FontSpec &spec); + unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, - FontMode mode=FM_Unspecified); + FontMode mode=FM_Unspecified) + { + FontSpec spec(font_size, mode, m_default_bold, m_default_italic); + return getLineHeight(spec); + } /** get default font size */ unsigned int getDefaultFontSize(); @@ -86,10 +135,10 @@ private: void updateFontCache(); /** initialize a new font */ - void initFont(unsigned int basesize, FontMode mode=FM_Unspecified); + gui::IGUIFont *initFont(const FontSpec &spec); /** initialize a font without freetype */ - void initSimpleFont(unsigned int basesize, FontMode mode); + gui::IGUIFont *initSimpleFont(const FontSpec &spec); /** update current minetest skin with font changes */ void updateSkin(); @@ -104,11 +153,15 @@ private: gui::IGUIEnvironment* m_env = nullptr; /** internal storage for caching fonts of different size */ - std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode]; + std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode << 2]; /** default font size to use */ unsigned int m_default_size[FM_MaxMode]; + /** default bold and italic */ + bool m_default_bold = false; + bool m_default_italic = false; + /** current font engine mode */ FontMode m_currentMode = FM_Standard; diff --git a/src/client/game.cpp b/src/client/game.cpp index 450eb4e32..0201ded69 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -811,6 +811,9 @@ private: void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); @@ -2523,6 +2526,9 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { {&Game::handleClientEvent_HudRemove}, {&Game::handleClientEvent_HudChange}, {&Game::handleClientEvent_SetSky}, + {&Game::handleClientEvent_SetSun}, + {&Game::handleClientEvent_SetMoon}, + {&Game::handleClientEvent_SetStars}, {&Game::handleClientEvent_OverrideDayNigthRatio}, {&Game::handleClientEvent_CloudParams}, }; @@ -2650,6 +2656,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->offset = *event->hudadd.offset; e->world_pos = *event->hudadd.world_pos; e->size = *event->hudadd.size; + e->z_index = event->hudadd.z_index; hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd.pos; @@ -2728,6 +2735,10 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca case HUD_STAT_SIZE: e->size = *event->hudchange.v2s32data; break; + + case HUD_STAT_Z_INDEX: + e->z_index = event->hudchange.data; + break; } delete event->hudchange.v3fdata; @@ -2739,41 +2750,85 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) { sky->setVisible(false); - // Whether clouds are visible in front of a custom skybox - sky->setCloudsEnabled(event->set_sky.clouds); + // Whether clouds are visible in front of a custom skybox. + sky->setCloudsEnabled(event->set_sky->clouds); if (skybox) { skybox->remove(); skybox = NULL; } - + // Clear the old textures out in case we switch rendering type. + sky->clearSkyboxTextures(); // Handle according to type - if (*event->set_sky.type == "regular") { + if (event->set_sky->type == "regular") { + // Shows the mesh skybox sky->setVisible(true); - sky->setCloudsEnabled(true); - } else if (*event->set_sky.type == "skybox" && - event->set_sky.params->size() == 6) { - sky->setFallbackBgColor(*event->set_sky.bgcolor); - skybox = RenderingEngine::get_scene_manager()->addSkyBoxSceneNode( - texture_src->getTextureForMesh((*event->set_sky.params)[0]), - texture_src->getTextureForMesh((*event->set_sky.params)[1]), - texture_src->getTextureForMesh((*event->set_sky.params)[2]), - texture_src->getTextureForMesh((*event->set_sky.params)[3]), - texture_src->getTextureForMesh((*event->set_sky.params)[4]), - texture_src->getTextureForMesh((*event->set_sky.params)[5])); - } - // Handle everything else as plain color - else { - if (*event->set_sky.type != "plain") + // Update mesh based skybox colours if applicable. + sky->setSkyColors(*event->set_sky); + sky->setHorizonTint( + event->set_sky->sun_tint, + event->set_sky->moon_tint, + event->set_sky->tint_type + ); + } else if (event->set_sky->type == "skybox" && + event->set_sky->textures.size() == 6) { + // Disable the dyanmic mesh skybox: + sky->setVisible(false); + // Set fog colors: + sky->setFallbackBgColor(event->set_sky->bgcolor); + // Set sunrise and sunset fog tinting: + sky->setHorizonTint( + event->set_sky->sun_tint, + event->set_sky->moon_tint, + event->set_sky->tint_type + ); + // Add textures to skybox. + for (int i = 0; i < 6; i++) + sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src); + } else { + // Handle everything else as plain color. + if (event->set_sky->type != "plain") infostream << "Unknown sky type: " - << (*event->set_sky.type) << std::endl; - - sky->setFallbackBgColor(*event->set_sky.bgcolor); + << (event->set_sky->type) << std::endl; + sky->setVisible(false); + sky->setFallbackBgColor(event->set_sky->bgcolor); + // Disable directional sun/moon tinting on plain or invalid skyboxes. + sky->setHorizonTint( + event->set_sky->bgcolor, + event->set_sky->bgcolor, + "custom" + ); } + delete event->set_sky; +} + +void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam) +{ + sky->setSunVisible(event->sun_params->visible); + sky->setSunTexture(event->sun_params->texture, + event->sun_params->tonemap, texture_src); + sky->setSunScale(event->sun_params->scale); + sky->setSunriseVisible(event->sun_params->sunrise_visible); + sky->setSunriseTexture(event->sun_params->sunrise, texture_src); + delete event->sun_params; +} - delete event->set_sky.bgcolor; - delete event->set_sky.type; - delete event->set_sky.params; +void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam) +{ + sky->setMoonVisible(event->moon_params->visible); + sky->setMoonTexture(event->moon_params->texture, + event->moon_params->tonemap, texture_src); + sky->setMoonScale(event->moon_params->scale); + delete event->moon_params; +} + +void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam) +{ + sky->setStarsVisible(event->star_params->visible); + sky->setStarCount(event->star_params->count, false); + sky->setStarColor(event->star_params->starcolor); + sky->setStarScale(event->star_params->scale); + delete event->star_params; } void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, @@ -3061,6 +3116,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) } else if (input->getLeftState()) { // When button is held down in air, show continuous animation runData.left_punch = true; + // Run callback even though item is not usable + if (input->getLeftClicked() && client->modsLoaded()) + client->getScript()->on_item_use(selected_item, pointed); } else if (input->getRightClicked()) { handlePointingAtNothing(selected_item); } @@ -3698,7 +3756,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, video::SColor clouds_dark = clouds->getColor() .getInterpolated(video::SColor(255, 0, 0, 0), 0.9); sky->overrideColors(clouds_dark, clouds->getColor()); - sky->setBodiesVisible(false); + sky->setInClouds(true); runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS); // do not draw clouds after all clouds->setVisible(false); @@ -4126,6 +4184,7 @@ void Game::showPauseMenu() << strgettext("- Creative Mode: ") << creative << "\n"; if (!simple_singleplayer_mode) { const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + //~ PvP = Player versus Player os << strgettext("- PvP: ") << pvp << "\n" << strgettext("- Public: ") << announced << "\n"; std::string server_name = g_settings->get("server_name"); diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 674d07fa6..138dfb4da 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -82,7 +82,6 @@ void GameUI::init() core::rect<s32>(0, 0, 0, 0), false, false, guiroot); m_guitext_profiler->setOverrideFont(g_fontengine->getFont( g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono)); - m_guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0)); m_guitext_profiler->setVisible(false); } @@ -155,7 +154,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext2->setVisible(m_flags.show_debug); - setStaticText(m_guitext_info, translate_string(m_infotext).c_str()); + setStaticText(m_guitext_info, m_infotext.c_str()); m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0); static const float statustext_time_max = 1.5f; @@ -169,7 +168,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ } } - setStaticText(m_guitext_status, translate_string(m_statustext).c_str()); + setStaticText(m_guitext_status, m_statustext.c_str()); m_guitext_status->setVisible(!m_statustext.empty()); if (!m_statustext.empty()) { @@ -246,11 +245,12 @@ void GameUI::updateProfiler() int lines = g_profiler->print(os, m_profiler_current_page, m_profiler_max_page); ++lines; - std::wstring text = utf8_to_wide(os.str()); - setStaticText(m_guitext_profiler, text.c_str()); + EnrichedString str(utf8_to_wide(os.str())); + str.setBackground(video::SColor(120, 0, 0, 0)); + setStaticText(m_guitext_profiler, str); core::dimension2d<u32> size = m_guitext_profiler->getOverrideFont()-> - getDimension(text.c_str()); + getDimension(str.c_str()); core::position2di upper_left(6, 50); core::position2di lower_right = upper_left; lower_right.X += size.Width + 10; diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 2ff57ab74..4262331bd 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -171,7 +171,8 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, } void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect<s32> &rect, const core::rect<s32> &middle) + const core::rect<s32> &rect, const core::rect<s32> &middle, + const core::rect<s32> *cliprect) { const video::SColor color(255,255,255,255); const video::SColor colors[] = {color,color,color,color}; @@ -222,9 +223,7 @@ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, break; } - draw2DImageFilterScaled(driver, texture, dest, - src, - NULL/*&AbsoluteClippingRect*/, colors, true); + draw2DImageFilterScaled(driver, texture, dest, src, cliprect, colors, true); } } } diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index 181009551..b703d91f0 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -53,4 +53,5 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, * 9-slice / segment drawing */ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect<s32> &rect, const core::rect<s32> &middle); + const core::rect<s32> &rect, const core::rect<s32> &middle, + const core::rect<s32> *cliprect = nullptr); diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 291d03816..37de6640b 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -221,19 +221,13 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, // Store hotbar_image in member variable, used by drawItem() if (hotbar_image != player->hotbar_image) { hotbar_image = player->hotbar_image; - if (!hotbar_image.empty()) - use_hotbar_image = tsrc->isKnownSourceImage(hotbar_image); - else - use_hotbar_image = false; + use_hotbar_image = !hotbar_image.empty(); } // Store hotbar_selected_image in member variable, used by drawItem() if (hotbar_selected_image != player->hotbar_selected_image) { hotbar_selected_image = player->hotbar_selected_image; - if (!hotbar_selected_image.empty()) - use_hotbar_selected_image = tsrc->isKnownSourceImage(hotbar_selected_image); - else - use_hotbar_selected_image = false; + use_hotbar_selected_image = !hotbar_selected_image.empty(); } // draw customized item background @@ -283,11 +277,25 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) { u32 text_height = g_fontengine->getTextHeight(); irr::gui::IGUIFont* font = g_fontengine->getFont(); + + // Reorder elements by z_index + std::vector<size_t> ids; + for (size_t i = 0; i != player->maxHudId(); i++) { HudElement *e = player->getHud(i); if (!e) continue; + auto it = ids.begin(); + while (it != ids.end() && player->getHud(*it)->z_index <= e->z_index) + ++it; + + ids.insert(it, i); + } + + for (size_t i : ids) { + HudElement *e = player->getHud(i); + v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5), floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { @@ -608,23 +616,24 @@ void Hud::resizeHotbar() { struct MeshTimeInfo { u64 time; - scene::IMesh *mesh; + scene::IMesh *mesh = nullptr; }; -void drawItemStack(video::IVideoDriver *driver, +void drawItemStack( + video::IVideoDriver *driver, gui::IGUIFont *font, const ItemStack &item, const core::rect<s32> &rect, const core::rect<s32> *clip, Client *client, - ItemRotationKind rotation_kind) + ItemRotationKind rotation_kind, + const v3s16 &angle, + const v3s16 &rotation_speed) { static MeshTimeInfo rotation_time_infos[IT_ROT_NONE]; - static thread_local bool enable_animations = - g_settings->getBool("inventory_items_animations"); if (item.empty()) { - if (rotation_kind < IT_ROT_NONE) { + if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) { rotation_time_infos[rotation_kind].mesh = NULL; } return; @@ -639,7 +648,7 @@ void drawItemStack(video::IVideoDriver *driver, s32 delta = 0; if (rotation_kind < IT_ROT_NONE) { MeshTimeInfo &ti = rotation_time_infos[rotation_kind]; - if (mesh != ti.mesh) { + if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) { ti.mesh = mesh; ti.time = porting::getTimeMs(); } else { @@ -677,9 +686,16 @@ void drawItemStack(video::IVideoDriver *driver, core::matrix4 matrix; matrix.makeIdentity(); + static thread_local bool enable_animations = + g_settings->getBool("inventory_items_animations"); + if (enable_animations) { - float timer_f = (float) delta / 5000.0; - matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0)); + float timer_f = (float) delta / 5000.f; + matrix.setRotationDegrees(v3f( + angle.X + rotation_speed.X * 3.60f * timer_f, + angle.Y + rotation_speed.Y * 3.60f * timer_f, + angle.Z + rotation_speed.Z * 3.60f * timer_f) + ); } driver->setTransform(video::ETS_WORLD, matrix); @@ -695,15 +711,18 @@ void drawItemStack(video::IVideoDriver *driver, // because these meshes are not buffered. assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER); video::SColor c = basecolor; + if (imesh->buffer_colors.size() > j) { ItemPartColor *p = &imesh->buffer_colors[j]; if (p->override_base) c = p->color; } + if (imesh->needs_shading) colorizeMeshBuffer(buf, &c); else setMeshBufferColor(buf, c); + video::SMaterial &material = buf->getMaterial(); material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.Lighting = false; @@ -726,12 +745,12 @@ void drawItemStack(video::IVideoDriver *driver, } } - if(def.type == ITEM_TOOL && item.wear != 0) - { + if (def.type == ITEM_TOOL && item.wear != 0) { // Draw a progressbar - float barheight = rect.getHeight()/16; - float barpad_x = rect.getWidth()/16; - float barpad_y = rect.getHeight()/16; + float barheight = rect.getHeight() / 16; + float barpad_x = rect.getWidth() / 16; + float barpad_y = rect.getHeight() / 16; + core::rect<s32> progressrect( rect.UpperLeftCorner.X + barpad_x, rect.LowerRightCorner.Y - barpad_y - barheight, @@ -739,18 +758,19 @@ void drawItemStack(video::IVideoDriver *driver, rect.LowerRightCorner.Y - barpad_y); // Shrink progressrect by amount of tool damage - float wear = item.wear / 65535.0; + float wear = item.wear / 65535.0f; int progressmid = wear * progressrect.UpperLeftCorner.X + - (1-wear) * progressrect.LowerRightCorner.X; + (1 - wear) * progressrect.LowerRightCorner.X; // Compute progressbar color // wear = 0.0: green // wear = 0.5: yellow // wear = 1.0: red - video::SColor color(255,255,255,255); + video::SColor color(255, 255, 255, 255); int wear_i = MYMIN(std::floor(wear * 600), 511); wear_i = MYMIN(wear_i + 10, 511); + if (wear_i <= 255) color.set(255, wear_i, 255, 0); else @@ -760,18 +780,17 @@ void drawItemStack(video::IVideoDriver *driver, progressrect2.LowerRightCorner.X = progressmid; driver->draw2DRectangle(color, progressrect2, clip); - color = video::SColor(255,0,0,0); + color = video::SColor(255, 0, 0, 0); progressrect2 = progressrect; progressrect2.UpperLeftCorner.X = progressmid; driver->draw2DRectangle(color, progressrect2, clip); } - if(font != NULL && item.count >= 2) - { + if (font != NULL && item.count >= 2) { // Get the item count as a string std::string text = itos(item.count); v2u32 dim = font->getDimension(utf8_to_wide(text).c_str()); - v2s32 sdim(dim.X,dim.Y); + v2s32 sdim(dim.X, dim.Y); core::rect<s32> rect2( /*rect.UpperLeftCorner, @@ -780,10 +799,23 @@ void drawItemStack(video::IVideoDriver *driver, sdim ); - video::SColor bgcolor(128,0,0,0); + video::SColor bgcolor(128, 0, 0, 0); driver->draw2DRectangle(bgcolor, rect2, clip); - video::SColor color(255,255,255,255); + video::SColor color(255, 255, 255, 255); font->draw(text.c_str(), rect2, color, false, false, clip); } } + +void drawItemStack( + video::IVideoDriver *driver, + gui::IGUIFont *font, + const ItemStack &item, + const core::rect<s32> &rect, + const core::rect<s32> *clip, + Client *client, + ItemRotationKind rotation_kind) +{ + drawItemStack(driver, font, item, rect, clip, client, rotation_kind, + v3s16(0, 0, 0), v3s16(0, 100, 0)); +} diff --git a/src/client/hud.h b/src/client/hud.h index 693d2adee..d9b5e0686 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -122,6 +122,7 @@ enum ItemRotationKind IT_ROT_SELECTED, IT_ROT_HOVERED, IT_ROT_DRAGGED, + IT_ROT_OTHER, IT_ROT_NONE, // Must be last, also serves as number }; @@ -133,4 +134,15 @@ void drawItemStack(video::IVideoDriver *driver, Client *client, ItemRotationKind rotation_kind); +void drawItemStack( + video::IVideoDriver *driver, + gui::IGUIFont *font, + const ItemStack &item, + const core::rect<s32> &rect, + const core::rect<s32> *clip, + Client *client, + ItemRotationKind rotation_kind, + const v3s16 &angle, + const v3s16 &rotation_speed); + #endif diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index 646d181e0..6a0e9f569 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -109,6 +109,7 @@ static const struct table_key table[] = { DEFINEKEY1(KEY_RETURN, N_("Return")) DEFINEKEY1(KEY_SHIFT, N_("Shift")) DEFINEKEY1(KEY_CONTROL, N_("Control")) + //~ Key name, common on Windows keyboards DEFINEKEY1(KEY_MENU, N_("Menu")) DEFINEKEY1(KEY_PAUSE, N_("Pause")) DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock")) @@ -121,7 +122,9 @@ static const struct table_key table[] = { DEFINEKEY1(KEY_UP, N_("Up")) DEFINEKEY1(KEY_RIGHT, N_("Right")) DEFINEKEY1(KEY_DOWN, N_("Down")) + //~ Key name DEFINEKEY1(KEY_SELECT, N_("Select")) + //~ "Print screen" key DEFINEKEY1(KEY_PRINT, N_("Print")) DEFINEKEY1(KEY_EXECUT, N_("Execute")) DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot")) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index c086d860a..c20c3619f 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -184,8 +184,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, v3f position = getPosition(); // Copy parent position if local player is attached - if (isAttached) { - setPosition(overridePosition); + if (getParent()) { + setPosition(m_cao->getPosition()); added_velocity = v3f(0.0f); // ignored return; } @@ -474,7 +474,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env) setYaw(control.yaw); // Nullify speed and don't run positioning code if the player is attached - if (isAttached) { + if (getParent()) { setSpeed(v3f(0.0f)); return; } @@ -706,6 +706,11 @@ v3f LocalPlayer::getEyeOffset() const return v3f(0.0f, BS * eye_height, 0.0f); } +ClientActiveObject *LocalPlayer::getParent() const +{ + return m_cao ? m_cao->getParent() : nullptr; +} + bool LocalPlayer::isDead() const { FATAL_ERROR_IF(!getCAO(), "LocalPlayer's CAO isn't initialized"); @@ -764,8 +769,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, v3f position = getPosition(); // Copy parent position if local player is attached - if (isAttached) { - setPosition(overridePosition); + if (getParent()) { + setPosition(m_cao->getPosition()); m_sneak_node_exists = false; added_velocity = v3f(0.0f); return; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 45dc6776e..95dceb1f4 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -47,12 +47,9 @@ public: LocalPlayer(Client *client, const char *name); virtual ~LocalPlayer() = default; - ClientActiveObject *parent = nullptr; - // Initialize hp to 0, so that no hearts will be shown if server // doesn't support health points u16 hp = 0; - bool isAttached = false; bool touching_ground = false; // This oscillates so that the player jumps a bit above the surface bool in_liquid = false; @@ -72,8 +69,6 @@ public: // Temporary option for old move code bool physics_override_new_move = true; - v3f overridePosition; - void move(f32 dtime, Environment *env, f32 pos_max_d); void move(f32 dtime, Environment *env, f32 pos_max_d, std::vector<CollisionInfo> *collision_info); @@ -112,6 +107,8 @@ public: GenericCAO *getCAO() const { return m_cao; } + ClientActiveObject *getParent() const; + void setCAO(GenericCAO *toset) { assert(!m_cao); // Pre-condition diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 2bfaa7a4f..a5bee6b88 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -795,6 +795,7 @@ static void getTileInfo( v3s16 &p_corrected, v3s16 &face_dir_corrected, u16 *lights, + u8 &waving, TileSpec &tile ) { @@ -842,6 +843,7 @@ static void getTileInfo( getNodeTile(n, p_corrected, face_dir_corrected, data, tile); const ContentFeatures &f = ndef->get(n); + waving = f.waving; tile.emissive_light = f.light_source; // eg. water and glass @@ -876,6 +878,10 @@ static void updateFastFaceRow( const v3s16 &&face_dir, std::vector<FastFace> &dest) { + static thread_local const bool waving_liquids = + g_settings->getBool("enable_shaders") && + g_settings->getBool("enable_waving_water"); + v3s16 p = startpos; u16 continuous_tiles_count = 1; @@ -884,10 +890,11 @@ static void updateFastFaceRow( v3s16 p_corrected; v3s16 face_dir_corrected; u16 lights[4] = {0, 0, 0, 0}; + u8 waving; TileSpec tile; getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, - lights, tile); + lights, waving, tile); // Unroll this variable which has a significant build cost TileSpec next_tile; @@ -910,12 +917,15 @@ static void updateFastFaceRow( getTileInfo(data, p_next, face_dir, next_makes_face, next_p_corrected, next_face_dir_corrected, next_lights, + waving, next_tile); if (next_makes_face == makes_face && next_p_corrected == p_corrected + translate_dir && next_face_dir_corrected == face_dir_corrected && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0 + // Don't apply fast faces to waving water. + && (waving != 3 || !waving_liquids) && next_tile.isTileable(tile)) { next_is_different = false; continuous_tiles_count++; diff --git a/src/client/shader.cpp b/src/client/shader.cpp index f36ff3d85..eda415ce6 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -188,7 +188,7 @@ public: delete setter; } - virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) + virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override { video::IVideoDriver *driver = services->getVideoDriver(); sanity_check(driver != NULL); @@ -198,6 +198,12 @@ public: for (IShaderConstantSetter *setter : m_setters) setter->onSetConstants(services, is_highlevel); } + + virtual void OnSetMaterial(const video::SMaterial& material) override + { + for (IShaderConstantSetter *setter : m_setters) + setter->onSetMaterial(material); + } }; diff --git a/src/client/shader.h b/src/client/shader.h index 583c776f4..109d39336 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -67,6 +67,8 @@ public: virtual ~IShaderConstantSetter() = default; virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) = 0; + virtual void onSetMaterial(const video::SMaterial& material) + { } }; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 346cd0642..7a7b188ce 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -18,22 +18,23 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "sky.h" +#include "ITexture.h" #include "IVideoDriver.h" #include "ISceneManager.h" #include "ICameraSceneNode.h" #include "S3DVertex.h" #include "client/tile.h" -#include "noise.h" // easeCurve +#include "noise.h" // easeCurve #include "profiler.h" #include "util/numeric.h" #include <cmath> #include "client/renderingengine.h" #include "settings.h" -#include "camera.h" // CameraModes +#include "camera.h" // CameraModes #include "config.h" +using namespace irr::core; - -Sky::Sky(s32 id, ITextureSource *tsrc): +Sky::Sky(s32 id, ITextureSource *tsrc) : scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(), RenderingEngine::get_scene_manager(), id) { @@ -67,44 +68,51 @@ Sky::Sky(s32 id, ITextureSource *tsrc): m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; - m_sun_texture = tsrc->isKnownSourceImage("sun.png") ? - tsrc->getTextureForMesh("sun.png") : NULL; - m_moon_texture = tsrc->isKnownSourceImage("moon.png") ? - tsrc->getTextureForMesh("moon.png") : NULL; - m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ? - tsrc->getTexture("sun_tonemap.png") : NULL; - m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ? - tsrc->getTexture("moon_tonemap.png") : NULL; + // Ensures that sun and moon textures and tonemaps are correct. + setSkyDefaults(); + m_sun_texture = tsrc->isKnownSourceImage(m_sun_params.texture) ? + tsrc->getTextureForMesh(m_sun_params.texture) : NULL; + m_moon_texture = tsrc->isKnownSourceImage(m_moon_params.texture) ? + tsrc->getTextureForMesh(m_moon_params.texture) : NULL; + m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ? + tsrc->getTexture(m_sun_params.tonemap) : NULL; + m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? + tsrc->getTexture(m_moon_params.tonemap) : NULL; if (m_sun_texture) { m_materials[3] = mat; m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Disables texture filtering + m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[3].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + // Use tonemaps if available if (m_sun_tonemap) m_materials[3].Lighting = true; } - if (m_moon_texture) { m_materials[4] = mat; m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Disables texture filtering + m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[4].setFlag(video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + // Use tonemaps if available if (m_moon_tonemap) m_materials[4].Lighting = true; } - for (v3f &star : m_stars) { - star = v3f( - myrand_range(-10000, 10000), - myrand_range(-10000, 10000), - myrand_range(-10000, 10000) - ); - star.normalize(); + for (int i = 5; i < 11; i++) { + m_materials[i] = mat; + m_materials[i].Lighting = true; + m_materials[i].MaterialType = video::EMT_SOLID; } - m_directional_colored_fog = g_settings->getBool("directional_colored_fog"); + setStarCount(1000, true); } - void Sky::OnRegisterSceneNode() { if (IsVisible) @@ -113,12 +121,8 @@ void Sky::OnRegisterSceneNode() scene::ISceneNode::OnRegisterSceneNode(); } - void Sky::render() { - if (!m_visible) - return; - video::IVideoDriver *driver = SceneManager->getVideoDriver(); scene::ICameraSceneNode *camera = SceneManager->getActiveCamera(); @@ -205,143 +209,103 @@ void Sky::render() video::SColor cloudyfogcolor = m_bgcolor; - // Draw far cloudy fog thing blended with skycolor - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); - vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); - } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); - } + // Abort rendering if we're in the clouds. + // Stops rendering a pure white hole in the bottom of the skybox. + if (m_in_clouds) + return; - // Draw far cloudy fog thing at and below all horizons - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); + // Draw the six sided skybox, + if (m_sky_params.textures.size() == 6) { + for (u32 j = 5; j < 11; j++) { + video::SColor c(255, 255, 255, 255); + driver->setMaterial(m_materials[j]); + // Use 1.05 rather than 1.0 to avoid colliding with the + // sun, moon and stars, as this is a background skybox. + vertices[0] = video::S3DVertex(-1.05, -1.05, -1.05, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1.05, -1.05, -1.05, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1.05, 1.05, -1.05, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1.05, 1.05, -1.05, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 5) { // Top texture + vertex.Pos.rotateYZBy(90); + vertex.Pos.rotateXZBy(90); + } else if (j == 6) { // Bottom texture + vertex.Pos.rotateYZBy(-90); + vertex.Pos.rotateXZBy(90); + } else if (j == 7) { // Left texture + vertex.Pos.rotateXZBy(90); + } else if (j == 8) { // Right texture + vertex.Pos.rotateXZBy(-90); + } else if (j == 9) { // Front texture, do nothing + // Irrlicht doesn't like it when vertexes are left + // alone and not rotated for some reason. + vertex.Pos.rotateXZBy(0); + } else {// Back texture + vertex.Pos.rotateXZBy(180); + } + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - // If sun, moon and stars are (temporarily) disabled, abort here - if (!m_bodies_visible) - return; - - // Draw stars before sun and moon to be behind them - do { + // Draw far cloudy fog thing blended with skycolor + if (m_visible) { driver->setMaterial(m_materials[1]); - // Tune values so that stars first appear just after the sun - // disappears over the horizon, and disappear just before the sun - // appears over the horizon. - // Also tune so that stars are at full brightness from time 20000 to - // time 4000. - float starbrightness = MYMAX(0, MYMIN(1, - (0.25 - fabs(wicked_time_of_day < 0.5 ? - wicked_time_of_day : (1.0 - wicked_time_of_day))) * 20)); - float f = starbrightness; - float d = 0.007 / 2; - video::SColor starcolor(255, f * 90, f * 90, f * 90); - // Stars are only drawn when brighter than skycolor - if (starcolor.getBlue() < m_skycolor.getBlue()) - break; -#if ENABLE_GLES - u16 indices[SKY_STAR_COUNT * 3]; - video::S3DVertex vertices[SKY_STAR_COUNT * 3]; - for (u32 i = 0; i < SKY_STAR_COUNT; i++) { - indices[i * 3 + 0] = i * 3 + 0; - indices[i * 3 + 1] = i * 3 + 1; - indices[i * 3 + 2] = i * 3 + 2; - v3f r = m_stars[i]; - core::CMatrix4<f32> a; - a.buildRotateFromTo(v3f(0, 1, 0), r); - v3f p = v3f(-d, 1, -d); - v3f p1 = v3f(d, 1, 0); - v3f p2 = v3f(-d, 1, d); - a.rotateVect(p); - a.rotateVect(p1); - a.rotateVect(p2); - p.rotateXYBy(wicked_time_of_day * 360 - 90); - p1.rotateXYBy(wicked_time_of_day * 360 - 90); - p2.rotateXYBy(wicked_time_of_day * 360 - 90); - vertices[i * 3 + 0].Pos = p; - vertices[i * 3 + 0].Color = starcolor; - vertices[i * 3 + 1].Pos = p1; - vertices[i * 3 + 1].Color = starcolor; - vertices[i * 3 + 2].Pos = p2; - vertices[i * 3 + 2].Color = starcolor; + for (u32 j = 0; j < 4; j++) { + video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45); + vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 0) + // Don't switch + {} + else if (j == 1) + // Switch from -Z (south) to +X (east) + vertex.Pos.rotateXZBy(90); + else if (j == 2) + // Switch from -Z (south) to -X (west) + vertex.Pos.rotateXZBy(-90); + else + // Switch from -Z (south) to +Z (north) + vertex.Pos.rotateXZBy(-180); + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - driver->drawIndexedTriangleList(vertices, SKY_STAR_COUNT * 3, - indices, SKY_STAR_COUNT); -#else - u16 indices[SKY_STAR_COUNT * 4]; - video::S3DVertex vertices[SKY_STAR_COUNT * 4]; - for (u32 i = 0; i < SKY_STAR_COUNT; i++) { - indices[i * 4 + 0] = i * 4 + 0; - indices[i * 4 + 1] = i * 4 + 1; - indices[i * 4 + 2] = i * 4 + 2; - indices[i * 4 + 3] = i * 4 + 3; - v3f r = m_stars[i]; - core::CMatrix4<f32> a; - a.buildRotateFromTo(v3f(0, 1, 0), r); - v3f p = v3f(-d, 1, -d); - v3f p1 = v3f( d, 1, -d); - v3f p2 = v3f( d, 1, d); - v3f p3 = v3f(-d, 1, d); - a.rotateVect(p); - a.rotateVect(p1); - a.rotateVect(p2); - a.rotateVect(p3); - p.rotateXYBy(wicked_time_of_day * 360 - 90); - p1.rotateXYBy(wicked_time_of_day * 360 - 90); - p2.rotateXYBy(wicked_time_of_day * 360 - 90); - p3.rotateXYBy(wicked_time_of_day * 360 - 90); - vertices[i * 4 + 0].Pos = p; - vertices[i * 4 + 0].Color = starcolor; - vertices[i * 4 + 1].Pos = p1; - vertices[i * 4 + 1].Color = starcolor; - vertices[i * 4 + 2].Pos = p2; - vertices[i * 4 + 2].Color = starcolor; - vertices[i * 4 + 3].Pos = p3; - vertices[i * 4 + 3].Color = starcolor; + + // Draw far cloudy fog thing at and below all horizons + for (u32 j = 0; j < 4; j++) { + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 0) + // Don't switch + {} + else if (j == 1) + // Switch from -Z (south) to +X (east) + vertex.Pos.rotateXZBy(90); + else if (j == 2) + // Switch from -Z (south) to -X (west) + vertex.Pos.rotateXZBy(-90); + else + // Switch from -Z (south) to +Z (north) + vertex.Pos.rotateXZBy(-180); + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4, - indices, SKY_STAR_COUNT, video::EVT_STANDARD, - scene::EPT_QUADS, video::EIT_16BIT); -#endif - } while (false); + } + + // Draw stars before sun and moon to be behind them + if (m_star_params.visible) + draw_stars(driver, wicked_time_of_day); - // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png) - { + // Draw sunrise/sunset horizon glow texture + // (textures/base/pack/sunrisebg.png) + if (m_sun_params.sunrise_visible) { driver->setMaterial(m_materials[2]); float mid1 = 0.25; float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1); @@ -366,53 +330,52 @@ void Sky::render() } // Draw sun - if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) { + if (m_sun_params.visible) draw_sun(driver, sunsize, suncolor, suncolor2, wicked_time_of_day); - } // Draw moon - if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) { + if (m_moon_params.visible) draw_moon(driver, moonsize, mooncolor, mooncolor2, wicked_time_of_day); - } // Draw far cloudy fog thing below all horizons in front of sun, moon // and stars. - driver->setMaterial(m_materials[1]); + if (m_visible) { + driver->setMaterial(m_materials[1]); - for (u32 j = 0; j < 4; j++) { - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); - vertices[2] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, c, o, o); - vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o); - for (video::S3DVertex &vertex : vertices) { - if (j == 0) - // Don't switch - {} - else if (j == 1) - // Switch from -Z (south) to +X (east) - vertex.Pos.rotateXZBy(90); - else if (j == 2) - // Switch from -Z (south) to -X (west) - vertex.Pos.rotateXZBy(-90); - else - // Switch from -Z (south) to +Z (north) - vertex.Pos.rotateXZBy(-180); + for (u32 j = 0; j < 4; j++) { + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t); + vertices[2] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, c, o, o); + vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o); + for (video::S3DVertex &vertex : vertices) { + if (j == 0) + // Don't switch + {} + else if (j == 1) + // Switch from -Z (south) to +X (east) + vertex.Pos.rotateXZBy(90); + else if (j == 2) + // Switch from -Z (south) to -X (west) + vertex.Pos.rotateXZBy(-90); + else + // Switch from -Z (south) to +Z (north) + vertex.Pos.rotateXZBy(-180); + } + driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } + + // Draw bottom far cloudy fog thing in front of sun, moon and stars + video::SColor c = cloudyfogcolor; + vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t); + vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); + vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); + vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } - - // Draw bottom far cloudy fog thing in front of sun, moon and stars - video::SColor c = cloudyfogcolor; - vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t); - vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t); - vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o); - vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o); - driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2); } } - void Sky::update(float time_of_day, float time_brightness, float direct_brightness, bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch) @@ -426,7 +389,7 @@ void Sky::update(float time_of_day, float time_brightness, m_first_update = false; for (u32 i = 0; i < 100; i++) { update(time_of_day, time_brightness, direct_brightness, - sunlight_seen, cam_mode, yaw, pitch); + sunlight_seen, cam_mode, yaw, pitch); } return; } @@ -434,7 +397,7 @@ void Sky::update(float time_of_day, float time_brightness, m_time_of_day = time_of_day; m_time_brightness = time_brightness; m_sunlight_seen = sunlight_seen; - m_bodies_visible = true; + m_in_clouds = false; bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35); @@ -452,19 +415,17 @@ void Sky::update(float time_of_day, float time_brightness, video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5); */ - video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240); - video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100); - video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240); - video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255); + video::SColorf bgcolor_bright_normal_f = m_sky_params.sky_color.day_horizon; + video::SColorf bgcolor_bright_indoor_f = m_sky_params.sky_color.indoors; + video::SColorf bgcolor_bright_dawn_f = m_sky_params.sky_color.dawn_horizon; + video::SColorf bgcolor_bright_night_f = m_sky_params.sky_color.night_horizon; - video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250); - video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250); - video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255); + video::SColorf skycolor_bright_normal_f = m_sky_params.sky_color.day_sky; + video::SColorf skycolor_bright_dawn_f = m_sky_params.sky_color.dawn_sky; + video::SColorf skycolor_bright_night_f = m_sky_params.sky_color.night_sky; - // pure white: becomes "diffuse light component" for clouds - video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255); - // dawn-factoring version of pure white (note: R is above 1.0) - video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f); + video::SColorf cloudcolor_bright_normal_f = m_cloudcolor_day_f; + video::SColorf cloudcolor_bright_dawn_f = m_cloudcolor_dawn_f; float cloud_color_change_fraction = 0.95; if (sunlight_seen) { @@ -558,13 +519,17 @@ void Sky::update(float time_of_day, float time_brightness, f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1); video::SColorf pointcolor_sun_f(1, 1, 1, 1); - if (m_sun_tonemap) { + // Use tonemap only if default sun/moon tinting is used + // which keeps previous behaviour. + if (m_sun_tonemap && m_default_tint) { pointcolor_sun_f.r = pointcolor_light * (float)m_materials[3].EmissiveColor.getRed() / 255; pointcolor_sun_f.b = pointcolor_light * (float)m_materials[3].EmissiveColor.getBlue() / 255; pointcolor_sun_f.g = pointcolor_light * (float)m_materials[3].EmissiveColor.getGreen() / 255; + } else if (!m_default_tint) { + pointcolor_sun_f = m_sky_params.sun_tint; } else { pointcolor_sun_f.r = pointcolor_light * 1; pointcolor_sun_f.b = pointcolor_light * @@ -573,9 +538,23 @@ void Sky::update(float time_of_day, float time_brightness, (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625); } - video::SColorf pointcolor_moon_f(0.5 * pointcolor_light, - 0.6 * pointcolor_light, 0.8 * pointcolor_light, 1); - if (m_moon_tonemap) { + video::SColorf pointcolor_moon_f; + if (m_default_tint) { + pointcolor_moon_f = video::SColorf( + 0.5 * pointcolor_light, + 0.6 * pointcolor_light, + 0.8 * pointcolor_light, + 1 + ); + } else { + pointcolor_moon_f = video::SColorf( + (m_sky_params.moon_tint.getRed() / 255) * pointcolor_light, + (m_sky_params.moon_tint.getGreen() / 255) * pointcolor_light, + (m_sky_params.moon_tint.getBlue() / 255) * pointcolor_light, + 1 + ); + } + if (m_moon_tonemap && m_default_tint) { pointcolor_moon_f.r = pointcolor_light * (float)m_materials[4].EmissiveColor.getRed() / 255; pointcolor_moon_f.b = pointcolor_light * @@ -640,7 +619,12 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol std::array<video::S3DVertex, 4> vertices; if (!m_sun_texture) { driver->setMaterial(m_materials[1]); - const float sunsizes[4] = {sunsize * 1.7f, sunsize * 1.2f, sunsize, sunsize * 0.7f}; + const float sunsizes[4] = { + (sunsize * 1.7f) * m_sun_params.scale, + (sunsize * 1.2f) * m_sun_params.scale, + (sunsize) * m_sun_params.scale, + (sunsize * 0.7f) * m_sun_params.scale + }; video::SColor c1 = suncolor; video::SColor c2 = suncolor; c1.setAlpha(0.05 * 255); @@ -653,7 +637,7 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol } } else { driver->setMaterial(m_materials[3]); - float d = sunsize * 1.7; + float d = (sunsize * 1.7) * m_sun_params.scale; video::SColor c; if (m_sun_tonemap) c = video::SColor(0, 0, 0, 0); @@ -668,31 +652,32 @@ void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SCol void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor, const video::SColor &mooncolor2, float wicked_time_of_day) - /* - * Draw moon in the sky. - * driver: Video driver object used to draw - * moonsize: the default size of the moon - * mooncolor: main moon color - * mooncolor2: second moon color - * wicked_time_of_day: current time of day, to know where should be the moon in the sky - */ +/* + * Draw moon in the sky. + * driver: Video driver object used to draw + * moonsize: the default size of the moon + * mooncolor: main moon color + * mooncolor2: second moon color + * wicked_time_of_day: current time of day, to know where should be the moon in + * the sky + */ { static const u16 indices[4] = {0, 1, 2, 3}; std::array<video::S3DVertex, 4> vertices; if (!m_moon_texture) { driver->setMaterial(m_materials[1]); const float moonsizes_1[4] = { - -moonsize * 1.9f, - -moonsize * 1.3f, - -moonsize, - -moonsize - }; + (-moonsize * 1.9f) * m_moon_params.scale, + (-moonsize * 1.3f) * m_moon_params.scale, + (-moonsize) * m_moon_params.scale, + (-moonsize) * m_moon_params.scale + }; const float moonsizes_2[4] = { - moonsize * 1.9f, - moonsize * 1.3f, - moonsize, - moonsize * 0.6f - }; + (moonsize * 1.9f) * m_moon_params.scale, + (moonsize * 1.3f) * m_moon_params.scale, + (moonsize) *m_moon_params.scale, + (moonsize * 0.6f) * m_moon_params.scale + }; video::SColor c1 = mooncolor; video::SColor c2 = mooncolor; c1.setAlpha(0.05 * 255); @@ -705,7 +690,7 @@ void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SC } } else { driver->setMaterial(m_materials[4]); - float d = moonsize * 1.9; + float d = (moonsize * 1.9) * m_moon_params.scale; video::SColor c; if (m_moon_tonemap) c = video::SColor(0, 0, 0, 0); @@ -717,14 +702,106 @@ void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SC } } +void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) +{ + driver->setMaterial(m_materials[1]); + // Tune values so that stars first appear just after the sun + // disappears over the horizon, and disappear just before the sun + // appears over the horizon. + // Also tune so that stars are at full brightness from time 20000 + // to time 4000. + + float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day); + float starbrightness = clamp((0.25f - fabsf(tod)) * 20.0f, 0.0f, 1.0f); + + float f = starbrightness; + float d = (0.006 / 2) * m_star_params.scale; + + video::SColor starcolor = m_star_params.starcolor; + starcolor.setAlpha(f * m_star_params.starcolor.getAlpha()); + + // Stars are only drawn when not fully transparent + if (m_star_params.starcolor.getAlpha() < 1) + return; +#if ENABLE_GLES + u16 *indices = new u16[m_star_params.count * 3]; + video::S3DVertex *vertices = + new video::S3DVertex[m_star_params.count * 3]; + for (u32 i = 0; i < m_star_params.count; i++) { + indices[i * 3 + 0] = i * 3 + 0; + indices[i * 3 + 1] = i * 3 + 1; + indices[i * 3 + 2] = i * 3 + 2; + v3f r = m_stars[i]; + core::CMatrix4<f32> a; + a.buildRotateFromTo(v3f(0, 1, 0), r); + v3f p = v3f(-d, 1, -d); + v3f p1 = v3f(d, 1, 0); + v3f p2 = v3f(-d, 1, d); + a.rotateVect(p); + a.rotateVect(p1); + a.rotateVect(p2); + p.rotateXYBy(wicked_time_of_day * 360 - 90); + p1.rotateXYBy(wicked_time_of_day * 360 - 90); + p2.rotateXYBy(wicked_time_of_day * 360 - 90); + vertices[i * 3 + 0].Pos = p; + vertices[i * 3 + 0].Color = starcolor; + vertices[i * 3 + 1].Pos = p1; + vertices[i * 3 + 1].Color = starcolor; + vertices[i * 3 + 2].Pos = p2; + vertices[i * 3 + 2].Color = starcolor; + } + driver->drawIndexedTriangleList(vertices, m_star_params.count * 3, + indices, m_star_params.count); + delete[] indices; + delete[] vertices; +#else + u16 *indices = new u16[m_star_params.count * 4]; + video::S3DVertex *vertices = + new video::S3DVertex[m_star_params.count * 4]; + for (u32 i = 0; i < m_star_params.count; i++) { + indices[i * 4 + 0] = i * 4 + 0; + indices[i * 4 + 1] = i * 4 + 1; + indices[i * 4 + 2] = i * 4 + 2; + indices[i * 4 + 3] = i * 4 + 3; + v3f r = m_stars[i]; + core::CMatrix4<f32> a; + a.buildRotateFromTo(v3f(0, 1, 0), r); + v3f p = v3f(-d, 1, -d); + v3f p1 = v3f(d, 1, -d); + v3f p2 = v3f(d, 1, d); + v3f p3 = v3f(-d, 1, d); + a.rotateVect(p); + a.rotateVect(p1); + a.rotateVect(p2); + a.rotateVect(p3); + p.rotateXYBy(wicked_time_of_day * 360 - 90); + p1.rotateXYBy(wicked_time_of_day * 360 - 90); + p2.rotateXYBy(wicked_time_of_day * 360 - 90); + p3.rotateXYBy(wicked_time_of_day * 360 - 90); + vertices[i * 4 + 0].Pos = p; + vertices[i * 4 + 0].Color = starcolor; + vertices[i * 4 + 1].Pos = p1; + vertices[i * 4 + 1].Color = starcolor; + vertices[i * 4 + 2].Pos = p2; + vertices[i * 4 + 2].Color = starcolor; + vertices[i * 4 + 3].Pos = p3; + vertices[i * 4 + 3].Color = starcolor; + } + driver->drawVertexPrimitiveList(vertices, m_star_params.count * 4, + indices, m_star_params.count, video::EVT_STANDARD, + scene::EPT_QUADS, video::EIT_16BIT); + delete[] indices; + delete[] vertices; +#endif +} void Sky::draw_sky_body(std::array<video::S3DVertex, 4> &vertices, float pos_1, float pos_2, const video::SColor &c) { /* - * Create an array of vertices with the dimensions specified. - * pos_1, pos_2: position of the body's vertices - * c: color of the body - */ + * Create an array of vertices with the dimensions specified. + * pos_1, pos_2: position of the body's vertices + * c: color of the body + */ const f32 t = 1.0f; const f32 o = 0.0f; @@ -738,11 +815,11 @@ void Sky::draw_sky_body(std::array<video::S3DVertex, 4> &vertices, float pos_1, void Sky::place_sky_body( std::array<video::S3DVertex, 4> &vertices, float horizon_position, float day_position) /* - * Place body in the sky. - * vertices: The body as a rectangle of 4 vertices - * horizon_position: turn the body around the Y axis - * day_position: turn the body around the Z axis, to place it depending of the time of the day - */ + * Place body in the sky. + * vertices: The body as a rectangle of 4 vertices + * horizon_position: turn the body around the Y axis + * day_position: turn the body around the Z axis, to place it depending of the time of the day + */ { for (video::S3DVertex &vertex : vertices) { // Body is directed to -Z (south) by default @@ -750,3 +827,151 @@ void Sky::place_sky_body( vertex.Pos.rotateXYBy(day_position); } } + +void Sky::setSunTexture(std::string sun_texture, + std::string sun_tonemap, ITextureSource *tsrc) +{ + // Ignore matching textures (with modifiers) entirely, + // but lets at least update the tonemap before hand. + m_sun_params.tonemap = sun_tonemap; + m_sun_tonemap = tsrc->isKnownSourceImage(m_sun_params.tonemap) ? + tsrc->getTexture(m_sun_params.tonemap) : NULL; + m_materials[3].Lighting = !!m_sun_tonemap; + + if (m_sun_params.texture == sun_texture) + return; + m_sun_params.texture = sun_texture; + + if (sun_texture != "") { + // We want to ensure the texture exists first. + m_sun_texture = tsrc->getTextureForMesh(m_sun_params.texture); + + if (m_sun_texture) { + m_materials[3] = m_materials[0]; + m_materials[3].setTexture(0, m_sun_texture); + m_materials[3].MaterialType = video:: + EMT_TRANSPARENT_ALPHA_CHANNEL; + // Disables texture filtering + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[3].setFlag( + video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + } + } else { + m_sun_texture = nullptr; + } +} + +void Sky::setSunriseTexture(std::string sunglow_texture, + ITextureSource* tsrc) +{ + // Ignore matching textures (with modifiers) entirely. + if (m_sun_params.sunrise == sunglow_texture) + return; + m_sun_params.sunrise = sunglow_texture; + m_materials[2].setTexture(0, tsrc->getTextureForMesh( + sunglow_texture.empty() ? "sunrisebg.png" : sunglow_texture) + ); +} + +void Sky::setMoonTexture(std::string moon_texture, + std::string moon_tonemap, ITextureSource *tsrc) +{ + // Ignore matching textures (with modifiers) entirely, + // but lets at least update the tonemap before hand. + m_moon_params.tonemap = moon_tonemap; + m_moon_tonemap = tsrc->isKnownSourceImage(m_moon_params.tonemap) ? + tsrc->getTexture(m_moon_params.tonemap) : NULL; + m_materials[4].Lighting = !!m_moon_tonemap; + + if (m_moon_params.texture == moon_texture) + return; + m_moon_params.texture = moon_texture; + + if (moon_texture != "") { + // We want to ensure the texture exists first. + m_moon_texture = tsrc->getTextureForMesh(m_moon_params.texture); + + if (m_moon_texture) { + m_materials[4] = m_materials[0]; + m_materials[4].setTexture(0, m_moon_texture); + m_materials[4].MaterialType = video:: + EMT_TRANSPARENT_ALPHA_CHANNEL; + // Disables texture filtering + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_BILINEAR_FILTER, false); + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_TRILINEAR_FILTER, false); + m_materials[4].setFlag( + video::E_MATERIAL_FLAG::EMF_ANISOTROPIC_FILTER, false); + } + } else { + m_moon_texture = nullptr; + } +} + +void Sky::setStarCount(u16 star_count, bool force_update) +{ + // Allow force updating star count at game init. + if (m_star_params.count != star_count || force_update) { + m_star_params.count = star_count; + m_stars.clear(); + // Rebuild the stars surrounding the camera + for (u16 i = 0; i < star_count; i++) { + v3f star = v3f( + myrand_range(-10000, 10000), + myrand_range(-10000, 10000), + myrand_range(-10000, 10000) + ); + + star.normalize(); + m_stars.emplace_back(star); + } + } +} + +void Sky::setSkyColors(const SkyboxParams sky) +{ + m_sky_params.sky_color = sky.sky_color; +} + +void Sky::setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, + std::string use_sun_tint) +{ + // Change sun and moon tinting: + m_sky_params.sun_tint = sun_tint; + m_sky_params.moon_tint = moon_tint; + // Faster than comparing strings every rendering frame + if (use_sun_tint == "default") + m_default_tint = true; + else if (use_sun_tint == "custom") + m_default_tint = false; + else + m_default_tint = true; +} + +void Sky::addTextureToSkybox(std::string texture, int material_id, + ITextureSource *tsrc) +{ + // Sanity check for more than six textures. + if (material_id + 5 >= SKY_MATERIAL_COUNT) + return; + // Keep a list of texture names handy. + m_sky_params.textures.emplace_back(texture); + video::ITexture *result = tsrc->getTextureForMesh(texture); + m_materials[material_id+5] = m_materials[0]; + m_materials[material_id+5].setTexture(0, result); + m_materials[material_id+5].MaterialType = video::EMT_SOLID; +} + +// To be called once at game init to setup default values. +void Sky::setSkyDefaults() +{ + SkyboxDefaults sky_defaults; + m_sky_params.sky_color = sky_defaults.getSkyColorDefaults(); + m_sun_params = sky_defaults.getSunDefaults(); + m_moon_params = sky_defaults.getMoonDefaults(); + m_star_params = sky_defaults.getStarDefaults(); +} diff --git a/src/client/sky.h b/src/client/sky.h index 9cff20e08..8637f96d4 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -21,11 +21,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <array> #include "camera.h" #include "irrlichttypes_extrabloated.h" +#include "skyparams.h" #pragma once -#define SKY_MATERIAL_COUNT 5 -#define SKY_STAR_COUNT 200 +#define SKY_MATERIAL_COUNT 12 class ITextureSource; @@ -45,8 +45,6 @@ public: // Used by Irrlicht for optimizing rendering virtual video::SMaterial &getMaterial(u32 i) { return m_materials[i]; } - - // Used by Irrlicht for optimizing rendering virtual u32 getMaterialCount() const { return SKY_MATERIAL_COUNT; } void update(float m_time_of_day, float time_brightness, float direct_brightness, @@ -64,6 +62,23 @@ public: return m_visible ? m_skycolor : m_fallback_bg_color; } + void setSunVisible(bool sun_visible) { m_sun_params.visible = sun_visible; } + void setSunTexture(std::string sun_texture, + std::string sun_tonemap, ITextureSource *tsrc); + void setSunScale(f32 sun_scale) { m_sun_params.scale = sun_scale; } + void setSunriseVisible(bool glow_visible) { m_sun_params.sunrise_visible = glow_visible; } + void setSunriseTexture(std::string sunglow_texture, ITextureSource* tsrc); + + void setMoonVisible(bool moon_visible) { m_moon_params.visible = moon_visible; } + void setMoonTexture(std::string moon_texture, + std::string moon_tonemap, ITextureSource *tsrc); + void setMoonScale(f32 moon_scale) { m_moon_params.scale = moon_scale; } + + void setStarsVisible(bool stars_visible) { m_star_params.visible = stars_visible; } + void setStarCount(u16 star_count, bool force_update); + void setStarColor(video::SColor star_color) { m_star_params.starcolor = star_color; } + void setStarScale(f32 star_scale) { m_star_params.scale = star_scale; } + bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; } const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } @@ -79,12 +94,16 @@ public: m_bgcolor = bgcolor; m_skycolor = skycolor; } - void setBodiesVisible(bool visible) { m_bodies_visible = visible; } - + void setSkyColors(const SkyboxParams sky); + void setHorizonTint(video::SColor sun_tint, video::SColor moon_tint, + std::string use_sun_tint); + void setInClouds(bool clouds) { m_in_clouds = clouds; } + void clearSkyboxTextures() { m_sky_params.textures.clear(); } + void addTextureToSkybox(std::string texture, int material_id, + ITextureSource *tsrc); private: aabb3f m_box; video::SMaterial m_materials[SKY_MATERIAL_COUNT]; - // How much sun & moon transition should affect horizon color float m_horizon_blend() { @@ -134,25 +153,46 @@ private: bool m_clouds_visible; // Whether clouds are disabled due to player underground bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API bool m_directional_colored_fog; - bool m_bodies_visible = true; // sun, moon, stars + bool m_in_clouds = true; // Prevent duplicating bools to remember old values + 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); video::SColorf m_cloudcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); video::SColor m_bgcolor; video::SColor m_skycolor; video::SColorf m_cloudcolor_f; - v3f m_stars[SKY_STAR_COUNT]; + + // pure white: becomes "diffuse light component" for clouds + video::SColorf m_cloudcolor_day_f = video::SColorf(1, 1, 1, 1); + // dawn-factoring version of pure white (note: R is above 1.0) + video::SColorf m_cloudcolor_dawn_f = video::SColorf( + 255.0f/240.0f, + 223.0f/240.0f, + 191.0f/255.0f + ); + + SkyboxParams m_sky_params; + SunParams m_sun_params; + MoonParams m_moon_params; + StarParams m_star_params; + + bool m_default_tint = true; + + std::vector<v3f> m_stars; + video::ITexture *m_sun_texture; video::ITexture *m_moon_texture; video::ITexture *m_sun_tonemap; video::ITexture *m_moon_tonemap; + void draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor, - const video::SColor &suncolor2, float wicked_time_of_day); + const video::SColor &suncolor2, float wicked_time_of_day); void draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor, - const video::SColor &mooncolor2, float wicked_time_of_day); + const video::SColor &mooncolor2, float wicked_time_of_day); void draw_sky_body(std::array<video::S3DVertex, 4> &vertices, - float pos_1, float pos_2, const video::SColor &c); - void place_sky_body( - std::array<video::S3DVertex, 4> &vertices, float horizon_position, - float day_position); + float pos_1, float pos_2, const video::SColor &c); + void draw_stars(video::IVideoDriver *driver, float wicked_time_of_day); + void place_sky_body(std::array<video::S3DVertex, 4> &vertices, + float horizon_position, float day_position); + void setSkyDefaults(); }; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 3d9e2470a..3189ab28c 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -1812,6 +1812,24 @@ bool TextureSource::generateImagePart(std::string part_of_name, } /* + Calculate the color of a single pixel drawn on top of another pixel. + + This is a little more complicated than just video::SColor::getInterpolated + because getInterpolated does not handle alpha correctly. For example, a + pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a + pixel with alpha=160, while getInterpolated would yield alpha=96. +*/ +static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) +{ + if (dst_c.getAlpha() == 0) + return src_c; + video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f); + out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) * + src_c.getAlpha() * ratio / (255 * 255)); + return out_c; +} + +/* Draw an image on top of an another one, using the alpha channel of the source image @@ -1830,7 +1848,7 @@ static void blit_with_alpha(video::IImage *src, video::IImage *dst, s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } @@ -1853,7 +1871,7 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, video::SColor dst_c = dst->getPixel(dst_x, dst_y); if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } diff --git a/src/clientiface.h b/src/clientiface.h index 2b0ccfbb1..bf95df4a8 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -338,6 +338,7 @@ public: u8 getMajor() const { return m_version_major; } u8 getMinor() const { return m_version_minor; } u8 getPatch() const { return m_version_patch; } + const std::string &getFull() const { return m_full_version; } private: // Version is stored in here after INIT before INIT2 u8 m_pending_serialization_version = SER_FMT_VER_INVALID; diff --git a/src/constants.h b/src/constants.h index 5ddb54656..7636b38e0 100644 --- a/src/constants.h +++ b/src/constants.h @@ -22,9 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc., /* All kinds of constants. - Cross-platform compatibility crap should go in porting.h. + Cross-platform compatibility stuff should go in porting.h. - Some things here are legacy crap. + Some things here are legacy. */ /* diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 39cdc056f..bf947cf85 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "log.h" #include "util/strfnd.h" -#include "defaultsettings.h" // for override_default_settings +#include "defaultsettings.h" // for set_default_settings #include "mapgen/mapgen.h" // for MapgenParams #include "util/string.h" @@ -298,7 +298,7 @@ bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamesp set_default_settings(g_settings); Settings game_defaults; getGameMinetestConfig(gamespec.path, game_defaults); - override_default_settings(g_settings, &game_defaults); + g_settings->overrideDefaults(&game_defaults); infostream << "Initializing world at " << path << std::endl; diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 87e6466a9..75c3eaf37 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -653,7 +653,7 @@ u16 LuaEntitySAO::punch(v3f dir, if (!damage_handled) { if (result.did_punch) { setHP((s32)getHP() - result.damage, - PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); std::string str = gob_cmd_punched(getHP()); // create message and add to list @@ -663,10 +663,10 @@ u16 LuaEntitySAO::punch(v3f dir, } if (getHP() == 0 && !isGone()) { - m_pending_removal = true; clearParentAttachment(); clearChildAttachments(); m_env->getScriptIface()->luaentity_on_death(m_id, puncher); + m_pending_removal = true; } actionstream << puncher->getDescription() << " (id=" << puncher->getId() << @@ -675,6 +675,7 @@ u16 LuaEntitySAO::punch(v3f dir, "), damage=" << (old_hp - (s32)getHP()) << (damage_handled ? " (handled by Lua)" : "") << std::endl; + // TODO: give Lua control over wear return result.wear; } @@ -864,12 +865,11 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p m_peer_id(peer_id_), m_is_singleplayer(is_singleplayer) { - assert(m_peer_id != 0); // pre-condition + SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; m_prop.physical = false; - m_prop.weight = 75; m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); m_prop.pointable = true; @@ -985,7 +985,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) void PlayerSAO::getStaticData(std::string * result) const { - FATAL_ERROR("Deprecated function"); + FATAL_ERROR("Obsolete function"); } void PlayerSAO::step(float dtime, bool send_recommended) @@ -1394,7 +1394,7 @@ bool PlayerSAO::setWieldedItem(const ItemStack &item) void PlayerSAO::disconnected() { - m_peer_id = 0; + m_peer_id = PEER_ID_INEXISTENT; m_pending_removal = true; } diff --git a/src/craftdef.h b/src/craftdef.h index 5971a89bf..7c14e702a 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -113,9 +113,6 @@ struct CraftOutput Example: If ("bucket:bucket_water", "bucket:bucket_empty") is a replacement pair, the crafting input slot that contained a water bucket will contain an empty bucket after crafting. - - Note: replacements only work correctly when stack_max of the item - to be replaced is 1. It is up to the mod writer to ensure this. */ struct CraftReplacements { @@ -410,10 +407,22 @@ public: ICraftDefManager() = default; virtual ~ICraftDefManager() = default; - // The main crafting function + /** + * The main crafting function. + * + * @param input The input grid. + * @param output CraftOutput where the result is placed. + * @param output_replacements A vector of ItemStacks where replacements are + * placed if they cannot be placed in the input. Replacements can be placed + * in the input if the stack of the replaced item has a count of 1. + * @param decrementInput If true, consume or replace input items. + * @param gamedef + * @return true if a result was found, otherwise false. + */ virtual bool getCraftResult(CraftInput &input, CraftOutput &output, std::vector<ItemStack> &output_replacements, bool decrementInput, IGameDef *gamedef) const=0; + virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, IGameDef *gamedef, unsigned limit=0) const=0; diff --git a/src/daynightratio.h b/src/daynightratio.h index 5986da5fc..538767cad 100644 --- a/src/daynightratio.h +++ b/src/daynightratio.h @@ -30,8 +30,8 @@ inline u32 time_to_daynight_ratio(float time_of_day, bool smooth) t = 24000.0f - t; const float values[9][2] = { - {4250.0f + 125.0f, 150.0f}, - {4500.0f + 125.0f, 150.0f}, + {4250.0f + 125.0f, 175.0f}, + {4500.0f + 125.0f, 175.0f}, {4750.0f + 125.0f, 250.0f}, {5000.0f + 125.0f, 350.0f}, {5250.0f + 125.0f, 500.0f}, diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 01ee97a33..472522bf4 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #include "constants.h" #include "porting.h" +#include "mapgen/mapgen.h" // Mapgen::setDefaultSettings #include "util/string.h" void set_default_settings(Settings *settings) @@ -164,7 +165,9 @@ void set_default_settings(Settings *settings) settings->setDefault("fps_max", "60"); settings->setDefault("pause_fps_max", "20"); settings->setDefault("viewing_range", "100"); +#if ENABLE_GLES settings->setDefault("near_plane", "0.1"); +#endif settings->setDefault("screen_w", "1024"); settings->setDefault("screen_h", "600"); settings->setDefault("autosave_screensize", "true"); @@ -292,9 +295,17 @@ void set_default_settings(Settings *settings) #if USE_FREETYPE settings->setDefault("freetype", "true"); settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf")); + settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf")); + settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf")); + settings->setDefault("font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-BoldItalic.ttf")); + settings->setDefault("font_bold", "false"); + settings->setDefault("font_italic", "false"); settings->setDefault("font_shadow", "1"); settings->setDefault("font_shadow_alpha", "127"); settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf")); + settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf")); + settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf")); + settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); settings->setDefault("fallback_font_shadow", "1"); @@ -418,10 +429,10 @@ void set_default_settings(Settings *settings) settings->setDefault("water_level", "1"); settings->setDefault("mapgen_limit", "31000"); settings->setDefault("chunksize", "5"); - settings->setDefault("mg_flags", "caves,dungeons,light,decorations,biomes"); settings->setDefault("fixed_map_seed", ""); settings->setDefault("max_block_generate_distance", "8"); settings->setDefault("enable_mapgen_debug_info", "false"); + Mapgen::setDefaultSettings(settings); // Server list announcing settings->setDefault("server_announce", "false"); @@ -489,10 +500,3 @@ void set_default_settings(Settings *settings) #endif } -void override_default_settings(Settings *settings, Settings *from) -{ - std::vector<std::string> names = from->getNames(); - for (const auto &name : names) { - settings->setDefault(name, from->get(name)); - } -} diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 2307856a4..110a00595 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,16 +1,24 @@ set(gui_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 29aae0836..999c1d237 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -17,7 +17,9 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "client/tile.h" // ITextureSource #include "irrlichttypes_extrabloated.h" +#include "util/string.h" #include <array> #pragma once @@ -29,17 +31,24 @@ public: { TEXTCOLOR, BGCOLOR, + BGCOLOR_HOVERED, + BGCOLOR_PRESSED, NOCLIP, BORDER, BGIMG, + BGIMG_HOVERED, + BGIMG_MIDDLE, BGIMG_PRESSED, + FGIMG, + FGIMG_HOVERED, + FGIMG_PRESSED, ALPHA, NUM_PROPERTIES, NONE }; private: - std::array<bool, NUM_PROPERTIES> property_set; + std::array<bool, NUM_PROPERTIES> property_set{}; std::array<std::string, NUM_PROPERTIES> properties; public: @@ -49,14 +58,28 @@ public: return TEXTCOLOR; } else if (name == "bgcolor") { return BGCOLOR; + } else if (name == "bgcolor_hovered") { + return BGCOLOR_HOVERED; + } else if (name == "bgcolor_pressed") { + return BGCOLOR_PRESSED; } else if (name == "noclip") { return NOCLIP; } else if (name == "border") { return BORDER; } else if (name == "bgimg") { return BGIMG; + } else if (name == "bgimg_hovered") { + return BGIMG_HOVERED; + } else if (name == "bgimg_middle") { + return BGIMG_MIDDLE; } else if (name == "bgimg_pressed") { return BGIMG_PRESSED; + } else if (name == "fgimg") { + return FGIMG; + } else if (name == "fgimg_hovered") { + return FGIMG_HOVERED; + } else if (name == "fgimg_pressed") { + return FGIMG_PRESSED; } else if (name == "alpha") { return ALPHA; } else { @@ -97,6 +120,52 @@ public: return color; } + irr::core::rect<s32> getRect(Property prop, irr::core::rect<s32> def) const + { + const auto &val = properties[prop]; + if (val.empty()) + return def; + + irr::core::rect<s32> rect; + if (!parseRect(val, &rect)) + return def; + + return rect; + } + + irr::core::rect<s32> getRect(Property prop) const + { + const auto &val = properties[prop]; + FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); + + irr::core::rect<s32> rect; + parseRect(val, &rect); + return rect; + } + + video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc, + video::ITexture *def) const + { + const auto &val = properties[prop]; + if (val.empty()) { + return def; + } + + video::ITexture *texture = tsrc->getTexture(val); + + return texture; + } + + video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc) const + { + const auto &val = properties[prop]; + FATAL_ERROR_IF(val.empty(), "Unexpected missing property"); + + video::ITexture *texture = tsrc->getTexture(val); + + return texture; + } + bool getBool(Property prop, bool def) const { const auto &val = properties[prop]; @@ -132,4 +201,36 @@ public: newspec |= other; return newspec; } + +private: + bool parseRect(const std::string &value, irr::core::rect<s32> *parsed_rect) const + { + irr::core::rect<s32> rect; + std::vector<std::string> v_rect = split(value, ','); + + if (v_rect.size() == 1) { + s32 x = stoi(v_rect[0]); + rect.UpperLeftCorner = irr::core::vector2di(x, x); + rect.LowerRightCorner = irr::core::vector2di(-x, -x); + } else if (v_rect.size() == 2) { + s32 x = stoi(v_rect[0]); + s32 y = stoi(v_rect[1]); + rect.UpperLeftCorner = irr::core::vector2di(x, y); + rect.LowerRightCorner = irr::core::vector2di(-x, -y); + // `-x` is interpreted as `w - x` + } else if (v_rect.size() == 4) { + rect.UpperLeftCorner = irr::core::vector2di( + stoi(v_rect[0]), stoi(v_rect[1])); + rect.LowerRightCorner = irr::core::vector2di( + stoi(v_rect[2]), stoi(v_rect[3])); + } else { + warningstream << "Invalid rectangle string format: \"" << value + << "\"" << std::endl; + return false; + } + + *parsed_rect = rect; + + return true; + } }; diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp new file mode 100644 index 000000000..b1447c45f --- /dev/null +++ b/src/gui/guiAnimatedImage.cpp @@ -0,0 +1,73 @@ +#include "guiAnimatedImage.h" + +#include "client/guiscalingfilter.h" +#include "client/tile.h" // ITextureSource +#include "log.h" +#include "porting.h" +#include "util/string.h" +#include <string> +#include <vector> + +GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, const core::rect<s32> &rectangle, const std::string &texture_name, + s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), m_tsrc(tsrc) +{ + m_texture = m_tsrc->getTexture(texture_name); + + m_frame_count = std::max(frame_count, 1); + m_frame_duration = std::max(frame_duration, 0); + + if (m_texture != nullptr) { + core::dimension2d<u32> size = m_texture->getOriginalSize(); + if (size.Height < (u64)m_frame_count) + m_frame_count = size.Height; + } else { + // No need to step an animation if we have nothing to draw + m_frame_count = 1; + } +} + +void GUIAnimatedImage::draw() +{ + // Render the current frame + if (m_texture != nullptr) { + video::IVideoDriver *driver = Environment->getVideoDriver(); + + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + + core::dimension2d<u32> size = m_texture->getOriginalSize(); + size.Height /= m_frame_count; + + draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, + core::rect<s32>(core::position2d<s32>(0, size.Height * m_frame_idx), size), + NoClip ? nullptr : &AbsoluteClippingRect, colors, true); + } + + // Step the animation + if (m_frame_count > 1 && m_frame_duration > 0) { + // Determine the delta time to step + u64 new_global_time = porting::getTimeMs(); + if (m_global_time > 0) + m_frame_time += new_global_time - m_global_time; + + m_global_time = new_global_time; + + // Advance by the number of elapsed frames, looping if necessary + m_frame_idx += u32(m_frame_time / m_frame_duration); + m_frame_idx %= m_frame_count; + + // If 1 or more frames have elapsed, reset the frame time counter with + // the remainder + m_frame_time %= m_frame_duration; + } +} + + +void GUIAnimatedImage::setFrameIndex(s32 frame) +{ + s32 idx = std::max(frame, 0); + if (idx > 0 && idx < m_frame_count) + m_frame_idx = idx; +} diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h new file mode 100644 index 000000000..f8e6a506e --- /dev/null +++ b/src/gui/guiAnimatedImage.h @@ -0,0 +1,28 @@ +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include <string> + +class ISimpleTextureSource; + +class GUIAnimatedImage : public gui::IGUIElement { +public: + GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, const core::rect<s32> &rectangle, const std::string &texture_name, + s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc); + + virtual void draw() override; + + void setFrameIndex(s32 frame); + s32 getFrameIndex() const { return m_frame_idx; }; + +private: + ISimpleTextureSource *m_tsrc; + + video::ITexture *m_texture = nullptr; + u64 m_global_time = 0; + s32 m_frame_idx = 0; + s32 m_frame_count = 1; + u64 m_frame_duration = 1; + u64 m_frame_time = 0; +}; diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp new file mode 100644 index 000000000..21c1e88cf --- /dev/null +++ b/src/gui/guiBackgroundImage.cpp @@ -0,0 +1,69 @@ +/* +Part of Minetest +Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "guiBackgroundImage.h" +#include "client/guiscalingfilter.h" +#include "log.h" + +GUIBackgroundImage::GUIBackgroundImage(gui::IGUIEnvironment *env, + gui::IGUIElement *parent, s32 id, const core::rect<s32> &rectangle, + const std::string &name, const core::rect<s32> &middle, + ISimpleTextureSource *tsrc, bool autoclip) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_name(name), m_middle(middle), m_tsrc(tsrc), m_autoclip(autoclip) +{ +} + +void GUIBackgroundImage::draw() +{ + if (!IsVisible) + return; + + video::ITexture *texture = m_tsrc->getTexture(m_name); + + if (!texture) { + errorstream << "GUIBackgroundImage::draw() Unable to load texture:" + << std::endl; + errorstream << "\t" << m_name << std::endl; + return; + } + + core::rect<s32> rect = AbsoluteRect; + if (m_autoclip) + rect.LowerRightCorner += Parent->getAbsolutePosition().getSize(); + + video::IVideoDriver *driver = Environment->getVideoDriver(); + + if (m_middle.getArea() == 0) { + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + draw2DImageFilterScaled(driver, texture, rect, + core::rect<s32>(core::position2d<s32>(0, 0), + core::dimension2di(texture->getOriginalSize())), + nullptr, colors, true); + } else { + core::rect<s32> middle = m_middle; + // `-x` is interpreted as `w - x` + if (middle.LowerRightCorner.X < 0) + middle.LowerRightCorner.X += texture->getOriginalSize().Width; + if (middle.LowerRightCorner.Y < 0) + middle.LowerRightCorner.Y += texture->getOriginalSize().Height; + draw2DImage9Slice(driver, texture, rect, middle); + } + + IGUIElement::draw(); +} diff --git a/src/gui/guiBackgroundImage.h b/src/gui/guiBackgroundImage.h new file mode 100644 index 000000000..31fbfd09c --- /dev/null +++ b/src/gui/guiBackgroundImage.h @@ -0,0 +1,38 @@ +/* +Part of Minetest +Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "util/string.h" +#include "client/tile.h" // ITextureSource + +class GUIBackgroundImage : public gui::IGUIElement +{ +public: + GUIBackgroundImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect<s32> &rectangle, const std::string &name, + const core::rect<s32> &middle, ISimpleTextureSource *tsrc, bool autoclip); + + virtual void draw() override; + +private: + std::string m_name; + core::rect<s32> m_middle; + ISimpleTextureSource *m_tsrc; + bool m_autoclip; +}; diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp new file mode 100644 index 000000000..7f329cc32 --- /dev/null +++ b/src/gui/guiBox.cpp @@ -0,0 +1,38 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiBox.h" + +GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect<s32> &rectangle, const video::SColor &color) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_color(color) +{ +} + +void GUIBox::draw() +{ + if (!IsVisible) + return; + + Environment->getVideoDriver()->draw2DRectangle(m_color, AbsoluteRect, + &AbsoluteClippingRect); + + IGUIElement::draw(); +} diff --git a/src/gui/guiBox.h b/src/gui/guiBox.h new file mode 100644 index 000000000..5306fdf65 --- /dev/null +++ b/src/gui/guiBox.h @@ -0,0 +1,34 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" + +class GUIBox : public gui::IGUIElement +{ +public: + GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect<s32> &rectangle, const video::SColor &color); + + virtual void draw() override; + +private: + video::SColor m_color; +}; diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 60d330f4a..4c16ee237 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -5,15 +5,25 @@ #include "guiButton.h"
+#include "client/guiscalingfilter.h"
+#include "client/tile.h"
#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IVideoDriver.h"
#include "IGUIFont.h"
+#include "irrlicht_changes/static_text.h"
#include "porting.h"
+#include "StyleSpec.h"
using namespace irr;
using namespace gui;
+// Multiply with a color to get the default corresponding hovered color
+#define COLOR_HOVERED_MOD 1.25f
+
+// Multiply with a color to get the default corresponding pressed color
+#define COLOR_PRESSED_MOD 0.85f
+
//! constructor
GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
s32 id, core::rect<s32> rectangle, bool noclip)
@@ -34,7 +44,17 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent, // PATCH
for (size_t i = 0; i < 4; i++) {
Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
+ HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
+ PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
}
+ StaticText = gui::StaticText::add(Environment, Text.c_str(), core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), false, false, this, id);
+ StaticText->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER);
// END PATCH
}
@@ -182,8 +202,12 @@ bool GUIButton::OnEvent(const SEvent& event) case EET_MOUSE_INPUT_EVENT:
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
{
- if (!IsPushButton)
+ // Sometimes formspec elements can receive mouse events when the
+ // mouse is outside of the formspec. Thus, we test the position here.
+ if ( !IsPushButton && AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y ))) {
setPressed(true);
+ }
return true;
}
@@ -247,13 +271,15 @@ void GUIButton::draw() if (!Pressed)
{
// PATCH
- skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect,
+ isHovered() ? HoveredColors : Colors);
// END PATCH
}
else
{
// PATCH
- skin->drawColored3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ skin->drawColored3DButtonPanePressed(this,
+ AbsoluteRect, &AbsoluteClippingRect, PressedColors);
// END PATCH
}
}
@@ -281,10 +307,25 @@ void GUIButton::draw() }
}
- driver->draw2DImage(ButtonImages[(u32)imageState].Texture,
- ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
- sourceRect, &AbsoluteClippingRect,
- 0, UseAlphaChannel);
+ // PATCH
+ video::ITexture* texture = ButtonImages[(u32)imageState].Texture;
+ if (BgMiddle.getArea() == 0) {
+ driver->draw2DImage(texture,
+ ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
+ sourceRect, &AbsoluteClippingRect,
+ 0, UseAlphaChannel);
+ } else {
+ core::rect<s32> middle = BgMiddle;
+ // `-x` is interpreted as `w - x`
+ if (middle.LowerRightCorner.X < 0)
+ middle.LowerRightCorner.X += texture->getOriginalSize().Width;
+ if (middle.LowerRightCorner.Y < 0)
+ middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
+ draw2DImage9Slice(driver, texture,
+ ScaleImage ? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
+ middle, &AbsoluteClippingRect);
+ }
+ // END PATCH
}
if (SpriteBank)
@@ -302,7 +343,7 @@ void GUIButton::draw() drawSprite(state, FocusTime, pos);
// mouse over / off animation
- state = Environment->getHovered() == this ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF;
+ state = isHovered() ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF;
drawSprite(state, HoverTime, pos);
}
else
@@ -312,23 +353,6 @@ void GUIButton::draw() }
}
- if (Text.size())
- {
- IGUIFont* font = getActiveFont();
-
- core::rect<s32> rect = AbsoluteRect;
- if (Pressed)
- {
- rect.UpperLeftCorner.X += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_X);
- rect.UpperLeftCorner.Y += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y);
- }
-
- if (font)
- font->draw(Text.c_str(), rect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
- true, true, &AbsoluteClippingRect);
- }
-
IGUIElement::draw();
}
@@ -356,10 +380,17 @@ void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::p EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
{
+ // PATCH
+ return getImageState(pressed, ButtonImages);
+ // END PATCH
+}
+
+EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed, const ButtonImage* images) const
+{
// figure state we should have
EGUI_BUTTON_IMAGE_STATE state = EGBIS_IMAGE_DISABLED;
bool focused = Environment->hasFocus((IGUIElement*)this);
- bool mouseOver = static_cast<const IGUIElement*>(Environment->getHovered()) == this; // (static cast for Borland)
+ bool mouseOver = isHovered();
if (isEnabled())
{
if ( pressed )
@@ -387,12 +418,13 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const }
// find a compatible state that has images
- while ( state != EGBIS_IMAGE_UP && !ButtonImages[(u32)state].Texture )
+ while ( state != EGBIS_IMAGE_UP && !images[(u32)state].Texture )
{
+ // PATCH
switch ( state )
{
case EGBIS_IMAGE_UP_FOCUSED:
- state = EGBIS_IMAGE_UP_MOUSEOVER;
+ state = EGBIS_IMAGE_UP;
break;
case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER:
state = EGBIS_IMAGE_UP_FOCUSED;
@@ -401,7 +433,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const state = EGBIS_IMAGE_DOWN;
break;
case EGBIS_IMAGE_DOWN_FOCUSED:
- state = EGBIS_IMAGE_DOWN_MOUSEOVER;
+ state = EGBIS_IMAGE_DOWN;
break;
case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER:
state = EGBIS_IMAGE_DOWN_FOCUSED;
@@ -415,6 +447,7 @@ EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const default:
state = EGBIS_IMAGE_UP;
}
+ // END PATCH
}
return state;
@@ -433,6 +466,8 @@ void GUIButton::setOverrideFont(IGUIFont* font) if (OverrideFont)
OverrideFont->grab();
+
+ StaticText->setOverrideFont(font);
}
//! Gets the override font (if any)
@@ -457,6 +492,8 @@ void GUIButton::setOverrideColor(video::SColor color) {
OverrideColor = color;
OverrideColorEnabled = true;
+
+ StaticText->setOverrideColor(color);
}
video::SColor GUIButton::getOverrideColor() const
@@ -490,6 +527,48 @@ void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image, ButtonImages[stateIdx].SourceRect = sourceRect;
}
+// PATCH
+void GUIButton::setImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_UP, image);
+}
+
+void GUIButton::setImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_UP, image, pos);
+}
+
+void GUIButton::setPressedImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_DOWN, image);
+}
+
+void GUIButton::setPressedImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
+}
+
+void GUIButton::setHoveredImage(video::ITexture* image)
+{
+ setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image);
+ setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image);
+}
+
+void GUIButton::setHoveredImage(video::ITexture* image, const core::rect<s32>& pos)
+{
+ setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos);
+ setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos);
+}
+
+//! Sets the text displayed by the button
+void GUIButton::setText(const wchar_t* text)
+{
+ StaticText->setText(text);
+
+ IGUIButton::setText(text);
+}
+// END PATCH
+
//! Sets if the button should behave like a push button. Which means it
//! can be in two states: Normal or Pressed. With a click on the button,
//! the user can change the state of the button.
@@ -505,6 +584,14 @@ bool GUIButton::isPressed() const return Pressed;
}
+// PATCH
+//! Returns if this element (or one of its direct children) is hovered
+bool GUIButton::isHovered() const
+{
+ IGUIElement *hovered = Environment->getHovered();
+ return hovered == this || (hovered != nullptr && hovered->getParent() == this);
+}
+// END PATCH
//! Sets the pressed state of the button if this is a pushbutton
void GUIButton::setPressed(bool pressed)
@@ -513,6 +600,24 @@ void GUIButton::setPressed(bool pressed) {
ClickTime = porting::getTimeMs();
Pressed = pressed;
+
+ GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
+
+ for(IGUIElement *child : getChildren())
+ {
+ core::rect<s32> originalRect = child->getRelativePosition();
+ if (Pressed) {
+ child->setRelativePosition(originalRect +
+ core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
+ } else {
+ child->setRelativePosition(originalRect -
+ core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
+ }
+ }
}
}
@@ -644,6 +749,76 @@ void GUIButton::setColor(video::SColor color) for (size_t i = 0; i < 4; i++) {
video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
Colors[i] = base.getInterpolated(color, d);
+ HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
+ PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
+ core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
+ core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
+ }
+}
+void GUIButton::setHoveredColor(video::SColor color)
+{
+ float d = 0.65f;
+ for (size_t i = 0; i < 4; i++) {
+ video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
+ HoveredColors[i] = base.getInterpolated(color, d);
+ }
+}
+void GUIButton::setPressedColor(video::SColor color)
+{
+ float d = 0.65f;
+ for (size_t i = 0; i < 4; i++) {
+ video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
+ PressedColors[i] = base.getInterpolated(color, d);
+ }
+}
+
+//! Set element properties from a StyleSpec
+void GUIButton::setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc)
+{
+ if (style.isNotDefault(StyleSpec::BGCOLOR)) {
+ setColor(style.getColor(StyleSpec::BGCOLOR));
+ }
+ if (style.isNotDefault(StyleSpec::BGCOLOR_HOVERED)) {
+ setHoveredColor(style.getColor(StyleSpec::BGCOLOR_HOVERED));
+ }
+ if (style.isNotDefault(StyleSpec::BGCOLOR_PRESSED)) {
+ setPressedColor(style.getColor(StyleSpec::BGCOLOR_PRESSED));
+ }
+
+ if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
+ setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
+ }
+ setNotClipped(style.getBool(StyleSpec::NOCLIP, isNotClipped()));
+ setDrawBorder(style.getBool(StyleSpec::BORDER, DrawBorder));
+ setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
+
+ const core::position2di buttonCenter(AbsoluteRect.getCenter());
+ core::position2d<s32> geom(buttonCenter);
+ if (style.isNotDefault(StyleSpec::BGIMG)) {
+ video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, tsrc);
+
+ setImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::BGIMG_HOVERED)) {
+ video::ITexture *hovered_texture = style.getTexture(StyleSpec::BGIMG_HOVERED, tsrc);
+
+ setHoveredImage(guiScalingImageButton(
+ Environment->getVideoDriver(), hovered_texture, geom.X, geom.Y));
+ setScaleImage(true);
+ }
+ if (style.isNotDefault(StyleSpec::BGIMG_PRESSED)) {
+ video::ITexture *pressed_texture = style.getTexture(StyleSpec::BGIMG_PRESSED, tsrc);
+
+ setPressedImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ setScaleImage(true);
}
+ BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
}
// END PATCH
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 63e29ccfc..3d1f98c32 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -6,6 +6,8 @@ #include "IrrCompileConfig.h"
+#include <IGUIStaticText.h>
+#include "irrlicht_changes/static_text.h"
#include "IGUIButton.h"
#include "IGUISpriteBank.h"
#include "ITexture.h"
@@ -64,6 +66,9 @@ using namespace irr; #endif
+class ISimpleTextureSource;
+class StyleSpec;
+
class GUIButton : public gui::IGUIButton
{
public:
@@ -102,34 +107,33 @@ public: //! Checks if an override color is enabled
virtual bool isOverrideColorEnabled(void) const;
+ // PATCH
//! Sets an image which should be displayed on the button when it is in the given state.
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
- video::ITexture* image=0,
+ video::ITexture* image=nullptr,
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0));
//! Sets an image which should be displayed on the button when it is in normal state.
- virtual void setImage(video::ITexture* image=0) override
- {
- setImage(gui::EGBIS_IMAGE_UP, image);
- }
+ virtual void setImage(video::ITexture* image=nullptr) override;
//! Sets an image which should be displayed on the button when it is in normal state.
- virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override
- {
- setImage(gui::EGBIS_IMAGE_UP, image, pos);
- }
+ virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
//! Sets an image which should be displayed on the button when it is in pressed state.
- virtual void setPressedImage(video::ITexture* image=0) override
- {
- setImage(gui::EGBIS_IMAGE_DOWN, image);
- }
+ virtual void setPressedImage(video::ITexture* image=nullptr) override;
//! Sets an image which should be displayed on the button when it is in pressed state.
- virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override
- {
- setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
- }
+ virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
+
+ //! Sets an image which should be displayed on the button when it is in hovered state.
+ virtual void setHoveredImage(video::ITexture* image=nullptr);
+
+ //! Sets the text displayed by the button
+ virtual void setText(const wchar_t* text) override;
+ // END PATCH
+
+ //! Sets an image which should be displayed on the button when it is in hovered state.
+ virtual void setHoveredImage(video::ITexture* image, const core::rect<s32>& pos);
//! Sets the sprite bank used by the button
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
@@ -176,6 +180,11 @@ public: //! Returns if the button is currently pressed
virtual bool isPressed() const override;
+ // PATCH
+ //! Returns if this element (or one of its direct children) is hovered
+ bool isHovered() const;
+ // END PATCH
+
//! Sets if the button should use the skin to draw its border
virtual void setDrawBorder(bool border=true) override;
@@ -215,6 +224,13 @@ public: void setColor(video::SColor color);
+ // PATCH
+ void setHoveredColor(video::SColor color);
+ void setPressedColor(video::SColor color);
+
+ //! Set element properties from a StyleSpec
+ virtual void setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc);
+ // END PATCH
//! Do not drop returned handle
@@ -225,28 +241,6 @@ protected: void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
-private:
-
- struct ButtonSprite
- {
- ButtonSprite() : Index(-1), Loop(false), Scale(false)
- {
- }
-
- bool operator==(const ButtonSprite& other) const
- {
- return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
- }
-
- s32 Index;
- video::SColor Color;
- bool Loop;
- bool Scale;
- };
-
- ButtonSprite ButtonSprites[gui::EGBS_COUNT];
- gui::IGUISpriteBank* SpriteBank;
-
struct ButtonImage
{
ButtonImage() : Texture(0), SourceRect(core::rect<s32>(0,0,0,0))
@@ -288,6 +282,30 @@ private: core::rect<s32> SourceRect;
};
+ gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
+
+private:
+
+ struct ButtonSprite
+ {
+ ButtonSprite() : Index(-1), Loop(false), Scale(false)
+ {
+ }
+
+ bool operator==(const ButtonSprite& other) const
+ {
+ return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
+ }
+
+ s32 Index;
+ video::SColor Color;
+ bool Loop;
+ bool Scale;
+ };
+
+ ButtonSprite ButtonSprites[gui::EGBS_COUNT];
+ gui::IGUISpriteBank* SpriteBank;
+
ButtonImage ButtonImages[gui::EGBIS_COUNT];
gui::IGUIFont* OverrideFont;
@@ -307,4 +325,12 @@ private: bool ScaleImage;
video::SColor Colors[4];
+ // PATCH
+ video::SColor HoveredColors[4];
+ video::SColor PressedColors[4];
+
+ gui::IGUIStaticText *StaticText;
+
+ core::rect<s32> BgMiddle;
+ // END PATCH
};
diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp new file mode 100644 index 000000000..02d920277 --- /dev/null +++ b/src/gui/guiButtonImage.cpp @@ -0,0 +1,160 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiButtonImage.h" + +#include "client/guiscalingfilter.h" +#include "debug.h" +#include "IGUIEnvironment.h" +#include "IGUIImage.h" +#include "IVideoDriver.h" +#include "StyleSpec.h" + +using namespace irr; +using namespace gui; + +GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, + gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle, bool noclip) + : GUIButton (environment, parent, id, rectangle, noclip) +{ + m_image = Environment->addImage( + core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), this); + m_image->setScaleImage(isScalingImage()); + sendToBack(m_image); +} + +bool GUIButtonImage::OnEvent(const SEvent& event) +{ + bool result = GUIButton::OnEvent(event); + + EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images); + video::ITexture *texture = m_foreground_images[(u32)imageState].Texture; + if (texture != nullptr) + { + m_image->setImage(texture); + } + + m_image->setVisible(texture != nullptr); + + return result; +} + +void GUIButtonImage::setForegroundImage(EGUI_BUTTON_IMAGE_STATE state, + video::ITexture *image, const core::rect<s32> &sourceRect) +{ + if (state >= EGBIS_COUNT) + return; + + if (image) + image->grab(); + + u32 stateIdx = (u32)state; + if (m_foreground_images[stateIdx].Texture) + m_foreground_images[stateIdx].Texture->drop(); + + m_foreground_images[stateIdx].Texture = image; + m_foreground_images[stateIdx].SourceRect = sourceRect; + + EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images); + if (imageState == stateIdx) + m_image->setImage(image); +} + +void GUIButtonImage::setForegroundImage(video::ITexture *image) +{ + setForegroundImage(gui::EGBIS_IMAGE_UP, image); +} + +void GUIButtonImage::setForegroundImage(video::ITexture *image, const core::rect<s32> &pos) +{ + setForegroundImage(gui::EGBIS_IMAGE_UP, image, pos); +} + +void GUIButtonImage::setPressedForegroundImage(video::ITexture *image) +{ + setForegroundImage(gui::EGBIS_IMAGE_DOWN, image); +} + +void GUIButtonImage::setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos) +{ + setForegroundImage(gui::EGBIS_IMAGE_DOWN, image, pos); +} + +void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image) +{ + setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image); + setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image); +} + +void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos) +{ + setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos); + setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos); +} + +void GUIButtonImage::setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) +{ + GUIButton::setFromStyle(style, tsrc); + + video::IVideoDriver *driver = Environment->getVideoDriver(); + + const core::position2di buttonCenter(AbsoluteRect.getCenter()); + core::position2d<s32> geom(buttonCenter); + if (style.isNotDefault(StyleSpec::FGIMG)) { + video::ITexture *texture = style.getTexture(StyleSpec::FGIMG, tsrc); + + setForegroundImage(guiScalingImageButton(driver, texture, geom.X, geom.Y)); + setScaleImage(true); + } + if (style.isNotDefault(StyleSpec::FGIMG_HOVERED)) { + video::ITexture *hovered_texture = style.getTexture(StyleSpec::FGIMG_HOVERED, tsrc); + + setHoveredForegroundImage(guiScalingImageButton(driver, hovered_texture, geom.X, geom.Y)); + setScaleImage(true); + } + if (style.isNotDefault(StyleSpec::FGIMG_PRESSED)) { + video::ITexture *pressed_texture = style.getTexture(StyleSpec::FGIMG_PRESSED, tsrc); + + setPressedForegroundImage(guiScalingImageButton(driver, pressed_texture, geom.X, geom.Y)); + setScaleImage(true); + } +} + +void GUIButtonImage::setScaleImage(bool scaleImage) +{ + GUIButton::setScaleImage(scaleImage); + m_image->setScaleImage(scaleImage); +} + +GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment, + const core::rect<s32> &rectangle, IGUIElement *parent, s32 id, + const wchar_t *text, const wchar_t *tooltiptext) +{ + GUIButtonImage *button = new GUIButtonImage(environment, + parent ? parent : environment->getRootGUIElement(), id, rectangle); + + if (text) + button->setText(text); + + if (tooltiptext) + button->setToolTipText(tooltiptext); + + button->drop(); + return button; +} diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h new file mode 100644 index 000000000..15901ee5d --- /dev/null +++ b/src/gui/guiButtonImage.h @@ -0,0 +1,59 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiButton.h" +#include "IGUIButton.h" + +using namespace irr; + +class GUIButtonImage : public GUIButton +{ +public: + //! constructor + GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, + s32 id, core::rect<s32> rectangle, bool noclip = false); + + virtual bool OnEvent(const SEvent& event) override; + + void setForegroundImage(gui::EGUI_BUTTON_IMAGE_STATE state, + video::ITexture *image = nullptr, + const core::rect<s32> &sourceRect = core::rect<s32>(0, 0, 0, 0)); + + void setForegroundImage(video::ITexture *image = nullptr); + void setForegroundImage(video::ITexture *image, const core::rect<s32> &pos); + + void setPressedForegroundImage(video::ITexture *image = nullptr); + void setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos); + + void setHoveredForegroundImage(video::ITexture *image = nullptr); + void setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos); + + virtual void setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) override; + + virtual void setScaleImage(bool scaleImage=true) override; + + //! Do not drop returned handle + static GUIButtonImage *addButton(gui::IGUIEnvironment *environment, + const core::rect<s32> &rectangle, IGUIElement *parent, s32 id, + const wchar_t *text, const wchar_t *tooltiptext = L""); + +private: + ButtonImage m_foreground_images[gui::EGBIS_COUNT]; + gui::IGUIImage *m_image; +}; diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp new file mode 100644 index 000000000..5c48b2acd --- /dev/null +++ b/src/gui/guiButtonItemImage.cpp @@ -0,0 +1,57 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiButtonItemImage.h" + +#include "client/client.h" +#include "client/hud.h" // drawItemStack +#include "guiItemImage.h" +#include "IGUIEnvironment.h" +#include "itemdef.h" + +using namespace irr; +using namespace gui; + +GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, + s32 id, core::rect<s32> rectangle, std::string item, Client *client, bool noclip) + : GUIButton (environment, parent, id, rectangle, noclip) +{ + m_image = new GUIItemImage(environment, this, id, + core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), + item, getActiveFont(), client); + sendToBack(m_image); + + m_item_name = item; + m_client = client; +} + +GUIButtonItemImage *GUIButtonItemImage::addButton(IGUIEnvironment *environment, + const core::rect<s32> &rectangle, IGUIElement *parent, s32 id, + const wchar_t *text, std::string item, Client *client) +{ + GUIButtonItemImage *button = new GUIButtonItemImage(environment, + parent ? parent : environment->getRootGUIElement(), + id, rectangle, item, client); + + if (text) + button->setText(text); + + button->drop(); + return button; +} diff --git a/src/gui/guiButtonItemImage.h b/src/gui/guiButtonItemImage.h new file mode 100644 index 000000000..0a61874da --- /dev/null +++ b/src/gui/guiButtonItemImage.h @@ -0,0 +1,45 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiButton.h" +#include "IGUIButton.h" + +using namespace irr; + +class Client; +class GUIItemImage; + +class GUIButtonItemImage : public GUIButton +{ +public: + //! constructor + GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, + s32 id, core::rect<s32> rectangle, std::string item, + Client *client, bool noclip = false); + + //! Do not drop returned handle + static GUIButtonItemImage *addButton(gui::IGUIEnvironment *environment, + const core::rect<s32> &rectangle, IGUIElement *parent, s32 id, + const wchar_t *text, std::string item, Client *client); + +private: + std::string m_item_name; + Client *m_client; + GUIItemImage *m_image; +}; diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 6fe2a4fc4..0d8bdf54e 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiConfirmRegistration.h" #include "client/client.h" +#include "guiButton.h" #include <IGUICheckBox.h> #include <IGUIButton.h> #include <IGUIStaticText.h> @@ -32,8 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc., // Continuing from guiPasswordChange.cpp const int ID_confirmPassword = 262; const int ID_confirm = 263; -const int ID_message = 264; +const int ID_intotext = 264; const int ID_cancel = 265; +const int ID_message = 266; GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, @@ -105,7 +107,7 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) wchar_t *info_text_buf_wide = utf8_to_wide_c(info_text_buf); gui::IGUIEditBox *e = new gui::intlGUIEditBox(info_text_buf_wide, true, - Environment, this, ID_message, rect2, false, true); + Environment, this, ID_intotext, rect2, false, true); delete[] info_text_buf_wide; e->drop(); e->setMultiLine(true); @@ -113,7 +115,7 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); } - ypos += 210 * s; + ypos += 200 * s; { core::rect<s32> rect2(0, 0, 540 * s, 30 * s); rect2 += topleft_client + v2s32(30 * s, ypos); @@ -123,24 +125,24 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) Environment->setFocus(e); } - ypos += 60 * s; + ypos += 50 * s; { core::rect<s32> rect2(0, 0, 230 * s, 35 * s); rect2 = rect2 + v2s32(size.X / 2 - 220 * s, ypos); text = wgettext("Register and Join"); - Environment->addButton(rect2, this, ID_confirm, text); + GUIButton::addButton(Environment, rect2, this, ID_confirm, text); delete[] text; } { core::rect<s32> rect2(0, 0, 120 * s, 35 * s); rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos); text = wgettext("Cancel"); - Environment->addButton(rect2, this, ID_cancel, text); + GUIButton::addButton(Environment, rect2, this, ID_cancel, text); delete[] text; } { - core::rect<s32> rect2(0, 0, 200 * s, 20 * s); - rect2 += topleft_client + v2s32(30 * s, ypos - 40 * s); + core::rect<s32> rect2(0, 0, 500 * s, 40 * s); + rect2 += topleft_client + v2s32(30 * s, ypos + 40 * s); text = wgettext("Passwords do not match!"); IGUIElement *e = Environment->addStaticText( text, rect2, false, true, this, ID_message); diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index 2f909f54f..442406688 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -1109,10 +1109,13 @@ void GUIEditBoxWithScrollBar::breakText() m_broken_text_positions.push_back(last_line_start); } -// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom) -// but HAlign according to line-width (pixels) and not by row. -// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling. -// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling). +// TODO: that function does interpret VAlign according to line-index (indexed +// line is placed on top-center-bottom) but HAlign according to line-width +// (pixels) and not by row. +// Intuitively I suppose HAlign handling is better as VScrollPos should handle +// the line-scrolling. +// But please no one change this without also rewriting (and this time +// testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling). void GUIEditBoxWithScrollBar::setTextRect(s32 line) { if (line < 0) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 8d740237c..59cd130ef 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -21,22 +21,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cstdlib> #include <algorithm> #include <iterator> -#include <sstream> #include <limits> -#include "guiButton.h" +#include <sstream> #include "guiFormSpecMenu.h" +#include "guiScrollBar.h" #include "guiTable.h" #include "constants.h" #include "gamedef.h" #include "client/keycode.h" #include "util/strfnd.h" +#include <IGUIButton.h> #include <IGUICheckBox.h> +#include <IGUIComboBox.h> #include <IGUIEditBox.h> -#include <IGUIButton.h> #include <IGUIStaticText.h> #include <IGUIFont.h> #include <IGUITabControl.h> -#include <IGUIComboBox.h> #include "client/renderingengine.h" #include "log.h" #include "client/tile.h" // ITextureSource @@ -55,8 +55,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" // for parseColorString() #include "irrlicht_changes/static_text.h" #include "client/guiscalingfilter.h" +#include "guiAnimatedImage.h" +#include "guiBackgroundImage.h" +#include "guiBox.h" +#include "guiButton.h" +#include "guiButtonImage.h" +#include "guiButtonItemImage.h" #include "guiEditBoxWithScrollbar.h" +#include "guiInventoryList.h" +#include "guiItemImage.h" +#include "guiScrollBar.h" +#include "guiTable.h" #include "intlGUIEditBox.h" +#include "guiHyperText.h" #define MY_CHECKPOS(a,b) \ if (v_pos.size() != 2) { \ @@ -118,9 +129,20 @@ GUIFormSpecMenu::~GUIFormSpecMenu() { removeChildren(); - for (auto &table_it : m_tables) { + for (auto &table_it : m_tables) table_it.second->drop(); - } + for (auto &inventorylist_it : m_inventorylists) + inventorylist_it->drop(); + for (auto &checkbox_it : m_checkboxes) + checkbox_it.second->drop(); + for (auto &scrollbar_it : m_scrollbars) + scrollbar_it.second->drop(); + for (auto &background_it : m_backgrounds) + background_it->drop(); + for (auto &tooltip_rect_it : m_tooltip_rects) + tooltip_rect_it.first->drop(); + for (auto &clickthrough_it : m_clickthrough_elements) + clickthrough_it->drop(); delete m_selected_item; delete m_form_src; @@ -155,16 +177,15 @@ void GUIFormSpecMenu::removeChildren() { const core::list<gui::IGUIElement*> &children = getChildren(); - while(!children.empty()) { + while (!children.empty()) { (*children.getLast())->remove(); } - if(m_tooltip_element) { + if (m_tooltip_element) { m_tooltip_element->remove(); m_tooltip_element->drop(); - m_tooltip_element = NULL; + m_tooltip_element = nullptr; } - } void GUIFormSpecMenu::setInitialFocus() @@ -256,14 +277,9 @@ std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string & return NULL; } -v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute, - const std::vector<std::string> *v_pos) +v2s32 GUIFormSpecMenu::getElementBasePos(const std::vector<std::string> *v_pos) { - v2s32 pos = padding; - if (absolute) - pos += AbsoluteRect.UpperLeftCorner; - - v2f32 pos_f = v2f32(pos.X, pos.Y) + pos_offset * spacing; + v2f32 pos_f = v2f32(padding.X, padding.Y) + pos_offset * spacing; if (v_pos) { pos_f.X += stof((*v_pos)[0]) * spacing.X; pos_f.Y += stof((*v_pos)[1]) * spacing.Y; @@ -271,18 +287,10 @@ v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute, return v2s32(pos_f.X, pos_f.Y); } -v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(bool absolute, - const std::vector<std::string> &v_pos) +v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(const std::vector<std::string> &v_pos) { - v2f32 pos_f = v2f32(0.0f, 0.0f); - - pos_f.X += stof(v_pos[0]) + pos_offset.X; - pos_f.Y += stof(v_pos[1]) + pos_offset.Y; - - if (absolute) - return v2s32(pos_f.X * imgsize.X + AbsoluteRect.UpperLeftCorner.X, - pos_f.Y * imgsize.Y + AbsoluteRect.UpperLeftCorner.Y); - return v2s32(pos_f.X * imgsize.X, pos_f.Y * imgsize.Y); + return v2s32((stof(v_pos[0]) + pos_offset.X) * imgsize.X, + (stof(v_pos[1]) + pos_offset.Y) * imgsize.Y); } v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom) @@ -343,7 +351,7 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data) } } -void GUIFormSpecMenu::parseList(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) { if (m_client == 0) { warningstream<<"invalid use of 'list' with m_client==0"<<std::endl; @@ -373,14 +381,7 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element) else loc.deSerialize(location); - v2s32 pos; v2s32 geom; - - if (data->real_coordinates) - pos = getRealCoordinateBasePos(true, v_pos); - else - pos = getElementBasePos(true, &v_pos); - geom.X = stoi(v_geom[0]); geom.Y = stoi(v_geom[1]); @@ -393,15 +394,67 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element) return; } - if(!data->explicit_size) - warningstream<<"invalid use of list without a size[] element"<<std::endl; - m_inventorylists.emplace_back(loc, listname, pos, geom, start_i, data->real_coordinates); + // check for the existence of inventory and list + Inventory *inv = m_invmgr->getInventory(loc); + if (!inv) { + warningstream << "GUIFormSpecMenu::parseList(): " + << "The inventory location " + << "\"" << loc.dump() << "\" doesn't exist" + << std::endl; + return; + } + InventoryList *ilist = inv->getList(listname); + if (!ilist) { + warningstream << "GUIFormSpecMenu::parseList(): " + << "The inventory list \"" << listname << "\" " + << "@ \"" << loc.dump() << "\" doesn't exist" + << std::endl; + return; + } + + // trim geom if it is larger than the actual inventory size + s32 list_size = (s32)ilist->getSize(); + if (list_size < geom.X * geom.Y + start_i) { + list_size -= MYMAX(start_i, 0); + geom.Y = list_size / geom.X; + geom.Y += list_size % geom.X > 0 ? 1 : 0; + if (geom.Y <= 1) + geom.X = list_size; + } + + if (!data->explicit_size) + warningstream << "invalid use of list without a size[] element" << std::endl; + + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 3 + ); + + v2f32 slot_spacing = data->real_coordinates ? + v2f32(imgsize.X * 1.25f, imgsize.Y * 1.25f) : spacing; + + v2s32 pos = data->real_coordinates ? getRealCoordinateBasePos(v_pos) + : getElementBasePos(&v_pos); + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, + pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X, + pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y); + + GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid, + rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing, + this, data->inventorylist_options, m_font); + + m_inventorylists.push_back(e); + m_fields.push_back(spec); return; } errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; } -void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element) +void GUIFormSpecMenu::parseListRing(parserData *data, const std::string &element) { if (m_client == 0) { errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl; @@ -428,10 +481,10 @@ void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element if (element.empty() && m_inventorylists.size() > 1) { size_t siz = m_inventorylists.size(); // insert the last two inv list elements into the list ring - const ListDrawSpec &spa = m_inventorylists[siz - 2]; - const ListDrawSpec &spb = m_inventorylists[siz - 1]; - m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname); - m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname); + const GUIInventoryList *spa = m_inventorylists[siz - 2]; + const GUIInventoryList *spb = m_inventorylists[siz - 1]; + m_inventory_rings.emplace_back(spa->getInventoryloc(), spa->getListname()); + m_inventory_rings.emplace_back(spb->getInventoryloc(), spb->getListname()); return; } @@ -470,7 +523,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element core::rect<s32> rect; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); rect = core::rect<s32>( pos.X, @@ -479,7 +532,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element pos.Y + y_center ); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); rect = core::rect<s32>( pos.X, pos.Y + imgsize.Y / 2 - y_center, @@ -497,7 +550,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element spec.ftype = f_CheckBox; - gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this, + gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this, spec.fid, spec.flabel.c_str()); auto style = getStyleForElement("checkbox", name); @@ -507,7 +560,8 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element Environment->setFocus(e); } - m_checkboxes.emplace_back(spec,e); + e->grab(); + m_checkboxes.emplace_back(spec, e); m_fields.push_back(spec); return; } @@ -531,10 +585,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen v2s32 dim; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); dim = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); dim.X = stof(v_geom[0]) * spacing.X; dim.Y = stof(v_geom[1]) * spacing.Y; } @@ -556,23 +610,87 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen spec.ftype = f_ScrollBar; spec.send = true; - gui::IGUIScrollBar* e = - Environment->addScrollBar(is_horizontal,rect,this,spec.fid); + GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect, + is_horizontal, true); auto style = getStyleForElement("scrollbar", name); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setArrowsVisible(data->scrollbar_options.arrow_visiblity); + + s32 max = data->scrollbar_options.max; + s32 min = data->scrollbar_options.min; + + e->setMax(max); + e->setMin(min); - e->setMax(1000); - e->setMin(0); e->setPos(stoi(parts[4])); - e->setSmallStep(10); - e->setLargeStep(100); + + e->setSmallStep(data->scrollbar_options.small_step); + e->setLargeStep(data->scrollbar_options.large_step); + + s32 scrollbar_size = is_horizontal ? dim.X : dim.Y; + + e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); m_scrollbars.emplace_back(spec,e); m_fields.push_back(spec); return; } - errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl; + errorstream << "Invalid scrollbar element(" << parts.size() << "): '" << element + << "'" << std::endl; +} + +void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() == 0) { + warningstream << "Invalid scrollbaroptions element(" << parts.size() << "): '" << + element << "'" << std::endl; + return; + } + + for (const std::string &i : parts) { + std::vector<std::string> options = split(i, '='); + + if (options.size() != 2) { + warningstream << "Invalid scrollbaroptions option syntax: '" << + element << "'" << std::endl; + continue; // Go to next option + } + + if (options[0] == "max") { + data->scrollbar_options.max = stoi(options[1]); + continue; + } else if (options[0] == "min") { + data->scrollbar_options.min = stoi(options[1]); + continue; + } else if (options[0] == "smallstep") { + int value = stoi(options[1]); + data->scrollbar_options.small_step = value < 0 ? 10 : value; + continue; + } else if (options[0] == "largestep") { + int value = stoi(options[1]); + data->scrollbar_options.large_step = value < 0 ? 100 : value; + continue; + } else if (options[0] == "thumbsize") { + int value = stoi(options[1]); + data->scrollbar_options.thumb_size = value <= 0 ? 1 : value; + continue; + } else if (options[0] == "arrows") { + std::string value = trim(options[1]); + if (value == "hide") + data->scrollbar_options.arrow_visiblity = GUIScrollBar::HIDE; + else if (value == "show") + data->scrollbar_options.arrow_visiblity = GUIScrollBar::SHOW; + else // Auto hide/show + data->scrollbar_options.arrow_visiblity = GUIScrollBar::DEFAULT; + continue; + } + + warningstream << "Invalid scrollbaroptions option(" << options[0] << + "): '" << element << "'" << std::endl; + } } void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) @@ -593,17 +711,42 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(true, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(true, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * (float)imgsize.X; geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } if (!data->explicit_size) warningstream<<"invalid use of image without a size[] element"<<std::endl; - m_images.emplace_back(name, pos, geom); + + video::ITexture *texture = m_tsrc->getTexture(name); + if (!texture) { + errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" + << std::endl << "\t" << name << std::endl; + return; + } + + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size(), + 1 + ); + core::rect<s32> rect(pos, pos + geom); + gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true); + e->setImage(texture); + e->setScaleImage(true); + auto style = getStyleForElement("image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + m_fields.push_back(spec); + + // images should let events through + e->grab(); + m_clickthrough_elements.push_back(e); return; } @@ -613,16 +756,98 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) MY_CHECKPOS("image", 0); - v2s32 pos = getElementBasePos(true, &v_pos); + v2s32 pos = getElementBasePos(&v_pos); if (!data->explicit_size) warningstream<<"invalid use of image without a size[] element"<<std::endl; - m_images.emplace_back(name, pos); + + video::ITexture *texture = m_tsrc->getTexture(name); + if (!texture) { + errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" + << std::endl << "\t" << name << std::endl; + return; + } + + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); + gui::IGUIImage *e = Environment->addImage(texture, pos, true, this, + spec.fid, 0); + auto style = getStyleForElement("image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + m_fields.push_back(spec); + + // images should let events through + e->grab(); + m_clickthrough_elements.push_back(e); return; } errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl; } +void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() != 6 && parts.size() != 7 && + !(parts.size() > 7 && m_formspec_version > FORMSPEC_API_VERSION)) { + errorstream << "Invalid animated_image element(" << parts.size() + << "): '" << element << "'" << std::endl; + return; + } + + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); + std::string name = parts[2]; + std::string texture_name = unescape_string(parts[3]); + s32 frame_count = stoi(parts[4]); + s32 frame_duration = stoi(parts[5]); + + MY_CHECKPOS("animated_image", 0); + MY_CHECKGEOM("animated_image", 1); + + v2s32 pos; + v2s32 geom; + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + } + + if (!data->explicit_size) + warningstream << "Invalid use of animated_image without a size[] element" << std::endl; + + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); + spec.ftype = f_AnimatedImage; + spec.send = true; + + core::rect<s32> rect = core::rect<s32>(pos, pos + geom); + + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, this, spec.fid, + rect, texture_name, frame_count, frame_duration, m_tsrc); + + if (parts.size() >= 7) + e->setFrameIndex(stoi(parts[6]) - 1); + + auto style = getStyleForElement("animated_image", spec.fname, "image"); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->drop(); + + m_fields.push_back(spec); +} + void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element) { std::vector<std::string> parts = split(element,';'); @@ -641,17 +866,35 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(true, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(true, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * (float)imgsize.X; geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } if(!data->explicit_size) warningstream<<"invalid use of item_image without a size[] element"<<std::endl; - m_itemimages.emplace_back("", name, pos, geom); + + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + 2 + ); + spec.ftype = f_ItemImage; + + GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid, + core::rect<s32>(pos, pos + geom), name, m_font, m_client); + auto style = getStyleForElement("item_image", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + + // item images should let events through + m_clickthrough_elements.push_back(e); + + m_fields.push_back(spec); return; } errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -678,12 +921,12 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, core::rect<s32> rect; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; @@ -700,7 +943,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, name, wlabel, L"", - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_Button; if(type == "button_exit") @@ -709,34 +952,7 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str()); auto style = getStyleForElement(type, name, (type != "button") ? "button" : ""); - if (style.isNotDefault(StyleSpec::BGCOLOR)) { - e->setColor(style.getColor(StyleSpec::BGCOLOR)); - } - if (style.isNotDefault(StyleSpec::TEXTCOLOR)) { - e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR)); - } - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); - - if (style.isNotDefault(StyleSpec::BGIMG)) { - std::string image_name = style.get(StyleSpec::BGIMG, ""); - std::string pressed_image_name = style.get(StyleSpec::BGIMG_PRESSED, ""); - - video::ITexture *texture = 0; - video::ITexture *pressed_texture = 0; - texture = m_tsrc->getTexture(image_name); - if (!pressed_image_name.empty()) - pressed_texture = m_tsrc->getTexture(pressed_image_name); - else - pressed_texture = texture; - - e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true)); - e->setImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, geom.X, geom.Y)); - e->setPressedImage(guiScalingImageButton( - Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y)); - e->setScaleImage(true); - } + e->setFromStyle(style, m_tsrc); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -765,10 +981,10 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(true, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(true, &v_pos); + pos = getElementBasePos(&v_pos); pos.X -= (spacing.X - (float)imgsize.X) / 2; pos.Y -= (spacing.Y - (float)imgsize.Y) / 2; @@ -779,7 +995,7 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme bool clip = false; if (parts.size() >= 4 && is_yes(parts[3])) { if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos) * -1; + pos = getRealCoordinateBasePos(v_pos) * -1; geom = v2s32(0, 0); } else { pos.X = stoi(v_pos[0]); //acts as offset @@ -812,8 +1028,33 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme if (!data->explicit_size && !clip) warningstream << "invalid use of unclipped background without a size[] element" << std::endl; - m_backgrounds.emplace_back(name, pos, geom, middle, clip); + FieldSpec spec( + name, + L"", + L"", + 258 + m_fields.size() + ); + + core::rect<s32> rect; + if (!clip) { + // no auto_clip => position like normal image + rect = core::rect<s32>(pos, pos + geom); + } else { + // it will be auto-clipped when drawing + rect = core::rect<s32>(-pos, pos); + } + + GUIBackgroundImage *e = new GUIBackgroundImage(Environment, this, spec.fid, + rect, name, middle, m_tsrc, clip); + + FATAL_ERROR_IF(!e, "Failed to create background formspec element"); + e->setNotClipped(true); + + e->setVisible(false); // the element is drawn manually before all others + + m_backgrounds.push_back(e); + m_fields.push_back(spec); return; } errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -875,10 +1116,10 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * spacing.X; geom.Y = stof(v_geom[1]) * spacing.Y; } @@ -889,7 +1130,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) name, L"", L"", - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_Table; @@ -899,8 +1140,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) } //now really show table - GUITable *e = new GUITable(Environment, this, spec.fid, rect, - m_tsrc); + GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -952,10 +1192,10 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * spacing.X; geom.Y = stof(v_geom[1]) * spacing.Y; } @@ -966,7 +1206,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element name, L"", L"", - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_Table; @@ -976,8 +1216,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element } //now really show list - GUITable *e = new GUITable(Environment, this, spec.fid, rect, - m_tsrc); + GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1030,11 +1269,11 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element MY_CHECKGEOM("dropdown",1); - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); s32 width = stof(parts[1]) * spacing.Y; @@ -1046,14 +1285,14 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element name, L"", L"", - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_DropDown; spec.send = true; //now really show list - gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid); + gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1097,8 +1336,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element { std::vector<std::string> parts = split(element,';'); - if ((parts.size() == 4) || (parts.size() == 5) || - ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + if ((parts.size() == 4) || + ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) { std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_geom = split(parts[1],','); @@ -1112,10 +1351,10 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); pos -= padding; geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); @@ -1133,7 +1372,9 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element name, wlabel, L"", - 258+m_fields.size() + 258 + m_fields.size(), + 0, + ECI_IBEAM ); spec.send = true; @@ -1167,12 +1408,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element evt.KeyInput.PressedDown = true; e->OnEvent(evt); - if (parts.size() >= 5) { - // TODO: remove after 2016-11-03 - warningstream << "pwdfield: use field_close_on_enter[name, enabled]" << - " instead of the 5th param" << std::endl; - field_close_on_enter[name] = is_yes(parts[4]); - } + // Note: Before 5.2.0 "parts.size() >= 5" resulted in a + // warning referring to field_close_on_enter[]! m_fields.push_back(spec); return; @@ -1187,7 +1424,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, if (!is_editable && !is_multiline) { // spec field id to 0, this stops submit searching for a value that isn't there gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - this, spec.fid); + this, spec.fid); return; } @@ -1204,20 +1441,21 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9; if (use_intl_edit_box && g_settings->getBool("freetype")) { - e = new gui::intlGUIEditBox(spec.fdefault.c_str(), - true, Environment, this, spec.fid, rect, is_editable, is_multiline); - e->drop(); + e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment, + this, spec.fid, rect, is_editable, is_multiline); } else { if (is_multiline) { e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, - Environment, this, spec.fid, rect, is_editable, true); - e->drop(); + Environment, this, spec.fid, rect, is_editable, true); } else if (is_editable) { - e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, - this, spec.fid); + e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, + spec.fid); + e->grab(); } } + auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname); + if (e) { if (is_editable && spec.fname == data->focused_fieldname) Environment->setFocus(e); @@ -1237,26 +1475,30 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, e->OnEvent(evt); } - auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { e->setDrawBackground(false); } + + e->drop(); } if (!spec.flabel.empty()) { int font_height = g_fontengine->getTextHeight(); rect.UpperLeftCorner.Y -= font_height; rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; - gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true, - this, 0); + IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(), + rect, false, true, this, 0); + + if (t) + t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); } } -void GUIFormSpecMenu::parseSimpleField(parserData* data, - std::vector<std::string> &parts) +void GUIFormSpecMenu::parseSimpleField(parserData *data, + std::vector<std::string> &parts) { std::string name = parts[0]; std::string label = parts[1]; @@ -1264,18 +1506,20 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, core::rect<s32> rect; - if(data->explicit_size) - warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl; + if (data->explicit_size) + warningstream << "invalid use of unpositioned \"field\" in inventory" << std::endl; - v2s32 pos = getElementBasePos(false, nullptr); - pos.Y = ((m_fields.size()+2)*60); + v2s32 pos = getElementBasePos(nullptr); + pos.Y = (data->simple_field_count + 2) * 60; v2s32 size = DesiredRect.getSize(); - rect = core::rect<s32>(size.X / 2 - 150, pos.Y, - (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2)); + rect = core::rect<s32>( + size.X / 2 - 150, pos.Y, + size.X / 2 - 150 + 300, pos.Y + m_btn_height * 2 + ); - if(m_form_src) + if (m_form_src) default_val = m_form_src->resolveText(default_val); @@ -1285,25 +1529,21 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, name, wlabel, utf8_to_wide(unescape_string(default_val)), - 258+m_fields.size() + 258 + m_fields.size(), + 0, + ECI_IBEAM ); createTextField(data, spec, rect, false); - if (parts.size() >= 4) { - // TODO: remove after 2016-11-03 - warningstream << "field/simple: use field_close_on_enter[name, enabled]" << - " instead of the 4th param" << std::endl; - field_close_on_enter[name] = is_yes(parts[3]); - } - m_fields.push_back(spec); + + data->simple_field_count++; } void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts, const std::string &type) { - std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_geom = split(parts[1],','); std::string name = parts[2]; @@ -1317,10 +1557,10 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); pos -= padding; geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); @@ -1353,17 +1593,15 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& name, wlabel, utf8_to_wide(unescape_string(default_val)), - 258+m_fields.size() + 258 + m_fields.size(), + 0, + ECI_IBEAM ); createTextField(data, spec, rect, type == "textarea"); - if (parts.size() >= 6) { - // TODO: remove after 2016-11-03 - warningstream << "field/textarea: use field_close_on_enter[name, enabled]" << - " instead of the 6th param" << std::endl; - field_close_on_enter[name] = is_yes(parts[5]); - } + // Note: Before 5.2.0 "parts.size() >= 6" resulted in a + // warning referring to field_close_on_enter[]! m_fields.push_back(spec); } @@ -1378,8 +1616,8 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, return; } - if ((parts.size() == 5) || (parts.size() == 6) || - ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) + if ((parts.size() == 5) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) { parseTextArea(data,parts,type); return; @@ -1387,6 +1625,58 @@ void GUIFormSpecMenu::parseField(parserData* data, const std::string &element, errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; } +void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &element) +{ + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() != 4 && m_formspec_version < FORMSPEC_API_VERSION) { + errorstream << "Invalid text element(" << parts.size() << "): '" << element << "'" << std::endl; + return; + } + + std::vector<std::string> v_pos = split(parts[0], ','); + std::vector<std::string> v_geom = split(parts[1], ','); + std::string name = parts[2]; + std::string text = parts[3]; + + MY_CHECKPOS("hypertext", 0); + MY_CHECKGEOM("hypertext", 1); + + v2s32 pos; + v2s32 geom; + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + pos -= padding; + + geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); + geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y - imgsize.Y); + pos.Y += m_btn_height; + } + + core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + geom.X, pos.Y + geom.Y); + + if(m_form_src) + text = m_form_src->resolveText(text); + + FieldSpec spec( + name, + utf8_to_wide(unescape_string(text)), + L"", + 258 + m_fields.size() + ); + + spec.ftype = f_HyperText; + GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this, + spec.fid, rect, m_client, m_tsrc); + e->drop(); + + m_fields.push_back(spec); +} + void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) { std::vector<std::string> parts = split(element,';'); @@ -1418,7 +1708,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) // easily without sacrificing good line distance. If // it was one whole imgsize, it would have too much // spacing. - v2s32 pos = getRealCoordinateBasePos(false, v_pos); + v2s32 pos = getRealCoordinateBasePos(v_pos); // Labels are positioned by their center, not their top. pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2); @@ -1439,7 +1729,7 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) // in the integer cases: 0.4 is not exactly // representable in binary floating point. - v2s32 pos = getElementBasePos(false, nullptr); + v2s32 pos = getElementBasePos(nullptr); pos.X += stof(v_pos[0]) * spacing.X; pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y; @@ -1455,10 +1745,11 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) "", wlabel_colors, L"", - 258+m_fields.size() + 258 + m_fields.size(), + 4 ); gui::IGUIStaticText *e = gui::StaticText::add(Environment, - spec.flabel.c_str(), rect, false, false, this, spec.fid); + spec.flabel.c_str(), rect, false, false, this, spec.fid); e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); auto style = getStyleForElement("label", spec.fname); @@ -1466,6 +1757,10 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element) e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); m_fields.push_back(spec); + + // labels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); } return; @@ -1491,7 +1786,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen core::rect<s32> rect; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); // Vertlabels are positioned by center, not left. pos.X -= imgsize.X / 2; @@ -1504,7 +1799,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen (text.length() + 1)); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); // As above, the length must be one longer. The width of // the rect (15 pixels) seems rather arbitrary, but @@ -1531,10 +1826,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen "", label, L"", - 258+m_fields.size() + 258 + m_fields.size() ); gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(), - rect, false, false, this, spec.fid); + rect, false, false, this, spec.fid); e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); auto style = getStyleForElement("vertlabel", spec.fname, "label"); @@ -1542,6 +1837,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); m_fields.push_back(spec); + + // vertlabels should let events through + e->grab(); + m_clickthrough_elements.push_back(e); return; } errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -1583,10 +1882,10 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); } @@ -1606,40 +1905,39 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem name, wlabel, utf8_to_wide(image_name), - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_Button; if (type == "image_button_exit") spec.is_exit = true; - video::ITexture *texture = 0; - video::ITexture *pressed_texture = 0; - texture = m_tsrc->getTexture(image_name); - if (!pressed_image_name.empty()) - pressed_texture = m_tsrc->getTexture(pressed_image_name); - else - pressed_texture = texture; - - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str()); if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); } auto style = getStyleForElement("image_button", spec.fname); + e->setFromStyle(style, m_tsrc); - e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true)); - e->setImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, geom.X, geom.Y)); - e->setPressedImage(guiScalingImageButton( - Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y)); + // We explicitly handle these arguments *after* the style properties in + // order to override them if they are provided + if (!image_name.empty()) + { + video::ITexture *texture = m_tsrc->getTexture(image_name); + e->setForegroundImage(guiScalingImageButton( + Environment->getVideoDriver(), texture, geom.X, geom.Y)); + } + if (!pressed_image_name.empty()) { + video::ITexture *pressed_texture = m_tsrc->getTexture(pressed_image_name); + e->setPressedForegroundImage(guiScalingImageButton( + Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y)); + } e->setScaleImage(true); + if (parts.size() >= 7) { e->setNotClipped(noclip); e->setDrawBorder(drawborder); - } else { - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); } m_fields.push_back(spec); @@ -1695,7 +1993,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen name, L"", L"", - 258+m_fields.size() + 258 + m_fields.size() ); spec.ftype = f_TabHeader; @@ -1704,7 +2002,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top. @@ -1789,10 +2087,10 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(false, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(false, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X); geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y); } @@ -1811,35 +2109,28 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & m_default_tooltip_bgcolor, m_default_tooltip_color); - FieldSpec spec( + // the spec for the button + FieldSpec spec_btn( name, utf8_to_wide(label), utf8_to_wide(item_name), - 258 + m_fields.size() + 258 + m_fields.size(), + 2 ); - gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L""); + GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, rect, this, spec_btn.fid, spec_btn.flabel.c_str(), item_name, m_client); - auto style = getStyleForElement("item_image_button", spec.fname, "image_button"); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); + auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button"); + e_btn->setFromStyle(style, m_tsrc); - if (spec.fname == data->focused_fieldname) { - Environment->setFocus(e); + if (spec_btn.fname == data->focused_fieldname) { + Environment->setFocus(e_btn); } - spec.ftype = f_Button; - rect+=data->basepos-padding; - spec.rect=rect; - m_fields.push_back(spec); - - if (data->real_coordinates) - pos = getRealCoordinateBasePos(true, v_pos); - else - pos = getElementBasePos(true, &v_pos); - - m_itemimages.emplace_back("", item_name, e, pos, geom); - m_static_texts.emplace_back(utf8_to_wide(label), rect, e); + spec_btn.ftype = f_Button; + rect += data->basepos-padding; + spec_btn.rect = rect; + m_fields.push_back(spec_btn); return; } errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -1862,10 +2153,10 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(true, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(true, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * spacing.X; geom.Y = stof(v_geom[1]) * spacing.Y; } @@ -1873,11 +2164,27 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) video::SColor tmp_color; if (parseColorString(parts[2], tmp_color, false, 0x8C)) { - BoxDrawSpec spec(pos, geom, tmp_color); + FieldSpec spec( + "", + L"", + L"", + 258 + m_fields.size(), + -2 + ); + spec.ftype = f_Box; - m_boxes.push_back(spec); - } - else { + core::rect<s32> rect(pos, pos + geom); + + GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color); + + auto style = getStyleForElement("box", spec.fname); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); + + e->drop(); + + m_fields.push_back(spec); + + } else { errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; } return; @@ -1888,21 +2195,36 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element) void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element) { std::vector<std::string> parts = split(element,';'); + const u32 parameter_count = parts.size(); + + if ((parameter_count > 2 && m_formspec_version < 3) || + (parameter_count > 3 && m_formspec_version <= FORMSPEC_API_VERSION)) { + errorstream << "Invalid bgcolor element(" << parameter_count << "): '" + << element << "'" << std::endl; + return; + } - if (((parts.size() == 1) || (parts.size() == 2)) || - ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) { + // bgcolor + if (parameter_count >= 1 && parts[0] != "") parseColorString(parts[0], m_bgcolor, false); - if (parts.size() == 2) { - std::string fullscreen = parts[1]; - m_bgfullscreen = is_yes(fullscreen); + // fullscreen + if (parameter_count >= 2) { + if (parts[1] == "both") { + m_bgnonfullscreen = true; + m_bgfullscreen = true; + } else if (parts[1] == "neither") { + m_bgnonfullscreen = false; + m_bgfullscreen = false; + } else if (parts[1] != "" || m_formspec_version < 3) { + m_bgfullscreen = is_yes(parts[1]); + m_bgnonfullscreen = !m_bgfullscreen; } - - return; } - errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'" - << std::endl; + // fbgcolor + if (parameter_count >= 3 && parts[2] != "") + parseColorString(parts[2], m_fullscreen_bgcolor, false); } void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element) @@ -1912,12 +2234,13 @@ void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &eleme if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) || ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) { - parseColorString(parts[0], m_slotbg_n, false); - parseColorString(parts[1], m_slotbg_h, false); + parseColorString(parts[0], data->inventorylist_options.slotbg_n, false); + parseColorString(parts[1], data->inventorylist_options.slotbg_h, false); if (parts.size() >= 3) { - if (parseColorString(parts[2], m_slotbordercolor, false)) { - m_slotborder = true; + if (parseColorString(parts[2], data->inventorylist_options.slotbordercolor, + false)) { + data->inventorylist_options.slotborder = true; } } if (parts.size() == 5) { @@ -1928,6 +2251,14 @@ void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &eleme if (parseColorString(parts[4], tmp_color, false)) m_default_tooltip_color = tmp_color; } + + // update all already parsed inventorylists + for (GUIInventoryList *e : m_inventorylists) { + e->setSlotBGColors(data->inventorylist_options.slotbg_n, + data->inventorylist_options.slotbg_h); + e->setSlotBorders(data->inventorylist_options.slotborder, + data->inventorylist_options.slotbordercolor); + } return; } errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl; @@ -1978,16 +2309,32 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element) v2s32 geom; if (data->real_coordinates) { - pos = getRealCoordinateBasePos(true, v_pos); + pos = getRealCoordinateBasePos(v_pos); geom = getRealCoordinateGeometry(v_geom); } else { - pos = getElementBasePos(true, &v_pos); + pos = getElementBasePos(&v_pos); geom.X = stof(v_geom[0]) * spacing.X; geom.Y = stof(v_geom[1]) * spacing.Y; } - irr::core::rect<s32> rect(pos, pos + geom); - m_tooltip_rects.emplace_back(rect, spec); + FieldSpec fieldspec( + "", + L"", + L"", + 258 + m_fields.size() + ); + + core::rect<s32> rect(pos, pos + geom); + + gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment, + this, fieldspec.fid, rect); + + // the element the rect tooltip is bound to should not block mouse-clicks + e->setVisible(false); + + m_fields.push_back(fieldspec); + m_tooltip_rects.emplace_back(e, spec); + } else { m_tooltips[parts[0]] = spec; } @@ -2034,7 +2381,7 @@ bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &eleme return false; if (type == "invsize") - log_deprecated("Deprecated formspec element \"invsize\" is used"); + warningstream << "Deprecated formspec element \"invsize\" is used" << std::endl; parseSize(data, description); @@ -2120,13 +2467,6 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b return false; } - std::string selector = trim(parts[0]); - if (selector.empty()) { - errorstream << "Invalid style element (Selector required): '" << element - << "'" << std::endl; - return false; - } - StyleSpec spec; for (size_t i = 1; i < parts.size(); i++) { @@ -2156,10 +2496,21 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b spec.set(prop, value); } - if (style_type) { - theme_by_type[selector] |= spec; - } else { - theme_by_name[selector] |= spec; + std::vector<std::string> selectors = split(parts[0], ','); + for (size_t sel = 0; sel < selectors.size(); sel++) { + std::string selector = trim(selectors[sel]); + + if (selector.empty()) { + errorstream << "Invalid style element (Empty selector): '" << element + << "'" << std::endl; + continue; + } + + if (style_type) { + theme_by_type[selector] |= spec; + } else { + theme_by_name[selector] |= spec; + } } return true; @@ -2223,6 +2574,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "animated_image") { + parseAnimatedImage(data, description); + return; + } + if (type == "item_image") { parseItemImage(data, description); return; @@ -2278,6 +2634,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "hypertext") { + parseHyperText(data,description); + return; + } + if (type == "label") { parseLabel(data,description); return; @@ -2343,6 +2704,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element) return; } + if (type == "scrollbaroptions") { + parseScrollBarOptions(data, description); + return; + } + // Ignore others infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\"" << std::endl; @@ -2385,37 +2751,45 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // Remove children removeChildren(); - for (auto &table_it : m_tables) { + for (auto &table_it : m_tables) table_it.second->drop(); - } + for (auto &inventorylist_it : m_inventorylists) + inventorylist_it->drop(); + for (auto &checkbox_it : m_checkboxes) + checkbox_it.second->drop(); + for (auto &scrollbar_it : m_scrollbars) + scrollbar_it.second->drop(); + for (auto &background_it : m_backgrounds) + background_it->drop(); + for (auto &tooltip_rect_it : m_tooltip_rects) + tooltip_rect_it.first->drop(); + for (auto &clickthrough_it : m_clickthrough_elements) + clickthrough_it->drop(); mydata.size= v2s32(100,100); mydata.screensize = screensize; mydata.offset = v2f32(0.5f, 0.5f); mydata.anchor = v2f32(0.5f, 0.5f); + mydata.simple_field_count = 0; // Base position of contents of form mydata.basepos = getBasePos(); - /* Convert m_init_draw_spec to m_inventorylists */ - m_inventorylists.clear(); - m_images.clear(); m_backgrounds.clear(); - m_itemimages.clear(); m_tables.clear(); m_checkboxes.clear(); m_scrollbars.clear(); m_fields.clear(); - m_boxes.clear(); m_tooltips.clear(); m_tooltip_rects.clear(); m_inventory_rings.clear(); - m_static_texts.clear(); m_dropdowns.clear(); theme_by_name.clear(); theme_by_type.clear(); + m_clickthrough_elements.clear(); + m_bgnonfullscreen = true; m_bgfullscreen = false; m_formspec_version = 1; @@ -2440,15 +2814,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) ); } - m_slotbg_n = video::SColor(255,128,128,128); - m_slotbg_h = video::SColor(255,192,192,192); - m_default_tooltip_bgcolor = video::SColor(255,110,130,60); m_default_tooltip_color = video::SColor(255,255,255,255); - m_slotbordercolor = video::SColor(200,0,0,0); - m_slotborder = false; - // Add tooltip { assert(!m_tooltip_element); @@ -2654,6 +3022,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) pos_offset = v2f32(); + // used for formspec versions < 3 + core::list<IGUIElement *>::Iterator legacy_sort_start = Children.getLast(); + if (enable_prepends) { // Backup the coordinates so that prepends can use the coordinates of choice. bool rc_backup = mydata.real_coordinates; @@ -2664,6 +3035,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) for (const auto &element : prepend_elements) parseElement(&mydata, element); + // legacy sorting for formspec versions < 3 + if (m_formspec_version >= 3) + // prepends do not need to be reordered + legacy_sort_start = Children.getLast(); + else if (version_backup >= 3) + // only prepends elements have to be reordered + legacySortElements(legacy_sort_start); + m_formspec_version = version_backup; mydata.real_coordinates = rc_backup; // Restore coordinates } @@ -2679,30 +3058,31 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // If there are fields without explicit size[], add a "Proceed" // button and adjust size to fit all the fields. - if (!m_fields.empty() && !mydata.explicit_size) { + if (mydata.simple_field_count > 0 && !mydata.explicit_size) { mydata.rect = core::rect<s32>( - mydata.screensize.X/2 - 580/2, - mydata.screensize.Y/2 - 300/2, - mydata.screensize.X/2 + 580/2, - mydata.screensize.Y/2 + 240/2+(m_fields.size()*60) + mydata.screensize.X / 2 - 580 / 2, + mydata.screensize.Y / 2 - 300 / 2, + mydata.screensize.X / 2 + 580 / 2, + mydata.screensize.Y / 2 + 240 / 2 + mydata.simple_field_count * 60 ); + DesiredRect = mydata.rect; recalculateAbsolutePosition(false); mydata.basepos = getBasePos(); { v2s32 pos = mydata.basepos; - pos.Y = ((m_fields.size()+2)*60); + pos.Y = (mydata.simple_field_count + 2) * 60; v2s32 size = DesiredRect.getSize(); - mydata.rect = - core::rect<s32>(size.X/2-70, pos.Y, - (size.X/2-70)+140, pos.Y + (m_btn_height*2)); + mydata.rect = core::rect<s32>( + size.X / 2 - 70, pos.Y, + size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2 + ); const wchar_t *text = wgettext("Proceed"); - Environment->addButton(mydata.rect, this, 257, text); + GUIButton::addButton(Environment, mydata.rect, this, 257, text); delete[] text; } - } //set initial focus if parser didn't set it @@ -2713,6 +3093,51 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) setInitialFocus(); skin->setFont(old_font); + + // legacy sorting + if (m_formspec_version < 3) + legacySortElements(legacy_sort_start); +} + +void GUIFormSpecMenu::legacySortElements(core::list<IGUIElement *>::Iterator from) +{ + /* + Draw order for formspec_version <= 2: + -3 bgcolor + -2 background + -1 box + 0 All other elements + 1 image + 2 item_image, item_image_button + 3 list + 4 label + */ + + if (from == Children.end()) + from = Children.begin(); + else + from++; + + core::list<IGUIElement *>::Iterator to = Children.end(); + // 1: Copy into a sortable container + std::vector<IGUIElement *> elements; + for (auto it = from; it != to; ++it) + elements.emplace_back(*it); + + // 2: Sort the container + std::stable_sort(elements.begin(), elements.end(), + [this] (const IGUIElement *a, const IGUIElement *b) -> bool { + const FieldSpec *spec_a = getSpecByID(a->getID()); + const FieldSpec *spec_b = getSpecByID(b->getID()); + return spec_a && spec_b && + spec_a->priority < spec_b->priority; + }); + + // 3: Re-assign the pointers + for (auto e : elements) { + *from = e; + from++; + } } #ifdef __ANDROID__ @@ -2724,13 +3149,13 @@ bool GUIFormSpecMenu::getAndroidUIInput() std::string fieldname = m_jni_field_name; m_jni_field_name.clear(); - for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); + for (std::vector<FieldSpec>::iterator iter = m_fields.begin(); iter != m_fields.end(); ++iter) { if (iter->fname != fieldname) { continue; } - IGUIElement* tochange = getElementFromId(iter->fid); + IGUIElement *tochange = getElementFromId(iter->fid, true); if (tochange == 0) { return false; @@ -2748,135 +3173,18 @@ bool GUIFormSpecMenu::getAndroidUIInput() } #endif -GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const +GUIInventoryList::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const { - core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y); - - for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) { - for(s32 i=0; i<s.geom.X*s.geom.Y; i++) { - s32 item_i = i + s.start_item_i; + core::rect<s32> imgrect(0, 0, imgsize.X, imgsize.Y); - s32 x; - s32 y; - if (s.real_coordinates) { - x = (i%s.geom.X) * (imgsize.X * 1.25); - y = (i/s.geom.X) * (imgsize.Y * 1.25); - } else { - x = (i%s.geom.X) * spacing.X; - y = (i/s.geom.X) * spacing.Y; - } - v2s32 p0(x,y); - core::rect<s32> rect = imgrect + s.pos + p0; - if(rect.isPointInside(p)) - { - return ItemSpec(s.inventoryloc, s.listname, item_i); - } - } + for (const GUIInventoryList *e : m_inventorylists) { + s32 item_index = e->getItemIndexAtPos(p); + if (item_index != -1) + return GUIInventoryList::ItemSpec(e->getInventoryloc(), e->getListname(), + item_index); } - return ItemSpec(InventoryLocation(), "", -1); -} - -void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer, - bool &item_hovered) -{ - video::IVideoDriver* driver = Environment->getVideoDriver(); - - Inventory *inv = m_invmgr->getInventory(s.inventoryloc); - if(!inv){ - warningstream<<"GUIFormSpecMenu::drawList(): " - <<"The inventory location " - <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist" - <<std::endl; - return; - } - InventoryList *ilist = inv->getList(s.listname); - if(!ilist){ - warningstream<<"GUIFormSpecMenu::drawList(): " - <<"The inventory list \""<<s.listname<<"\" @ \"" - <<s.inventoryloc.dump()<<"\" doesn't exist" - <<std::endl; - return; - } - - core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y); - - for (s32 i = 0; i < s.geom.X * s.geom.Y; i++) { - s32 item_i = i + s.start_item_i; - if (item_i >= (s32)ilist->getSize()) - break; - - s32 x; - s32 y; - if (s.real_coordinates) { - x = (i%s.geom.X) * (imgsize.X * 1.25); - y = (i/s.geom.X) * (imgsize.Y * 1.25); - } else { - x = (i%s.geom.X) * spacing.X; - y = (i/s.geom.X) * spacing.Y; - } - v2s32 p(x,y); - core::rect<s32> rect = imgrect + s.pos + p; - ItemStack item = ilist->getItem(item_i); - - bool selected = m_selected_item - && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv - && m_selected_item->listname == s.listname - && m_selected_item->i == item_i; - bool hovering = rect.isPointInside(m_pointer); - ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED : - (hovering ? IT_ROT_HOVERED : IT_ROT_NONE); - - if (layer == 0) { - if (hovering) { - item_hovered = true; - driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect); - } else { - driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect); - } - } - - //Draw inv slot borders - if (m_slotborder) { - s32 x1 = rect.UpperLeftCorner.X; - s32 y1 = rect.UpperLeftCorner.Y; - s32 x2 = rect.LowerRightCorner.X; - s32 y2 = rect.LowerRightCorner.Y; - s32 border = 1; - driver->draw2DRectangle(m_slotbordercolor, - core::rect<s32>(v2s32(x1 - border, y1 - border), - v2s32(x2 + border, y1)), NULL); - driver->draw2DRectangle(m_slotbordercolor, - core::rect<s32>(v2s32(x1 - border, y2), - v2s32(x2 + border, y2 + border)), NULL); - driver->draw2DRectangle(m_slotbordercolor, - core::rect<s32>(v2s32(x1 - border, y1), - v2s32(x1, y2)), NULL); - driver->draw2DRectangle(m_slotbordercolor, - core::rect<s32>(v2s32(x2, y1), - v2s32(x2 + border, y2)), NULL); - } - - if (layer == 1) { - if (selected) - item.takeItem(m_selected_amount); - - if (!item.empty()) { - // Draw item stack - drawItemStack(driver, m_font, item, - rect, &AbsoluteClippingRect, m_client, - rotation_kind); - // Draw tooltip - if (hovering && !m_selected_item) { - std::string tooltip = item.getDescription(m_client->idef()); - if (m_tooltip_append_itemname) - tooltip += "\n[" + item.name + "]"; - showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color, - m_default_tooltip_bgcolor); - } - } - } - } + return GUIInventoryList::ItemSpec(InventoryLocation(), "", -1); } void GUIFormSpecMenu::drawSelectedItem() @@ -2884,9 +3192,10 @@ void GUIFormSpecMenu::drawSelectedItem() video::IVideoDriver* driver = Environment->getVideoDriver(); if (!m_selected_item) { + // reset rotation time drawItemStack(driver, m_font, ItemStack(), - core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), - NULL, m_client, IT_ROT_DRAGGED); + core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL, + m_client, IT_ROT_DRAGGED); return; } @@ -2918,22 +3227,31 @@ void GUIFormSpecMenu::drawMenu() gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); + m_hovered_item_tooltips.clear(); + updateSelectedItem(); video::IVideoDriver* driver = Environment->getVideoDriver(); + /* + Draw background color + */ v2u32 screenSize = driver->getScreenSize(); core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y); if (m_bgfullscreen) driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg); - else + if (m_bgnonfullscreen) driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect); + /* + Draw rect_mode tooltip + */ m_tooltip_element->setVisible(false); for (const auto &pair : m_tooltip_rects) { - if (pair.first.isPointInside(m_pointer)) { + const core::rect<s32> &rect = pair.first->getAbsoluteClippingRect(); + if (rect.getArea() > 0 && rect.isPointInside(m_pointer)) { const std::wstring &text = pair.second.tooltip; if (!text.empty()) { showTooltip(text, pair.second.color, pair.second.bgcolor); @@ -2945,135 +3263,33 @@ void GUIFormSpecMenu::drawMenu() /* Draw backgrounds */ - for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) { - video::ITexture *texture = m_tsrc->getTexture(spec.name); - - if (texture != 0) { - // Image size on screen - core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); - // Image rectangle on screen - core::rect<s32> rect = imgrect + spec.pos; - // Middle rect for 9-slicing - core::rect<s32> middle = spec.middle; - - if (spec.clip) { - core::dimension2d<s32> absrec_size = AbsoluteRect.getSize(); - rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X, - AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y, - AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X, - AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y); - } - - if (middle.getArea() == 0) { - const video::SColor color(255, 255, 255, 255); - const video::SColor colors[] = {color, color, color, color}; - draw2DImageFilterScaled(driver, texture, rect, - core::rect<s32>(core::position2d<s32>(0, 0), - core::dimension2di(texture->getOriginalSize())), - NULL/*&AbsoluteClippingRect*/, colors, true); - } else { - // `-x` is interpreted as `w - x` - if (middle.LowerRightCorner.X < 0) { - middle.LowerRightCorner.X += texture->getOriginalSize().Width; - } - if (middle.LowerRightCorner.Y < 0) { - middle.LowerRightCorner.Y += texture->getOriginalSize().Height; - } - draw2DImage9Slice(driver, texture, rect, middle); - } - } else { - errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl; - errorstream << "\t" << spec.name << std::endl; - } + for (gui::IGUIElement *e : m_backgrounds) { + e->setVisible(true); + e->draw(); + e->setVisible(false); } - /* - Draw Boxes - */ - for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) { - irr::video::SColor todraw = spec.color; - - core::rect<s32> rect(spec.pos.X,spec.pos.Y, - spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y); - - driver->draw2DRectangle(todraw, rect, 0); - } + // Some elements are only visible while being drawn + for (gui::IGUIElement *e : m_clickthrough_elements) + e->setVisible(true); /* Call base class + (This is where all the drawing happens.) */ gui::IGUIElement::draw(); - /* - Draw images - */ - for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) { - video::ITexture *texture = m_tsrc->getTexture(spec.name); + for (gui::IGUIElement *e : m_clickthrough_elements) + e->setVisible(false); - if (texture != 0) { - const core::dimension2d<u32>& img_origsize = texture->getOriginalSize(); - // Image size on screen - core::rect<s32> imgrect; - - if (spec.scale) - imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y); - else { - - imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height); - } - // Image rectangle on screen - core::rect<s32> rect = imgrect + spec.pos; - const video::SColor color(255,255,255,255); - const video::SColor colors[] = {color,color,color,color}; - draw2DImageFilterScaled(driver, texture, rect, - core::rect<s32>(core::position2d<s32>(0,0),img_origsize), - NULL/*&AbsoluteClippingRect*/, colors, true); - } - else { - errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl; - errorstream << "\t" << spec.name << std::endl; - } + // Draw hovered item tooltips + for (const std::string &tooltip : m_hovered_item_tooltips) { + showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color, + m_default_tooltip_bgcolor); } - /* - Draw item images - */ - for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) { - if (m_client == 0) - break; - - IItemDefManager *idef = m_client->idef(); - ItemStack item; - item.deSerialize(spec.item_name, idef); - core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y); - // Viewport rectangle on screen - core::rect<s32> rect = imgrect + spec.pos; - if (spec.parent_button && spec.parent_button->isPressed()) { -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - rect += core::dimension2d<s32>( - 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight()); -#else - rect += core::dimension2d<s32>( - skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X), - skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)); -#endif - } - drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect, - m_client, IT_ROT_NONE); - } - - /* - Draw items - Layer 0: Item slot rectangles - Layer 1: Item images; prepare tooltip - */ - bool item_hovered = false; - for (int layer = 0; layer < 2; layer++) { - for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) { - drawList(spec, layer, item_hovered); - } - } - if (!item_hovered) { + if (m_hovered_item_tooltips.empty()) { + // reset rotation time drawItemStack(driver, m_font, ItemStack(), core::rect<s32>(v2s32(0, 0), v2s32(0, 0)), NULL, m_client, IT_ROT_HOVERED); @@ -3085,32 +3301,18 @@ void GUIFormSpecMenu::drawMenu() #endif /* - Draw static text elements - */ - for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) { - core::rect<s32> rect = spec.rect; - if (spec.parent_button && spec.parent_button->isPressed()) { -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - rect += core::dimension2d<s32>( - 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight()); -#else - // Use image offset instead of text's because its a bit smaller - // and fits better, also TEXT_OFFSET_X is always 0 - rect += core::dimension2d<s32>( - skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X), - skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)); -#endif - } - video::SColor color(255, 255, 255, 255); - m_font->draw(spec.text.c_str(), rect, color, true, true, &rect); - } - - /* - Draw fields/buttons tooltips + Draw fields/buttons tooltips and update the mouse cursor */ gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(m_pointer); +#ifndef HAVE_TOUCHSCREENGUI + gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()-> + getCursorControl(); + gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon(); +#endif + bool hovered_element_found = false; + if (hovered != NULL) { s32 id = hovered->getID(); @@ -3126,23 +3328,41 @@ void GUIFormSpecMenu::drawMenu() } } - // Find and update the current tooltip - if (id != -1 && delta >= m_tooltip_show_delay) { + // Find and update the current tooltip and cursor icon + if (id != -1) { for (const FieldSpec &field : m_fields) { if (field.fid != id) continue; - const std::wstring &text = m_tooltips[field.fname].tooltip; - if (!text.empty()) - showTooltip(text, m_tooltips[field.fname].color, - m_tooltips[field.fname].bgcolor); + if (delta >= m_tooltip_show_delay) { + const std::wstring &text = m_tooltips[field.fname].tooltip; + if (!text.empty()) + showTooltip(text, m_tooltips[field.fname].color, + m_tooltips[field.fname].bgcolor); + } + +#ifndef HAVE_TOUCHSCREENGUI + if (field.ftype != f_HyperText && // Handled directly in guiHyperText + current_cursor_icon != field.fcursor_icon) + cursor_control->setActiveIcon(field.fcursor_icon); +#endif + + hovered_element_found = true; break; } } } + if (!hovered_element_found) { + // no element is hovered +#ifndef HAVE_TOUCHSCREENGUI + if (current_cursor_icon != ECI_NORMAL) + cursor_control->setActiveIcon(ECI_NORMAL); +#endif + } + m_tooltip_element->draw(); /* @@ -3157,19 +3377,16 @@ void GUIFormSpecMenu::drawMenu() void GUIFormSpecMenu::showTooltip(const std::wstring &text, const irr::video::SColor &color, const irr::video::SColor &bgcolor) { - const std::wstring ntext = translate_string(text); - m_tooltip_element->setOverrideColor(color); - m_tooltip_element->setBackgroundColor(bgcolor); - setStaticText(m_tooltip_element, ntext.c_str()); + EnrichedString ntext(text); + ntext.setDefaultColor(color); + ntext.setBackground(bgcolor); + + setStaticText(m_tooltip_element, ntext); // Tooltip size and offset s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; -#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1 - std::vector<std::wstring> text_rows = str_split(ntext, L'\n'); - s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5; -#else s32 tooltip_height = m_tooltip_element->getTextHeight() + 5; -#endif + v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; @@ -3206,11 +3423,11 @@ void GUIFormSpecMenu::updateSelectedItem() // If craftresult is nonempty and nothing else is selected, select it now. if (!m_selected_item) { - for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) { - if (s.listname != "craftpreview") + for (const GUIInventoryList *e : m_inventorylists) { + if (e->getListname() != "craftpreview") continue; - Inventory *inv = m_invmgr->getInventory(s.inventoryloc); + Inventory *inv = m_invmgr->getInventory(e->getInventoryloc()); if (!inv) continue; @@ -3224,8 +3441,8 @@ void GUIFormSpecMenu::updateSelectedItem() continue; // Grab selected item from the crafting result list - m_selected_item = new ItemSpec; - m_selected_item->inventoryloc = s.inventoryloc; + m_selected_item = new GUIInventoryList::ItemSpec; + m_selected_item->inventoryloc = e->getInventoryloc(); m_selected_item->listname = "craftresult"; m_selected_item->i = 0; m_selected_amount = item.count; @@ -3246,16 +3463,12 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() // If the selected stack has become smaller, adjust m_selected_amount. // Return the selected stack. - if(m_selected_item) - { - if(m_selected_item->isValid()) - { + if (m_selected_item) { + if (m_selected_item->isValid()) { Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc); - if(inv) - { + if (inv) { InventoryList *list = inv->getList(m_selected_item->listname); - if(list && (u32) m_selected_item->i < list->getSize()) - { + if (list && (u32) m_selected_item->i < list->getSize()) { ItemStack stack = list->getItem(m_selected_item->i); if (!m_selected_swap.empty()) { if (m_selected_swap.name == stack.name && @@ -3273,7 +3486,7 @@ ItemStack GUIFormSpecMenu::verifySelectedItem() // selection was not valid delete m_selected_item; - m_selected_item = NULL; + m_selected_item = nullptr; m_selected_amount = 0; m_selected_dragging = false; } @@ -3322,7 +3535,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) } for (const GUIFormSpecMenu::FieldSpec &s : m_fields) { - if(s.send) { + if (s.send) { std::string name = s.fname; if (s.ftype == f_Button) { fields[name] = wide_to_utf8(s.flabel); @@ -3331,14 +3544,17 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) if (table) { fields[name] = table->checkEvent(); } - } - else if(s.ftype == f_DropDown) { - // no dynamic cast possible due to some distributions shipped - // without rtti support in irrlicht - IGUIElement * element = getElementFromId(s.fid); + } else if (s.ftype == f_DropDown) { + // No dynamic cast possible due to some distributions shipped + // without rtti support in Irrlicht + IGUIElement *element = getElementFromId(s.fid, true); gui::IGUIComboBox *e = NULL; if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) { - e = static_cast<gui::IGUIComboBox*>(element); + e = static_cast<gui::IGUIComboBox *>(element); + } else { + warningstream << "GUIFormSpecMenu::acceptInput: dropdown " + << "field without dropdown element" << std::endl; + continue; } s32 selected = e->getSelected(); if (selected >= 0) { @@ -3348,12 +3564,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) fields[name] = (*dropdown_values)[selected]; } } - } - else if (s.ftype == f_TabHeader) { - // no dynamic cast possible due to some distributions shipped - // without rttzi support in irrlicht - IGUIElement * element = getElementFromId(s.fid); - gui::IGUITabControl *e = NULL; + } else if (s.ftype == f_TabHeader) { + // No dynamic cast possible due to some distributions shipped + // without rtti support in Irrlicht + IGUIElement *element = getElementFromId(s.fid, true); + gui::IGUITabControl *e = nullptr; if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) { e = static_cast<gui::IGUITabControl *>(element); } @@ -3363,12 +3578,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) ss << (e->getActiveTab() +1); fields[name] = ss.str(); } - } - else if (s.ftype == f_CheckBox) { - // no dynamic cast possible due to some distributions shipped - // without rtti support in irrlicht - IGUIElement * element = getElementFromId(s.fid); - gui::IGUICheckBox *e = NULL; + } else if (s.ftype == f_CheckBox) { + // No dynamic cast possible due to some distributions shipped + // without rtti support in Irrlicht + IGUIElement *element = getElementFromId(s.fid, true); + gui::IGUICheckBox *e = nullptr; if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) { e = static_cast<gui::IGUICheckBox*>(element); } @@ -3379,17 +3593,15 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) else fields[name] = "false"; } - } - else if (s.ftype == f_ScrollBar) { - // no dynamic cast possible due to some distributions shipped - // without rtti support in irrlicht - IGUIElement * element = getElementFromId(s.fid); - gui::IGUIScrollBar *e = NULL; - if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) { - e = static_cast<gui::IGUIScrollBar*>(element); - } - - if (e != 0) { + } else if (s.ftype == f_ScrollBar) { + // No dynamic cast possible due to some distributions shipped + // without rtti support in Irrlicht + IGUIElement *element = getElementFromId(s.fid, true); + GUIScrollBar *e = nullptr; + if (element && element->getType() == gui::EGUIET_ELEMENT) + e = static_cast<GUIScrollBar *>(element); + + if (e) { std::stringstream os; os << e->getPos(); if (s.fdefault == L"Changed") @@ -3397,13 +3609,20 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) else fields[name] = "VAL:" + os.str(); } - } - else - { - IGUIElement* e = getElementFromId(s.fid); - if(e != NULL) { + } else if (s.ftype == f_AnimatedImage) { + // No dynamic cast possible due to some distributions shipped + // without rtti support in Irrlicht + IGUIElement *element = getElementFromId(s.fid, true); + GUIAnimatedImage *e = nullptr; + if (element && element->getType() == gui::EGUIET_ELEMENT) + e = static_cast<GUIAnimatedImage *>(element); + + if (e) + fields[name] = std::to_string(e->getFrameIndex() + 1); + } else { + IGUIElement *e = getElementFromId(s.fid, true); + if (e) fields[name] = wide_to_utf8(e->getText()); - } } } } @@ -3412,9 +3631,9 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) } } -static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent) +static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) { - while(tocheck != NULL) { + while (tocheck) { if (tocheck == parent) { return true; } @@ -3451,8 +3670,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } // Fix Esc/Return key being eaten by checkboxen and tables - if(event.EventType==EET_KEY_INPUT_EVENT) { - KeyPress kp(event.KeyInput); + if (event.EventType == EET_KEY_INPUT_EVENT) { + KeyPress kp(event.KeyInput); if (kp == EscapeKey || kp == CancelKey || kp == getKeySetting("keymap_inventory") || event.KeyInput.Key==KEY_RETURN) { @@ -3467,9 +3686,11 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } } } - // Mouse wheel events: send to hovered element instead of focused - if(event.EventType==EET_MOUSE_INPUT_EVENT - && event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + // Mouse wheel and move events: send to hovered element instead of focused + if (event.EventType == EET_MOUSE_INPUT_EVENT && + (event.MouseInput.Event == EMIE_MOUSE_WHEEL || + (event.MouseInput.Event == EMIE_MOUSE_MOVED && + event.MouseInput.ButtonStates == 0))) { s32 x = event.MouseInput.X; s32 y = event.MouseInput.Y; gui::IGUIElement *hovered = @@ -3477,7 +3698,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) core::position2d<s32>(x, y)); if (hovered && isMyChild(hovered)) { hovered->OnEvent(event); - return true; + return event.MouseInput.Event == EMIE_MOUSE_WHEEL; } } @@ -3490,7 +3711,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { m_old_tooltip_id = -1; } - if (!isChild(hovered,this)) { + if (!isChild(hovered, this)) { if (DoubleClickDetection(event)) { return true; } @@ -3661,7 +3882,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_old_tooltip_id = -1; updateSelectedItem(); - ItemSpec s = getItemAtPos(m_pointer); + GUIInventoryList::ItemSpec s = getItemAtPos(m_pointer); Inventory *inv_selected = NULL; Inventory *inv_s = NULL; @@ -3763,8 +3984,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) case BET_DOWN: // Some mouse button has been pressed - //infostream<<"Mouse button "<<button<<" pressed at p=(" - // <<p.X<<","<<p.Y<<")"<<std::endl; + //infostream << "Mouse button " << button << " pressed at p=(" + // << event.MouseInput.X << "," << event.MouseInput.Y << ")" + // << std::endl; m_selected_dragging = false; @@ -3774,7 +3996,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } else if (!m_selected_item) { if (s_count && button != BET_WHEEL_UP) { // Non-empty stack has been clicked: select or shift-move it - m_selected_item = new ItemSpec(s); + m_selected_item = new GUIInventoryList::ItemSpec(s); u32 count; if (button == BET_RIGHT) @@ -4002,7 +4224,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // if there are no items selected or the selected item // belongs to craftresult list, proceed with crafting - if (m_selected_item == NULL || + if (!m_selected_item || !m_selected_item->isValid() || m_selected_item->listname == "craftresult") { assert(inv_s); @@ -4020,14 +4242,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (m_selected_amount == 0) { m_selected_swap.clear(); delete m_selected_item; - m_selected_item = NULL; + m_selected_item = nullptr; m_selected_amount = 0; m_selected_dragging = false; } m_old_pointer = m_pointer; } - if (event.EventType == EET_GUI_EVENT) { + if (event.EventType == EET_GUI_EVENT) { if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED && isVisible()) { // find the element that was clicked @@ -4054,9 +4276,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) { - unsigned int btn_id = event.GUIEvent.Caller->getID(); + s32 caller_id = event.GUIEvent.Caller->getID(); - if (btn_id == 257) { + if (caller_id == 257) { if (m_allowclose) { acceptInput(quit_mode_accept); quitMenu(); @@ -4072,8 +4294,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) for (GUIFormSpecMenu::FieldSpec &s : m_fields) { // if its a button, set the send field so // lua knows which button was pressed - if ((s.ftype == f_Button || s.ftype == f_CheckBox) && - s.fid == event.GUIEvent.Caller->getID()) { + + if (caller_id != s.fid) + continue; + + if (s.ftype == f_Button || s.ftype == f_CheckBox) { s.send = true; if (s.is_exit) { if (m_allowclose) { @@ -4089,8 +4314,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.send = false; return true; - } else if ((s.ftype == f_DropDown) && - (s.fid == event.GUIEvent.Caller->getID())) { + } else if (s.ftype == f_DropDown) { // only send the changed dropdown for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) { if (s2.ftype == f_DropDown) { @@ -4108,11 +4332,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } return true; - } else if ((s.ftype == f_ScrollBar) && - (s.fid == event.GUIEvent.Caller->getID())) { + } else if (s.ftype == f_ScrollBar) { s.fdefault = L"Changed"; acceptInput(quit_mode_no); s.fdefault = L""; + } else if (s.ftype == f_Unknown || s.ftype == f_HyperText) { + s.send = true; + acceptInput(); + s.send = false; } } } @@ -4175,13 +4402,22 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) std::string GUIFormSpecMenu::getNameByID(s32 id) { for (FieldSpec &spec : m_fields) { - if (spec.fid == id) { + if (spec.fid == id) return spec.fname; - } } return ""; } + +const GUIFormSpecMenu::FieldSpec *GUIFormSpecMenu::getSpecByID(s32 id) +{ + for (FieldSpec &spec : m_fields) { + if (spec.fid == id) + return &spec; + } + return nullptr; +} + /** * get label of element by id * @param id of element @@ -4190,9 +4426,8 @@ std::string GUIFormSpecMenu::getNameByID(s32 id) std::wstring GUIFormSpecMenu::getLabelByID(s32 id) { for (FieldSpec &spec : m_fields) { - if (spec.fid == id) { + if (spec.fid == id) return spec.flabel; - } } return L""; } diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 46df0930c..17bfef205 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "inventorymanager.h" #include "modalMenu.h" +#include "guiInventoryList.h" #include "guiTable.h" #include "network/networkprotocol.h" #include "client/joystick_controller.h" @@ -36,6 +37,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class InventoryManager; class ISimpleTextureSource; class Client; +class GUIScrollBar; +class TexturePool; typedef enum { f_Button, @@ -44,6 +47,10 @@ typedef enum { f_CheckBox, f_DropDown, f_ScrollBar, + f_Box, + f_ItemImage, + f_HyperText, + f_AnimatedImage, f_Unknown } FormspecFieldType; @@ -75,51 +82,6 @@ public: class GUIFormSpecMenu : public GUIModalMenu { - struct ItemSpec - { - ItemSpec() = default; - - ItemSpec(const InventoryLocation &a_inventoryloc, - const std::string &a_listname, - s32 a_i) : - inventoryloc(a_inventoryloc), - listname(a_listname), - i(a_i) - { - } - - bool isValid() const { return i != -1; } - - InventoryLocation inventoryloc; - std::string listname; - s32 i = -1; - }; - - struct ListDrawSpec - { - ListDrawSpec() = default; - - ListDrawSpec(const InventoryLocation &a_inventoryloc, - const std::string &a_listname, - v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i, - bool a_real_coordinates): - inventoryloc(a_inventoryloc), - listname(a_listname), - pos(a_pos), - geom(a_geom), - start_item_i(a_start_item_i), - real_coordinates(a_real_coordinates) - { - } - - InventoryLocation inventoryloc; - std::string listname; - v2s32 pos; - v2s32 geom; - s32 start_item_i; - bool real_coordinates; - }; - struct ListRingSpec { ListRingSpec() = default; @@ -135,121 +97,36 @@ class GUIFormSpecMenu : public GUIModalMenu std::string listname; }; - struct ImageDrawSpec - { - ImageDrawSpec(): - parent_button(NULL), - clip(false) - { - } - - ImageDrawSpec(const std::string &a_name, - const std::string &a_item_name, - gui::IGUIButton *a_parent_button, - const v2s32 &a_pos, const v2s32 &a_geom): - name(a_name), - item_name(a_item_name), - parent_button(a_parent_button), - pos(a_pos), - geom(a_geom), - scale(true), - clip(false) - { - } - - ImageDrawSpec(const std::string &a_name, - const std::string &a_item_name, - const v2s32 &a_pos, const v2s32 &a_geom): - name(a_name), - item_name(a_item_name), - parent_button(NULL), - pos(a_pos), - geom(a_geom), - scale(true), - clip(false) - { - } - - ImageDrawSpec(const std::string &a_name, - const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false): - name(a_name), - parent_button(NULL), - pos(a_pos), - geom(a_geom), - scale(true), - clip(clip) - { - } - - ImageDrawSpec(const std::string &a_name, - const v2s32 &a_pos, const v2s32 &a_geom, const core::rect<s32> &middle, bool clip=false): - name(a_name), - parent_button(NULL), - pos(a_pos), - geom(a_geom), - middle(middle), - scale(true), - clip(clip) - { - } - - ImageDrawSpec(const std::string &a_name, - const v2s32 &a_pos): - name(a_name), - parent_button(NULL), - pos(a_pos), - scale(false), - clip(false) - { - } - - std::string name; - std::string item_name; - gui::IGUIButton *parent_button; - v2s32 pos; - v2s32 geom; - core::rect<s32> middle; - bool scale; - bool clip; - }; - struct FieldSpec { FieldSpec() = default; FieldSpec(const std::string &name, const std::wstring &label, - const std::wstring &default_text, int id) : + const std::wstring &default_text, s32 id, int priority = 0, + gui::ECURSOR_ICON cursor_icon = ECI_NORMAL) : fname(name), flabel(label), fdefault(unescape_enriched(translate_string(default_text))), fid(id), send(false), ftype(f_Unknown), - is_exit(false) + is_exit(false), + priority(priority), + fcursor_icon(cursor_icon) { } std::string fname; std::wstring flabel; std::wstring fdefault; - int fid; + s32 fid; bool send; FormspecFieldType ftype; bool is_exit; + // Draw priority for formspec version < 3 + int priority; core::rect<s32> rect; - }; - - struct BoxDrawSpec - { - BoxDrawSpec(v2s32 a_pos, v2s32 a_geom, irr::video::SColor a_color): - pos(a_pos), - geom(a_geom), - color(a_color) - { - } - v2s32 pos; - v2s32 geom; - irr::video::SColor color; + gui::ECURSOR_ICON fcursor_icon; }; struct TooltipSpec @@ -268,35 +145,6 @@ class GUIFormSpecMenu : public GUIModalMenu irr::video::SColor color; }; - struct StaticTextSpec - { - StaticTextSpec(): - parent_button(NULL) - { - } - - StaticTextSpec(const std::wstring &a_text, - const core::rect<s32> &a_rect): - text(a_text), - rect(a_rect), - parent_button(NULL) - { - } - - StaticTextSpec(const std::wstring &a_text, - const core::rect<s32> &a_rect, - gui::IGUIButton *a_parent_button): - text(a_text), - rect(a_rect), - parent_button(a_parent_button) - { - } - - std::wstring text; - core::rect<s32> rect; - gui::IGUIButton *parent_button; - }; - public: GUIFormSpecMenu(JoystickController *joystick, gui::IGUIElement* parent, s32 id, @@ -365,13 +213,37 @@ public: m_focused_element = elementname; } + Client *getClient() const + { + return m_client; + } + + const GUIInventoryList::ItemSpec *getSelectedItem() const + { + return m_selected_item; + } + + const u16 getSelectedAmount() const + { + return m_selected_amount; + } + + bool doTooltipAppendItemname() const + { + return m_tooltip_append_itemname; + } + + void addHoveredItemTooltip(const std::string &name) + { + m_hovered_item_tooltips.emplace_back(name); + } + /* Remove and re-add (or reposition) stuff */ void regenerateGui(v2u32 screensize); - ItemSpec getItemAtPos(v2s32 p) const; - void drawList(const ListDrawSpec &s, int layer, bool &item_hovered); + GUIInventoryList::ItemSpec getItemAtPos(v2s32 p) const; void drawSelectedItem(); void drawMenu(); void updateSelectedItem(); @@ -397,10 +269,9 @@ protected: } std::wstring getLabelByID(s32 id); std::string getNameByID(s32 id); - v2s32 getElementBasePos(bool absolute, - const std::vector<std::string> *v_pos); - v2s32 getRealCoordinateBasePos(bool absolute, - const std::vector<std::string> &v_pos); + const FieldSpec *getSpecByID(s32 id); + v2s32 getElementBasePos(const std::vector<std::string> *v_pos); + v2s32 getRealCoordinateBasePos(const std::vector<std::string> &v_pos); v2s32 getRealCoordinateGeometry(const std::vector<std::string> &v_geom); std::unordered_map<std::string, StyleSpec> theme_by_type; @@ -425,23 +296,20 @@ protected: std::string m_formspec_prepend; InventoryLocation m_current_inventory_location; - std::vector<ListDrawSpec> m_inventorylists; + std::vector<GUIInventoryList *> m_inventorylists; std::vector<ListRingSpec> m_inventory_rings; - std::vector<ImageDrawSpec> m_backgrounds; - std::vector<ImageDrawSpec> m_images; - std::vector<ImageDrawSpec> m_itemimages; - std::vector<BoxDrawSpec> m_boxes; + std::vector<gui::IGUIElement *> m_backgrounds; std::unordered_map<std::string, bool> field_close_on_enter; std::vector<FieldSpec> m_fields; - std::vector<StaticTextSpec> m_static_texts; - std::vector<std::pair<FieldSpec,GUITable*> > m_tables; - std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes; + std::vector<std::pair<FieldSpec, GUITable *>> m_tables; + std::vector<std::pair<FieldSpec, gui::IGUICheckBox *>> m_checkboxes; std::map<std::string, TooltipSpec> m_tooltips; - std::vector<std::pair<irr::core::rect<s32>, TooltipSpec>> m_tooltip_rects; - std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars; - std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns; + std::vector<std::pair<gui::IGUIElement *, TooltipSpec>> m_tooltip_rects; + std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars; + std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns; + std::vector<gui::IGUIElement *> m_clickthrough_elements; - ItemSpec *m_selected_item = nullptr; + GUIInventoryList::ItemSpec *m_selected_item = nullptr; u16 m_selected_amount = 0; bool m_selected_dragging = false; ItemStack m_selected_swap; @@ -459,16 +327,14 @@ protected: bool m_lock = false; v2u32 m_lockscreensize; + bool m_bgnonfullscreen; bool m_bgfullscreen; - bool m_slotborder; video::SColor m_bgcolor; video::SColor m_fullscreen_bgcolor; - video::SColor m_slotbg_n; - video::SColor m_slotbg_h; - video::SColor m_slotbordercolor; video::SColor m_default_tooltip_bgcolor; video::SColor m_default_tooltip_color; + private: IFormSource *m_form_src; TextDest *m_text_dst; @@ -479,6 +345,7 @@ private: typedef struct { bool explicit_size; bool real_coordinates; + u8 simple_field_count; v2f invsize; v2s32 size; v2f32 offset; @@ -489,6 +356,18 @@ private: std::string focused_fieldname; GUITable::TableOptions table_options; GUITable::TableColumns table_columns; + + GUIInventoryList::Options inventorylist_options; + + struct { + s32 max = 1000; + s32 min = 0; + s32 small_step = 10; + s32 large_step = 100; + s32 thumb_size = 1; + GUIScrollBar::ArrowVisibility arrow_visiblity = GUIScrollBar::DEFAULT; + } scrollbar_options; + // used to restore table selection/scroll/treeview state std::unordered_map<std::string, GUITable::DynamicData> table_dyndata; } parserData; @@ -502,6 +381,7 @@ private: fs_key_pendig current_keys_pending; std::string current_field_enter_pending = ""; + std::vector<std::string> m_hovered_item_tooltips; void parseElement(parserData* data, const std::string &element); @@ -512,6 +392,7 @@ private: void parseListRing(parserData* data, const std::string &element); void parseCheckbox(parserData* data, const std::string &element); void parseImage(parserData* data, const std::string &element); + void parseAnimatedImage(parserData *data, const std::string &element); void parseItemImage(parserData* data, const std::string &element); void parseButton(parserData* data, const std::string &element, const std::string &typ); @@ -529,6 +410,7 @@ private: void parseSimpleField(parserData* data,std::vector<std::string> &parts); void parseTextArea(parserData* data,std::vector<std::string>& parts, const std::string &type); + void parseHyperText(parserData *data, const std::string &element); void parseLabel(parserData* data, const std::string &element); void parseVertLabel(parserData* data, const std::string &element); void parseImageButton(parserData* data, const std::string &element, @@ -542,6 +424,7 @@ private: bool parseVersionDirect(const std::string &data); bool parseSizeDirect(parserData* data, const std::string &element); void parseScrollBar(parserData* data, const std::string &element); + void parseScrollBarOptions(parserData *data, const std::string &element); bool parsePositionDirect(parserData *data, const std::string &element); void parsePosition(parserData *data, const std::string &element); bool parseAnchorDirect(parserData *data, const std::string &element); @@ -554,6 +437,13 @@ private: const irr::video::SColor &bgcolor); /** + * In formspec version < 2 the elements were not ordered properly. Some element + * types were drawn before others. + * This function sorts the elements in the old order for backwards compatibility. + */ + void legacySortElements(core::list<IGUIElement *>::Iterator from); + + /** * check if event is part of a double click * @param event event to evaluate * @return true/false if a doubleclick was detected diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp new file mode 100644 index 000000000..e107b5a3e --- /dev/null +++ b/src/gui/guiHyperText.cpp @@ -0,0 +1,1158 @@ +/* +Minetest +Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.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 "IGUIEnvironment.h" +#include "IGUIElement.h" +#include "guiScrollBar.h" +#include "IGUIFont.h" +#include <vector> +#include <list> +#include <unordered_map> +using namespace irr::gui; +#include "client/fontengine.h" +#include <SColor.h> +#include "client/tile.h" +#include "IVideoDriver.h" +#include "client/client.h" +#include "client/renderingengine.h" +#include "hud.h" +#include "guiHyperText.h" +#include "util/string.h" + +bool check_color(const std::string &str) +{ + irr::video::SColor color; + return parseColorString(str, color, false); +} + +bool check_integer(const std::string &str) +{ + if (str.empty()) + return false; + + char *endptr = nullptr; + strtol(str.c_str(), &endptr, 10); + + return *endptr == '\0'; +} + +// ----------------------------------------------------------------------------- +// ParsedText - A text parser + +void ParsedText::Element::setStyle(StyleList &style) +{ + this->underline = is_yes(style["underline"]); + + video::SColor color; + + if (parseColorString(style["color"], color, false)) + this->color = color; + if (parseColorString(style["hovercolor"], color, false)) + this->hovercolor = color; + + unsigned int font_size = std::atoi(style["fontsize"].c_str()); + FontMode font_mode = FM_Standard; + if (style["fontstyle"] == "mono") + font_mode = FM_Mono; + + FontSpec spec(font_size, font_mode, + is_yes(style["bold"]), is_yes(style["italic"])); + + // TODO: find a way to check font validity + // Build a new fontengine ? + this->font = g_fontengine->getFont(spec); + + if (!this->font) + printf("No font found ! Size=%d, mode=%d, bold=%s, italic=%s\n", + font_size, font_mode, style["bold"].c_str(), + style["italic"].c_str()); +} + +void ParsedText::Paragraph::setStyle(StyleList &style) +{ + if (style["halign"] == "center") + this->halign = HALIGN_CENTER; + else if (style["halign"] == "right") + this->halign = HALIGN_RIGHT; + else if (style["halign"] == "justify") + this->halign = HALIGN_JUSTIFY; + else + this->halign = HALIGN_LEFT; +} + +ParsedText::ParsedText(const wchar_t *text) +{ + // Default style + m_root_tag.name = "root"; + m_root_tag.style["fontsize"] = "16"; + m_root_tag.style["fontstyle"] = "normal"; + m_root_tag.style["bold"] = "false"; + m_root_tag.style["italic"] = "false"; + m_root_tag.style["underline"] = "false"; + m_root_tag.style["halign"] = "left"; + m_root_tag.style["color"] = "#EEEEEE"; + m_root_tag.style["hovercolor"] = "#FF0000"; + + m_active_tags.push_front(&m_root_tag); + m_style = m_root_tag.style; + + // Default simple tags definitions + StyleList style; + + style["color"] = "#0000FF"; + style["underline"] = "true"; + m_elementtags["action"] = style; + style.clear(); + + style["bold"] = "true"; + m_elementtags["b"] = style; + style.clear(); + + style["italic"] = "true"; + m_elementtags["i"] = style; + style.clear(); + + style["underline"] = "true"; + m_elementtags["u"] = style; + style.clear(); + + style["fontstyle"] = "mono"; + m_elementtags["mono"] = style; + style.clear(); + + style["fontsize"] = m_root_tag.style["fontsize"]; + m_elementtags["normal"] = style; + style.clear(); + + style["fontsize"] = "24"; + m_elementtags["big"] = style; + style.clear(); + + style["fontsize"] = "36"; + m_elementtags["bigger"] = style; + style.clear(); + + style["halign"] = "center"; + m_paragraphtags["center"] = style; + style.clear(); + + style["halign"] = "justify"; + m_paragraphtags["justify"] = style; + style.clear(); + + style["halign"] = "left"; + m_paragraphtags["left"] = style; + style.clear(); + + style["halign"] = "right"; + m_paragraphtags["right"] = style; + style.clear(); + + m_element = NULL; + m_paragraph = NULL; + m_end_paragraph_reason = ER_NONE; + + parse(text); +} + +ParsedText::~ParsedText() +{ + for (auto &tag : m_not_root_tags) + delete tag; +} + +void ParsedText::parse(const wchar_t *text) +{ + wchar_t c; + u32 cursor = 0; + bool escape = false; + + while ((c = text[cursor]) != L'\0') { + cursor++; + + if (c == L'\r') { // Mac or Windows breaks + if (text[cursor] == L'\n') + cursor++; + // If text has begun, don't skip empty line + if (m_paragraph) { + endParagraph(ER_NEWLINE); + enterElement(ELEMENT_SEPARATOR); + } + escape = false; + continue; + } + + if (c == L'\n') { // Unix breaks + // If text has begun, don't skip empty line + if (m_paragraph) { + endParagraph(ER_NEWLINE); + enterElement(ELEMENT_SEPARATOR); + } + escape = false; + continue; + } + + if (escape) { + escape = false; + pushChar(c); + continue; + } + + if (c == L'\\') { + escape = true; + continue; + } + + // Tag check + if (c == L'<') { + u32 newcursor = parseTag(text, cursor); + if (newcursor > 0) { + cursor = newcursor; + continue; + } + } + + // Default behavior + pushChar(c); + } + + endParagraph(ER_NONE); +} + +void ParsedText::endElement() +{ + m_element = NULL; +} + +void ParsedText::endParagraph(EndReason reason) +{ + if (!m_paragraph) + return; + + EndReason previous = m_end_paragraph_reason; + m_end_paragraph_reason = reason; + if (m_empty_paragraph && (reason == ER_TAG || + (reason == ER_NEWLINE && previous == ER_TAG))) { + // Ignore last empty paragraph + m_paragraph = nullptr; + m_paragraphs.pop_back(); + return; + } + endElement(); + m_paragraph = NULL; +} + +void ParsedText::enterParagraph() +{ + if (!m_paragraph) { + m_paragraphs.emplace_back(); + m_paragraph = &m_paragraphs.back(); + m_paragraph->setStyle(m_style); + m_empty_paragraph = true; + } +} + +void ParsedText::enterElement(ElementType type) +{ + enterParagraph(); + + if (!m_element || m_element->type != type) { + m_paragraph->elements.emplace_back(); + m_element = &m_paragraph->elements.back(); + m_element->type = type; + m_element->tags = m_active_tags; + m_element->setStyle(m_style); + } +} + +void ParsedText::pushChar(wchar_t c) +{ + // New word if needed + if (c == L' ' || c == L'\t') { + if (!m_empty_paragraph) + enterElement(ELEMENT_SEPARATOR); + else + return; + } else { + m_empty_paragraph = false; + enterElement(ELEMENT_TEXT); + } + m_element->text += c; +} + +ParsedText::Tag *ParsedText::newTag(const std::string &name, const AttrsList &attrs) +{ + endElement(); + Tag *newtag = new Tag(); + newtag->name = name; + newtag->attrs = attrs; + m_not_root_tags.push_back(newtag); + return newtag; +} + +ParsedText::Tag *ParsedText::openTag(const std::string &name, const AttrsList &attrs) +{ + Tag *newtag = newTag(name, attrs); + m_active_tags.push_front(newtag); + return newtag; +} + +bool ParsedText::closeTag(const std::string &name) +{ + bool found = false; + for (auto id = m_active_tags.begin(); id != m_active_tags.end(); ++id) + if ((*id)->name == name) { + m_active_tags.erase(id); + found = true; + break; + } + return found; +} + +void ParsedText::parseGenericStyleAttr( + const std::string &name, const std::string &value, StyleList &style) +{ + // Color styles + if (name == "color" || name == "hovercolor") { + if (check_color(value)) + style[name] = value; + + // Boolean styles + } else if (name == "bold" || name == "italic" || name == "underline") { + style[name] = is_yes(value); + + } else if (name == "size") { + if (check_integer(value)) + style["fontsize"] = value; + + } else if (name == "font") { + if (value == "mono" || value == "normal") + style["fontstyle"] = value; + } +} + +void ParsedText::parseStyles(const AttrsList &attrs, StyleList &style) +{ + for (auto const &attr : attrs) + parseGenericStyleAttr(attr.first, attr.second, style); +} + +void ParsedText::globalTag(const AttrsList &attrs) +{ + for (const auto &attr : attrs) { + // Only page level style + if (attr.first == "margin") { + if (check_integer(attr.second)) + margin = stoi(attr.second.c_str()); + + } else if (attr.first == "valign") { + if (attr.second == "top") + valign = ParsedText::VALIGN_TOP; + else if (attr.second == "bottom") + valign = ParsedText::VALIGN_BOTTOM; + else if (attr.second == "middle") + valign = ParsedText::VALIGN_MIDDLE; + } else if (attr.first == "background") { + irr::video::SColor color; + if (attr.second == "none") { + background_type = BACKGROUND_NONE; + } else if (parseColorString(attr.second, color, false)) { + background_type = BACKGROUND_COLOR; + background_color = color; + } + + // Inheriting styles + + } else if (attr.first == "halign") { + if (attr.second == "left" || attr.second == "center" || + attr.second == "right" || + attr.second == "justify") + m_root_tag.style["halign"] = attr.second; + + // Generic default styles + + } else { + parseGenericStyleAttr(attr.first, attr.second, m_root_tag.style); + } + } +} + +u32 ParsedText::parseTag(const wchar_t *text, u32 cursor) +{ + // Tag name + bool end = false; + std::string name = ""; + wchar_t c = text[cursor]; + + if (c == L'/') { + end = true; + c = text[++cursor]; + if (c == L'\0') + return 0; + } + + while (c != ' ' && c != '>') { + name += c; + c = text[++cursor]; + if (c == L'\0') + return 0; + } + + // Tag attributes + AttrsList attrs; + while (c != L'>') { + std::string attr_name = ""; + core::stringw attr_val = L""; + + while (c == ' ') { + c = text[++cursor]; + if (c == L'\0' || c == L'=') + return 0; + } + + while (c != L' ' && c != L'=') { + attr_name += (char)c; + c = text[++cursor]; + if (c == L'\0' || c == L'>') + return 0; + } + + while (c == L' ') { + c = text[++cursor]; + if (c == L'\0' || c == L'>') + return 0; + } + + if (c != L'=') + return 0; + + c = text[++cursor]; + + if (c == L'\0') + return 0; + + while (c != L'>' && c != L' ') { + attr_val += c; + c = text[++cursor]; + if (c == L'\0') + return 0; + } + + attrs[attr_name] = stringw_to_utf8(attr_val); + } + + ++cursor; // Last ">" + + // Tag specific processing + StyleList style; + + if (name == "global") { + if (end) + return 0; + globalTag(attrs); + + } else if (name == "style") { + if (end) { + closeTag(name); + } else { + parseStyles(attrs, style); + openTag(name, attrs)->style = style; + } + endElement(); + } else if (name == "img" || name == "item") { + if (end) + return 0; + + // Name is a required attribute + if (!attrs.count("name")) + return 0; + + // Rotate attribute is only for <item> + if (attrs.count("rotate") && name != "item") + return 0; + + // Angle attribute is only for <item> + if (attrs.count("angle") && name != "item") + return 0; + + // Ok, element can be created + newTag(name, attrs); + + if (name == "img") + enterElement(ELEMENT_IMAGE); + else + enterElement(ELEMENT_ITEM); + + m_element->text = utf8_to_stringw(attrs["name"]); + + if (attrs.count("float")) { + if (attrs["float"] == "left") + m_element->floating = FLOAT_LEFT; + if (attrs["float"] == "right") + m_element->floating = FLOAT_RIGHT; + } + + if (attrs.count("width")) { + int width = stoi(attrs["width"]); + if (width > 0) + m_element->dim.Width = width; + } + + if (attrs.count("height")) { + int height = stoi(attrs["height"]); + if (height > 0) + m_element->dim.Height = height; + } + + if (attrs.count("angle")) { + std::string str = attrs["angle"]; + std::vector<std::string> parts = split(str, ','); + if (parts.size() == 3) { + m_element->angle = v3s16( + rangelim(stoi(parts[0]), -180, 180), + rangelim(stoi(parts[1]), -180, 180), + rangelim(stoi(parts[2]), -180, 180)); + m_element->rotation = v3s16(0, 0, 0); + } + } + + if (attrs.count("rotate")) { + if (attrs["rotate"] == "yes") { + m_element->rotation = v3s16(0, 100, 0); + } else { + std::string str = attrs["rotate"]; + std::vector<std::string> parts = split(str, ','); + if (parts.size() == 3) { + m_element->rotation = v3s16 ( + rangelim(stoi(parts[0]), -1000, 1000), + rangelim(stoi(parts[1]), -1000, 1000), + rangelim(stoi(parts[2]), -1000, 1000)); + } + } + } + + endElement(); + + } else if (name == "tag") { + // Required attributes + if (!attrs.count("name")) + return 0; + + StyleList tagstyle; + parseStyles(attrs, tagstyle); + + if (is_yes(attrs["paragraph"])) + m_paragraphtags[attrs["name"]] = tagstyle; + else + m_elementtags[attrs["name"]] = tagstyle; + + } else if (name == "action") { + if (end) { + closeTag(name); + } else { + if (!attrs.count("name")) + return 0; + openTag(name, attrs)->style = m_elementtags["action"]; + } + + } else if (m_elementtags.count(name)) { + if (end) { + closeTag(name); + } else { + openTag(name, attrs)->style = m_elementtags[name]; + } + endElement(); + + } else if (m_paragraphtags.count(name)) { + if (end) { + closeTag(name); + } else { + openTag(name, attrs)->style = m_paragraphtags[name]; + } + endParagraph(ER_TAG); + + } else + return 0; // Unknown tag + + // Update styles accordingly + m_style.clear(); + for (auto tag = m_active_tags.crbegin(); tag != m_active_tags.crend(); ++tag) + for (const auto &prop : (*tag)->style) + m_style[prop.first] = prop.second; + + return cursor; +} + +// ----------------------------------------------------------------------------- +// Text Drawer + +TextDrawer::TextDrawer(const wchar_t *text, Client *client, + gui::IGUIEnvironment *environment, ISimpleTextureSource *tsrc) : + m_text(text), + m_client(client), m_environment(environment) +{ + // Size all elements + for (auto &p : m_text.m_paragraphs) { + for (auto &e : p.elements) { + switch (e.type) { + case ParsedText::ELEMENT_SEPARATOR: + case ParsedText::ELEMENT_TEXT: + if (e.font) { + e.dim.Width = e.font->getDimension(e.text.c_str()).Width; + e.dim.Height = e.font->getDimension(L"Yy").Height; +#if USE_FREETYPE + if (e.font->getType() == irr::gui::EGFT_CUSTOM) { + e.baseline = e.dim.Height - 1 - + ((irr::gui::CGUITTFont *)e.font)->getAscender() / 64; + } +#endif + } else { + e.dim = {0, 0}; + } + break; + + case ParsedText::ELEMENT_IMAGE: + case ParsedText::ELEMENT_ITEM: + // Resize only non sized items + if (e.dim.Height != 0 && e.dim.Width != 0) + break; + + // Default image and item size + core::dimension2d<u32> dim(80, 80); + + if (e.type == ParsedText::ELEMENT_IMAGE) { + video::ITexture *texture = + m_client->getTextureSource()-> + getTexture(stringw_to_utf8(e.text)); + if (texture) + dim = texture->getOriginalSize(); + } + + if (e.dim.Height == 0) + if (e.dim.Width == 0) + e.dim = dim; + else + e.dim.Height = dim.Height * e.dim.Width / + dim.Width; + else + e.dim.Width = dim.Width * e.dim.Height / + dim.Height; + break; + } + } + } +} + +// Get element at given coordinates. Coordinates are inner coordinates (starting +// at 0,0). +ParsedText::Element *TextDrawer::getElementAt(core::position2d<s32> pos) +{ + pos.Y -= m_voffset; + for (auto &p : m_text.m_paragraphs) { + for (auto &el : p.elements) { + core::rect<s32> rect(el.pos, el.dim); + if (rect.isPointInside(pos)) + return ⪙ + } + } + return 0; +} + +/* + This function places all elements according to given width. Elements have + been previously sized by constructor and will be later drawed by draw. + It may be called each time width changes and resulting height can be + retrieved using getHeight. See GUIHyperText constructor, it uses it once to + test if text fits in window and eventually another time if width is reduced + m_floating because of scrollbar added. +*/ +void TextDrawer::place(const core::rect<s32> &dest_rect) +{ + m_floating.clear(); + s32 y = 0; + s32 ymargin = m_text.margin; + + // Iterator used : + // p - Current paragraph, walked only once + // el - Current element, walked only once + // e and f - local element and floating operators + + for (auto &p : m_text.m_paragraphs) { + // Find and place floating stuff in paragraph + for (auto e = p.elements.begin(); e != p.elements.end(); ++e) { + if (e->floating != ParsedText::FLOAT_NONE) { + if (y) + e->pos.Y = y + std::max(ymargin, e->margin); + else + e->pos.Y = ymargin; + + if (e->floating == ParsedText::FLOAT_LEFT) + e->pos.X = m_text.margin; + if (e->floating == ParsedText::FLOAT_RIGHT) + e->pos.X = dest_rect.getWidth() - e->dim.Width - + m_text.margin; + + RectWithMargin floating; + floating.rect = core::rect<s32>(e->pos, e->dim); + floating.margin = e->margin; + + m_floating.push_back(floating); + } + } + + if (y) + y = y + std::max(ymargin, p.margin); + + ymargin = p.margin; + + // Place non floating stuff + std::vector<ParsedText::Element>::iterator el = p.elements.begin(); + + while (el != p.elements.end()) { + // Determine line width and y pos + s32 left, right; + s32 nexty = y; + do { + y = nexty; + nexty = 0; + + // Inner left & right + left = m_text.margin; + right = dest_rect.getWidth() - m_text.margin; + + for (const auto &f : m_floating) { + // Does floating rect intersect paragraph y line? + if (f.rect.UpperLeftCorner.Y - f.margin <= y && + f.rect.LowerRightCorner.Y + f.margin >= y) { + + // Next Y to try if no room left + if (!nexty || f.rect.LowerRightCorner.Y + + std::max(f.margin, p.margin) < nexty) { + nexty = f.rect.LowerRightCorner.Y + + std::max(f.margin, p.margin) + 1; + } + + if (f.rect.UpperLeftCorner.X - f.margin <= left && + f.rect.LowerRightCorner.X + f.margin < right) { + // float on left + if (f.rect.LowerRightCorner.X + + std::max(f.margin, p.margin) > left) { + left = f.rect.LowerRightCorner.X + + std::max(f.margin, p.margin); + } + } else if (f.rect.LowerRightCorner.X + f.margin >= right && + f.rect.UpperLeftCorner.X - f.margin > left) { + // float on right + if (f.rect.UpperLeftCorner.X - + std::max(f.margin, p.margin) < right) + right = f.rect.UpperLeftCorner.X - + std::max(f.margin, p.margin); + + } else if (f.rect.UpperLeftCorner.X - f.margin <= left && + f.rect.LowerRightCorner.X + f.margin >= right) { + // float taking all space + left = right; + } + else + { // float in the middle -- should not occure yet, see that later + } + } + } + } while (nexty && right <= left); + + u32 linewidth = right - left; + float x = left; + + u32 charsheight = 0; + u32 charswidth = 0; + u32 wordcount = 0; + + // Skip begining of line separators but include them in height + // computation. + while (el != p.elements.end() && + el->type == ParsedText::ELEMENT_SEPARATOR) { + if (el->floating == ParsedText::FLOAT_NONE) { + el->drawwidth = 0; + if (charsheight < el->dim.Height) + charsheight = el->dim.Height; + } + el++; + } + + std::vector<ParsedText::Element>::iterator linestart = el; + std::vector<ParsedText::Element>::iterator lineend = p.elements.end(); + + // First pass, find elements fitting into line + // (or at least one element) + while (el != p.elements.end() && (charswidth == 0 || + charswidth + el->dim.Width <= linewidth)) { + if (el->floating == ParsedText::FLOAT_NONE) { + if (el->type != ParsedText::ELEMENT_SEPARATOR) { + lineend = el; + wordcount++; + } + charswidth += el->dim.Width; + if (charsheight < el->dim.Height) + charsheight = el->dim.Height; + } + el++; + } + + // Empty line, nothing to place only go down line height + if (lineend == p.elements.end()) { + y += charsheight; + continue; + } + + // Point to the first position outside line (may be end()) + lineend++; + + // Second pass, compute printable line width and adjustments + charswidth = 0; + s32 top = 0; + s32 bottom = 0; + for (auto e = linestart; e != lineend; ++e) { + if (e->floating == ParsedText::FLOAT_NONE) { + charswidth += e->dim.Width; + if (top < (s32)e->dim.Height - e->baseline) + top = e->dim.Height - e->baseline; + if (bottom < e->baseline) + bottom = e->baseline; + } + } + + float extraspace = 0.f; + + switch (p.halign) { + case ParsedText::HALIGN_CENTER: + x += (linewidth - charswidth) / 2.f; + break; + case ParsedText::HALIGN_JUSTIFY: + if (wordcount > 1 && // Justification only if at least two words + !(lineend == p.elements.end())) // Don't justify last line + extraspace = ((float)(linewidth - charswidth)) / (wordcount - 1); + break; + case ParsedText::HALIGN_RIGHT: + x += linewidth - charswidth; + break; + case ParsedText::HALIGN_LEFT: + break; + } + + // Third pass, actually place everything + for (auto e = linestart; e != lineend; ++e) { + if (e->floating != ParsedText::FLOAT_NONE) + continue; + + e->pos.X = x; + e->pos.Y = y; + + switch (e->type) { + case ParsedText::ELEMENT_TEXT: + case ParsedText::ELEMENT_SEPARATOR: + e->pos.X = x; + + // Align char baselines + e->pos.Y = y + top + e->baseline - e->dim.Height; + + x += e->dim.Width; + if (e->type == ParsedText::ELEMENT_SEPARATOR) + x += extraspace; + break; + + case ParsedText::ELEMENT_IMAGE: + case ParsedText::ELEMENT_ITEM: + x += e->dim.Width; + break; + } + + // Draw width for separator can be different than element + // width. This will be important for char effects like + // underline. + e->drawwidth = x - e->pos.X; + } + y += charsheight; + } // Elements (actually lines) + } // Paragraph + + // Check if float goes under paragraph + for (const auto &f : m_floating) { + if (f.rect.LowerRightCorner.Y >= y) + y = f.rect.LowerRightCorner.Y; + } + + m_height = y + m_text.margin; + // Compute vertical offset according to vertical alignment + if (m_height < dest_rect.getHeight()) + switch (m_text.valign) { + case ParsedText::VALIGN_BOTTOM: + m_voffset = dest_rect.getHeight() - m_height; + break; + case ParsedText::VALIGN_MIDDLE: + m_voffset = (dest_rect.getHeight() - m_height) / 2; + break; + case ParsedText::VALIGN_TOP: + default: + m_voffset = 0; + } + else + m_voffset = 0; +} + +// Draw text in a rectangle with a given offset. Items are actually placed in +// relative (to upper left corner) coordinates. +void TextDrawer::draw(const core::rect<s32> &dest_rect, + const core::position2d<s32> &dest_offset) +{ + irr::video::IVideoDriver *driver = m_environment->getVideoDriver(); + core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset; + offset.Y += m_voffset; + + if (m_text.background_type == ParsedText::BACKGROUND_COLOR) + driver->draw2DRectangle(m_text.background_color, dest_rect); + + for (auto &p : m_text.m_paragraphs) { + for (auto &el : p.elements) { + core::rect<s32> rect(el.pos + offset, el.dim); + if (!rect.isRectCollided(dest_rect)) + continue; + + switch (el.type) { + case ParsedText::ELEMENT_SEPARATOR: + case ParsedText::ELEMENT_TEXT: { + irr::video::SColor color = el.color; + + for (auto tag : el.tags) + if (&(*tag) == m_hovertag) + color = el.hovercolor; + + if (!el.font) + break; + + if (el.type == ParsedText::ELEMENT_TEXT) + el.font->draw(el.text, rect, color, false, true, + &dest_rect); + + if (el.underline && el.drawwidth) { + s32 linepos = el.pos.Y + offset.Y + + el.dim.Height - (el.baseline >> 1); + + core::rect<s32> linerect(el.pos.X + offset.X, + linepos - (el.baseline >> 3) - 1, + el.pos.X + offset.X + el.drawwidth, + linepos + (el.baseline >> 3)); + + driver->draw2DRectangle(color, linerect, &dest_rect); + } + } break; + + case ParsedText::ELEMENT_IMAGE: { + video::ITexture *texture = + m_client->getTextureSource()->getTexture( + stringw_to_utf8(el.text)); + if (texture != 0) + m_environment->getVideoDriver()->draw2DImage( + texture, rect, + irr::core::rect<s32>( + core::position2d<s32>(0, 0), + texture->getOriginalSize()), + &dest_rect, 0, true); + } break; + + case ParsedText::ELEMENT_ITEM: { + IItemDefManager *idef = m_client->idef(); + ItemStack item; + item.deSerialize(stringw_to_utf8(el.text), idef); + + drawItemStack( + m_environment->getVideoDriver(), + g_fontengine->getFont(), item, rect, &dest_rect, + m_client, IT_ROT_OTHER, el.angle, el.rotation + ); + } break; + } + } + } +} + +// ----------------------------------------------------------------------------- +// GUIHyperText - The formated text area formspec item + +//! constructor +GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, + IGUIElement *parent, s32 id, const core::rect<s32> &rectangle, + Client *client, ISimpleTextureSource *tsrc) : + IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle), + m_client(client), m_vscrollbar(nullptr), + m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0) +{ + +#ifdef _DEBUG + setDebugName("GUIHyperText"); +#endif + + IGUISkin *skin = 0; + if (Environment) + skin = Environment->getSkin(); + + m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; + + core::rect<s32> rect = irr::core::rect<s32>( + RelativeRect.getWidth() - m_scrollbar_width, 0, + RelativeRect.getWidth(), RelativeRect.getHeight()); + + m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true); + m_vscrollbar->setVisible(false); +} + +//! destructor +GUIHyperText::~GUIHyperText() +{ + m_vscrollbar->remove(); + m_vscrollbar->drop(); +} + +ParsedText::Element *GUIHyperText::getElementAt(s32 X, s32 Y) +{ + core::position2d<s32> pos{X, Y}; + pos -= m_display_text_rect.UpperLeftCorner; + pos -= m_text_scrollpos; + return m_drawer.getElementAt(pos); +} + +void GUIHyperText::checkHover(s32 X, s32 Y) +{ + m_drawer.m_hovertag = nullptr; + + if (AbsoluteRect.isPointInside(core::position2d<s32>(X, Y))) { + ParsedText::Element *element = getElementAt(X, Y); + + if (element) { + for (auto &tag : element->tags) { + if (tag->name == "action") { + m_drawer.m_hovertag = tag; + break; + } + } + } + } + +#ifndef HAVE_TOUCHSCREENGUI + if (m_drawer.m_hovertag) + RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( + gui::ECI_HAND); + else + RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( + gui::ECI_NORMAL); +#endif +} + +bool GUIHyperText::OnEvent(const SEvent &event) +{ + // Scroll bar + if (event.EventType == EET_GUI_EVENT && + event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED && + event.GUIEvent.Caller == m_vscrollbar) { + m_text_scrollpos.Y = -m_vscrollbar->getPos(); + } + + // Reset hover if element left + if (event.EventType == EET_GUI_EVENT && + event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { + m_drawer.m_hovertag = nullptr; +#ifndef HAVE_TOUCHSCREENGUI + gui::ICursorControl *cursor_control = + RenderingEngine::get_raw_device()->getCursorControl(); + if (cursor_control->isVisible()) + cursor_control->setActiveIcon(gui::ECI_NORMAL); +#endif + } + + if (event.EventType == EET_MOUSE_INPUT_EVENT) { + if (event.MouseInput.Event == EMIE_MOUSE_MOVED) + checkHover(event.MouseInput.X, event.MouseInput.Y); + + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + m_vscrollbar->setPos(m_vscrollbar->getPos() - + event.MouseInput.Wheel * m_vscrollbar->getSmallStep()); + m_text_scrollpos.Y = -m_vscrollbar->getPos(); + m_drawer.draw(m_display_text_rect, m_text_scrollpos); + checkHover(event.MouseInput.X, event.MouseInput.Y); + + } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + ParsedText::Element *element = getElementAt( + event.MouseInput.X, event.MouseInput.Y); + + if (element) { + for (auto &tag : element->tags) { + if (tag->name == "action") { + Text = core::stringw(L"action:") + + utf8_to_stringw(tag->attrs["name"]); + if (Parent) { + SEvent newEvent; + newEvent.EventType = EET_GUI_EVENT; + newEvent.GUIEvent.Caller = this; + newEvent.GUIEvent.Element = 0; + newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED; + Parent->OnEvent(newEvent); + } + break; + } + } + } + } + } + + return IGUIElement::OnEvent(event); +} + +//! draws the element and its children +void GUIHyperText::draw() +{ + if (!IsVisible) + return; + + // Text + m_display_text_rect = AbsoluteRect; + m_drawer.place(m_display_text_rect); + + // Show scrollbar if text overflow + if (m_drawer.getHeight() > m_display_text_rect.getHeight()) { + m_vscrollbar->setSmallStep(m_display_text_rect.getHeight() * 0.1f); + m_vscrollbar->setLargeStep(m_display_text_rect.getHeight() * 0.5f); + m_vscrollbar->setMax(m_drawer.getHeight() - m_display_text_rect.getHeight()); + + m_vscrollbar->setVisible(true); + + m_vscrollbar->setPageSize(s32(m_drawer.getHeight())); + + core::rect<s32> smaller_rect = m_display_text_rect; + + smaller_rect.LowerRightCorner.X -= m_scrollbar_width; + m_drawer.place(smaller_rect); + } else { + m_vscrollbar->setMax(0); + m_vscrollbar->setPos(0); + m_vscrollbar->setVisible(false); + } + m_drawer.draw(m_display_text_rect, m_text_scrollpos); + + // draw children + IGUIElement::draw(); +} diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h new file mode 100644 index 000000000..c55f8a705 --- /dev/null +++ b/src/gui/guiHyperText.h @@ -0,0 +1,229 @@ +/* +Minetest +Copyright (C) 2019 EvicenceBKidscode / Pierre-Yves Rollo <dev@pyrollo.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 "config.h" // for USE_FREETYPE + +using namespace irr; + +class ISimpleTextureSource; +class Client; + +#if USE_FREETYPE +#include "irrlicht_changes/CGUITTFont.h" +#endif + +class ParsedText +{ +public: + ParsedText(const wchar_t *text); + ~ParsedText(); + + enum ElementType + { + ELEMENT_TEXT, + ELEMENT_SEPARATOR, + ELEMENT_IMAGE, + ELEMENT_ITEM + }; + + enum BackgroundType + { + BACKGROUND_NONE, + BACKGROUND_COLOR + }; + + enum FloatType + { + FLOAT_NONE, + FLOAT_RIGHT, + FLOAT_LEFT + }; + + enum HalignType + { + HALIGN_CENTER, + HALIGN_LEFT, + HALIGN_RIGHT, + HALIGN_JUSTIFY + }; + + enum ValignType + { + VALIGN_MIDDLE, + VALIGN_TOP, + VALIGN_BOTTOM + }; + + typedef std::unordered_map<std::string, std::string> StyleList; + typedef std::unordered_map<std::string, std::string> AttrsList; + + struct Tag + { + std::string name; + AttrsList attrs; + StyleList style; + }; + + struct Element + { + std::list<Tag *> tags; + ElementType type; + core::stringw text = ""; + + core::dimension2d<u32> dim; + core::position2d<s32> pos; + s32 drawwidth; + + FloatType floating = FLOAT_NONE; + + ValignType valign; + + gui::IGUIFont *font; + + irr::video::SColor color; + irr::video::SColor hovercolor; + bool underline; + + s32 baseline = 0; + + // img & item specific attributes + std::string name; + v3s16 angle{0, 0, 0}; + v3s16 rotation{0, 0, 0}; + + s32 margin = 10; + + void setStyle(StyleList &style); + }; + + struct Paragraph + { + std::vector<Element> elements; + HalignType halign; + s32 margin = 10; + + void setStyle(StyleList &style); + }; + + std::vector<Paragraph> m_paragraphs; + + // Element style + s32 margin = 3; + ValignType valign = VALIGN_TOP; + BackgroundType background_type = BACKGROUND_NONE; + irr::video::SColor background_color; + + Tag m_root_tag; + +protected: + typedef enum { ER_NONE, ER_TAG, ER_NEWLINE } EndReason; + + // Parser functions + void enterElement(ElementType type); + void endElement(); + void enterParagraph(); + void endParagraph(EndReason reason); + void pushChar(wchar_t c); + ParsedText::Tag *newTag(const std::string &name, const AttrsList &attrs); + ParsedText::Tag *openTag(const std::string &name, const AttrsList &attrs); + bool closeTag(const std::string &name); + void parseGenericStyleAttr(const std::string &name, const std::string &value, + StyleList &style); + void parseStyles(const AttrsList &attrs, StyleList &style); + void globalTag(const ParsedText::AttrsList &attrs); + u32 parseTag(const wchar_t *text, u32 cursor); + void parse(const wchar_t *text); + + std::unordered_map<std::string, StyleList> m_elementtags; + std::unordered_map<std::string, StyleList> m_paragraphtags; + + std::vector<Tag *> m_not_root_tags; + std::list<Tag *> m_active_tags; + + // Current values + StyleList m_style; + Element *m_element; + Paragraph *m_paragraph; + bool m_empty_paragraph; + EndReason m_end_paragraph_reason; +}; + +class TextDrawer +{ +public: + TextDrawer(const wchar_t *text, Client *client, gui::IGUIEnvironment *environment, + ISimpleTextureSource *tsrc); + + void place(const core::rect<s32> &dest_rect); + inline s32 getHeight() { return m_height; }; + void draw(const core::rect<s32> &dest_rect, + const core::position2d<s32> &dest_offset); + ParsedText::Element *getElementAt(core::position2d<s32> pos); + ParsedText::Tag *m_hovertag; + +protected: + struct RectWithMargin + { + core::rect<s32> rect; + s32 margin; + }; + + ParsedText m_text; + Client *m_client; + gui::IGUIEnvironment *m_environment; + s32 m_height; + s32 m_voffset; + std::vector<RectWithMargin> m_floating; +}; + +class GUIHyperText : public gui::IGUIElement +{ +public: + //! constructor + GUIHyperText(const wchar_t *text, gui::IGUIEnvironment *environment, + gui::IGUIElement *parent, s32 id, + const core::rect<s32> &rectangle, Client *client, + ISimpleTextureSource *tsrc); + + //! destructor + virtual ~GUIHyperText(); + + //! draws the element and its children + virtual void draw(); + + core::dimension2du getTextDimension(); + + bool OnEvent(const SEvent &event); + +protected: + // GUI members + Client *m_client; + GUIScrollBar *m_vscrollbar; + TextDrawer m_drawer; + + // Positioning + u32 m_scrollbar_width; + core::rect<s32> m_display_text_rect; + core::position2d<s32> m_text_scrollpos; + + ParsedText::Element *getElementAt(s32 X, s32 Y); + void checkHover(s32 X, s32 Y); +}; diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp new file mode 100644 index 000000000..536471229 --- /dev/null +++ b/src/gui/guiInventoryList.cpp @@ -0,0 +1,217 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiInventoryList.h" +#include "guiFormSpecMenu.h" +#include "client/hud.h" +#include "client/client.h" + +GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, + gui::IGUIElement *parent, + s32 id, + const core::rect<s32> &rectangle, + InventoryManager *invmgr, + const InventoryLocation &inventoryloc, + const std::string &listname, + const v2s32 &geom, + const s32 start_item_i, + const v2s32 &slot_size, + const v2f32 &slot_spacing, + GUIFormSpecMenu *fs_menu, + const Options &options, + gui::IGUIFont *font) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_invmgr(invmgr), + m_inventoryloc(inventoryloc), + m_listname(listname), + m_geom(geom), + m_start_item_i(start_item_i), + m_slot_size(slot_size), + m_slot_spacing(slot_spacing), + m_fs_menu(fs_menu), + m_options(options), + m_font(font), + m_hovered_i(-1) +{ +} + +void GUIInventoryList::draw() +{ + if (!IsVisible) + return; + + Inventory *inv = m_invmgr->getInventory(m_inventoryloc); + if (!inv) { + warningstream << "GUIInventoryList::draw(): " + << "The inventory location " + << "\"" << m_inventoryloc.dump() << "\" doesn't exist anymore" + << std::endl; + return; + } + InventoryList *ilist = inv->getList(m_listname); + if (!ilist) { + warningstream << "GUIInventoryList::draw(): " + << "The inventory list \"" << m_listname << "\" @ \"" + << m_inventoryloc.dump() << "\" doesn't exist anymore" + << std::endl; + return; + } + + video::IVideoDriver *driver = Environment->getVideoDriver(); + Client *client = m_fs_menu->getClient(); + const ItemSpec *selected_item = m_fs_menu->getSelectedItem(); + + core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y); + v2s32 base_pos = AbsoluteRect.UpperLeftCorner; + + for (s32 i = 0; i < m_geom.X * m_geom.Y; i++) { + s32 item_i = i + m_start_item_i; + if (item_i >= (s32)ilist->getSize()) + break; + + v2s32 p((i % m_geom.X) * m_slot_spacing.X, + (i / m_geom.X) * m_slot_spacing.Y); + core::rect<s32> rect = imgrect + base_pos + p; + ItemStack item = ilist->getItem(item_i); + + bool selected = selected_item + && m_invmgr->getInventory(selected_item->inventoryloc) == inv + && selected_item->listname == m_listname + && selected_item->i == item_i; + core::rect<s32> clipped_rect(rect); + clipped_rect.clipAgainst(AbsoluteClippingRect); + bool hovering = m_hovered_i == item_i; + ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED : + (hovering ? IT_ROT_HOVERED : IT_ROT_NONE); + + // layer 0 + if (hovering) { + driver->draw2DRectangle(m_options.slotbg_h, rect, &AbsoluteClippingRect); + } else { + driver->draw2DRectangle(m_options.slotbg_n, rect, &AbsoluteClippingRect); + } + + // Draw inv slot borders + if (m_options.slotborder) { + s32 x1 = rect.UpperLeftCorner.X; + s32 y1 = rect.UpperLeftCorner.Y; + s32 x2 = rect.LowerRightCorner.X; + s32 y2 = rect.LowerRightCorner.Y; + s32 border = 1; + core::rect<s32> clipping_rect = Parent ? Parent->getAbsoluteClippingRect() + : core::rect<s32>(); + core::rect<s32> *clipping_rect_ptr = Parent ? &clipping_rect : nullptr; + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect<s32>(v2s32(x1 - border, y1 - border), + v2s32(x2 + border, y1)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect<s32>(v2s32(x1 - border, y2), + v2s32(x2 + border, y2 + border)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect<s32>(v2s32(x1 - border, y1), + v2s32(x1, y2)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect<s32>(v2s32(x2, y1), + v2s32(x2 + border, y2)), clipping_rect_ptr); + } + + // layer 1 + if (selected) + item.takeItem(m_fs_menu->getSelectedAmount()); + + if (!item.empty()) { + // Draw item stack + drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect, + client, rotation_kind); + // Add hovering tooltip + if (hovering && !selected_item) { + std::string tooltip = item.getDescription(client->idef()); + if (m_fs_menu->doTooltipAppendItemname()) + tooltip += "\n[" + item.name + "]"; + m_fs_menu->addHoveredItemTooltip(tooltip); + } + } + } + + IGUIElement::draw(); +} + +bool GUIInventoryList::OnEvent(const SEvent &event) +{ + if (event.EventType != EET_MOUSE_INPUT_EVENT) { + if (event.EventType == EET_GUI_EVENT && + event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { + // element is no longer hovered + m_hovered_i = -1; + } + return IGUIElement::OnEvent(event); + } + + m_hovered_i = getItemIndexAtPos(v2s32(event.MouseInput.X, event.MouseInput.Y)); + + if (m_hovered_i != -1) + return IGUIElement::OnEvent(event); + + // no item slot at pos of mouse event => allow clicking through + // find the element that would be hovered if this inventorylist was invisible + bool was_visible = IsVisible; + IsVisible = false; + IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)); + + // if the player clicks outside of the formspec window, hovered is not + // m_fs_menu, but some other weird element (with ID -1). we do however need + // hovered to be m_fs_menu as item dropping when clicking outside of the + // formspec window is handled in its OnEvent callback + if (!hovered || hovered->getID() == -1) + hovered = m_fs_menu; + + bool ret = hovered->OnEvent(event); + + IsVisible = was_visible; + + return ret; +} + +s32 GUIInventoryList::getItemIndexAtPos(v2s32 p) const +{ + if (!IsVisible || AbsoluteClippingRect.getArea() <= 0 || + !AbsoluteClippingRect.isPointInside(p)) + return -1; + + core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y); + v2s32 base_pos = AbsoluteRect.UpperLeftCorner; + + // instead of looping through each slot, we look where p would be in the grid + s32 i = (p.X - base_pos.X) / (s32)m_slot_spacing.X + + m_geom.X * ((p.Y - base_pos.Y) / (s32)m_slot_spacing.Y); + + v2s32 p0((i % m_geom.X) * m_slot_spacing.X, + (i / m_geom.X) * m_slot_spacing.Y); + + core::rect<s32> rect = imgrect + base_pos + p0; + + rect.clipAgainst(AbsoluteClippingRect); + + if (rect.getArea() > 0 && rect.isPointInside(p)) + return i + m_start_item_i; + + return -1; +} diff --git a/src/gui/guiInventoryList.h b/src/gui/guiInventoryList.h new file mode 100644 index 000000000..fd2c3601b --- /dev/null +++ b/src/gui/guiInventoryList.h @@ -0,0 +1,130 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "inventorymanager.h" +#include "irrlichttypes_extrabloated.h" +#include "util/string.h" + +class GUIFormSpecMenu; + +class GUIInventoryList : public gui::IGUIElement +{ +public: + struct ItemSpec + { + ItemSpec() = default; + + ItemSpec(const InventoryLocation &a_inventoryloc, + const std::string &a_listname, + s32 a_i) : + inventoryloc(a_inventoryloc), + listname(a_listname), + i(a_i) + { + } + + bool isValid() const { return i != -1; } + + InventoryLocation inventoryloc; + std::string listname; + s32 i = -1; + }; + + // options for inventorylists that are setable with the lua api + struct Options { + // whether a one-pixel border for the slots should be drawn and its color + bool slotborder = false; + video::SColor slotbordercolor = video::SColor(200, 0, 0, 0); + // colors for normal and highlighted slot background + video::SColor slotbg_n = video::SColor(255, 128, 128, 128); + video::SColor slotbg_h = video::SColor(255, 192, 192, 192); + }; + + GUIInventoryList(gui::IGUIEnvironment *env, + gui::IGUIElement *parent, + s32 id, + const core::rect<s32> &rectangle, + InventoryManager *invmgr, + const InventoryLocation &inventoryloc, + const std::string &listname, + const v2s32 &geom, + const s32 start_item_i, + const v2s32 &slot_size, + const v2f32 &slot_spacing, + GUIFormSpecMenu *fs_menu, + const Options &options, + gui::IGUIFont *font); + + virtual void draw() override; + + virtual bool OnEvent(const SEvent &event) override; + + const InventoryLocation &getInventoryloc() const + { + return m_inventoryloc; + } + + const std::string &getListname() const + { + return m_listname; + } + + void setSlotBGColors(const video::SColor &slotbg_n, const video::SColor &slotbg_h) + { + m_options.slotbg_n = slotbg_n; + m_options.slotbg_h = slotbg_h; + } + + void setSlotBorders(bool slotborder, const video::SColor &slotbordercolor) + { + m_options.slotborder = slotborder; + m_options.slotbordercolor = slotbordercolor; + } + + // returns -1 if not item is at pos p + s32 getItemIndexAtPos(v2s32 p) const; + +private: + InventoryManager *m_invmgr; + const InventoryLocation m_inventoryloc; + const std::string m_listname; + + // specifies the width and height of the inventorylist in itemslots + const v2s32 m_geom; + // the first item's index in inventory + const s32 m_start_item_i; + + // specifies how large the slot rects are + const v2s32 m_slot_size; + // specifies how large the space between slots is (space between is spacing-size) + const v2f32 m_slot_spacing; + + // the GUIFormSpecMenu can have an item selected and co. + GUIFormSpecMenu *m_fs_menu; + + Options m_options; + + // the font + gui::IGUIFont *m_font; + + // the index of the hovered item; -1 if no item is hovered + s32 m_hovered_i; +}; diff --git a/src/gui/guiItemImage.cpp b/src/gui/guiItemImage.cpp new file mode 100644 index 000000000..f93d5476c --- /dev/null +++ b/src/gui/guiItemImage.cpp @@ -0,0 +1,52 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiItemImage.h" +#include "client/client.h" + +GUIItemImage::GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, const core::rect<s32> &rectangle, const std::string &item_name, + gui::IGUIFont *font, Client *client) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_item_name(item_name), m_font(font), m_client(client), m_label(core::stringw()) +{ +} + +void GUIItemImage::draw() +{ + if (!IsVisible) + return; + + if (!m_client) { + IGUIElement::draw(); + return; + } + + IItemDefManager *idef = m_client->idef(); + ItemStack item; + item.deSerialize(m_item_name, idef); + // Viewport rectangle on screen + core::rect<s32> rect = core::rect<s32>(AbsoluteRect); + drawItemStack(Environment->getVideoDriver(), m_font, item, rect, + &AbsoluteClippingRect, m_client, IT_ROT_NONE); + video::SColor color(255, 255, 255, 255); + m_font->draw(m_label, rect, color, true, true, &AbsoluteClippingRect); + + IGUIElement::draw(); +} diff --git a/src/gui/guiItemImage.h b/src/gui/guiItemImage.h new file mode 100644 index 000000000..6fede6564 --- /dev/null +++ b/src/gui/guiItemImage.h @@ -0,0 +1,46 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "util/string.h" + +class Client; + +class GUIItemImage : public gui::IGUIElement +{ +public: + GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, + const core::rect<s32> &rectangle, const std::string &item_name, + gui::IGUIFont *font, Client *client); + + virtual void draw() override; + + virtual void setText(const wchar_t *text) override + { + m_label = text; + } + +private: + std::string m_item_name; + gui::IGUIFont *m_font; + Client *m_client; + core::stringw m_label; +}; diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index ca331a7d4..3f270fc7a 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -21,6 +21,7 @@ #include "guiKeyChangeMenu.h" #include "debug.h" +#include "guiButton.h" #include "serialization.h" #include <string> #include <IGUICheckBox.h> @@ -85,8 +86,6 @@ GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, GUIModalMenu(env, parent, id, menumgr) { init_keys(); - for (key_setting *ks : key_settings) - key_used.push_back(ks->key); } GUIKeyChangeMenu::~GUIKeyChangeMenu() @@ -111,6 +110,7 @@ void GUIKeyChangeMenu::removeChildren() for (gui::IGUIElement *i : children_copy) { i->remove(); } + key_used_text = nullptr; } void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) @@ -157,7 +157,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s); const wchar_t *text = wgettext(k->key.name()); - k->button = Environment->addButton(rect, this, k->id, text); + k->button = GUIButton::addButton(Environment, rect, this, k->id, text); delete[] text; } if ((i + 1) % KMaxButtonPerColumns == 0) { @@ -217,16 +217,14 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s); const wchar_t *text = wgettext("Save"); - Environment->addButton(rect, this, GUI_ID_BACK_BUTTON, - text); + GUIButton::addButton(Environment, rect, this, GUI_ID_BACK_BUTTON, text); delete[] text; } { core::rect<s32> rect(0, 0, 100 * s, 30 * s); rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s); const wchar_t *text = wgettext("Cancel"); - Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON, - text); + GUIButton::addButton(Environment, rect, this, GUI_ID_ABORT_BUTTON, text); delete[] text; } } @@ -247,7 +245,13 @@ void GUIKeyChangeMenu::drawMenu() bool GUIKeyChangeMenu::acceptInput() { for (key_setting *k : key_settings) { - g_settings->set(k->setting_name, k->key.sym()); + std::string default_key; + g_settings->getDefaultNoEx(k->setting_name, default_key); + + if (k->key.sym() != default_key) + g_settings->set(k->setting_name, k->key.sym()); + else + g_settings->remove(k->setting_name); } { @@ -275,29 +279,28 @@ bool GUIKeyChangeMenu::acceptInput() bool GUIKeyChangeMenu::resetMenu() { - if (activeKey >= 0) - { - for (key_setting *k : key_settings) { - if (k->id == activeKey) { - const wchar_t *text = wgettext(k->key.name()); - k->button->setText(text); - delete[] text; - break; - } - } - activeKey = -1; + if (active_key) { + const wchar_t *text = wgettext(active_key->key.name()); + active_key->button->setText(text); + delete[] text; + active_key = nullptr; return false; } return true; } bool GUIKeyChangeMenu::OnEvent(const SEvent& event) { - if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0 + if (event.EventType == EET_KEY_INPUT_EVENT && active_key && event.KeyInput.PressedDown) { bool prefer_character = shift_down; KeyPress kp(event.KeyInput, prefer_character); + if (event.KeyInput.Key == irr::KEY_DELETE) + kp = KeyPress(""); // To erase key settings + else if (event.KeyInput.Key == irr::KEY_ESCAPE) + kp = active_key->key; // Cancel + bool shift_went_down = false; if(!shift_down && (event.KeyInput.Key == irr::KEY_SHIFT || @@ -305,51 +308,46 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) event.KeyInput.Key == irr::KEY_RSHIFT)) shift_went_down = true; - // Remove Key already in use message - if(this->key_used_text) - { - this->key_used_text->remove(); - this->key_used_text = NULL; - } // Display Key already in use message - if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end()) - { - core::rect < s32 > rect(0, 0, 600, 40); + bool key_in_use = false; + if (strcmp(kp.sym(), "") != 0) { + for (key_setting *ks : key_settings) { + if (ks != active_key && ks->key == kp) { + key_in_use = true; + break; + } + } + } + + if (key_in_use && !this->key_used_text) { + core::rect<s32> rect(0, 0, 600, 40); rect += v2s32(0, 0) + v2s32(25, 30); const wchar_t *text = wgettext("Key already in use"); this->key_used_text = Environment->addStaticText(text, rect, false, true, this, -1); delete[] text; - //infostream << "Key already in use" << std::endl; + } else if (!key_in_use && this->key_used_text) { + this->key_used_text->remove(); + this->key_used_text = nullptr; } // But go on { - key_setting *k = NULL; - for (key_setting *ks : key_settings) { - if (ks->id == activeKey) { - k = ks; - break; - } - } - FATAL_ERROR_IF(k == NULL, "Key setting not found"); - k->key = kp; - const wchar_t *text = wgettext(k->key.name()); - k->button->setText(text); + active_key->key = kp; + const wchar_t *text = wgettext(kp.name()); + active_key->button->setText(text); delete[] text; - this->key_used.push_back(kp); - // Allow characters made with shift - if(shift_went_down){ + if (shift_went_down){ shift_down = true; return false; } - activeKey = -1; + active_key = nullptr; return true; } - } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0 + } else if (event.EventType == EET_KEY_INPUT_EVENT && !active_key && event.KeyInput.PressedDown && event.KeyInput.Key == irr::KEY_ESCAPE) { quitMenu(); @@ -378,24 +376,19 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) quitMenu(); return true; default: - key_setting *k = NULL; - + resetMenu(); for (key_setting *ks : key_settings) { if (ks->id == event.GUIEvent.Caller->getID()) { - k = ks; + active_key = ks; break; } } - FATAL_ERROR_IF(k == NULL, "Key setting not found"); + FATAL_ERROR_IF(!active_key, "Key setting not found"); - resetMenu(); shift_down = false; - activeKey = event.GUIEvent.Caller->getID(); const wchar_t *text = wgettext("press key"); - k->button->setText(text); + active_key->button->setText(text); delete[] text; - this->key_used.erase(std::remove(this->key_used.begin(), - this->key_used.end(), k->key), this->key_used.end()); break; } Environment->setFocus(this); diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h index 0aaa05e18..528827fd9 100644 --- a/src/gui/guiKeyChangeMenu.h +++ b/src/gui/guiKeyChangeMenu.h @@ -70,9 +70,8 @@ private: void add_key(int id, const wchar_t *button_name, const std::string &setting_name); bool shift_down = false; - s32 activeKey = -1; - std::vector<KeyPress> key_used; + key_setting *active_key = nullptr; gui::IGUIStaticText *key_used_text = nullptr; std::vector<key_setting *> key_settings; }; diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 469c38dbe..af91ce84c 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -18,6 +18,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiPasswordChange.h" #include "client/client.h" +#include "guiButton.h" #include <IGUICheckBox.h> #include <IGUIEditBox.h> #include <IGUIButton.h> @@ -145,14 +146,14 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 100 * s, 30 * s); rect = rect + v2s32(size.X / 4 + 56 * s, ypos); text = wgettext("Change"); - Environment->addButton(rect, this, ID_change, text); + GUIButton::addButton(Environment, rect, this, ID_change, text); delete[] text; } { core::rect<s32> rect(0, 0, 100 * s, 30 * s); rect = rect + v2s32(size.X / 4 + 185 * s, ypos); text = wgettext("Cancel"); - Environment->addButton(rect, this, ID_cancel, text); + GUIButton::addButton(Environment, rect, this, ID_cancel, text); delete[] text; } diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index f7218e733..b04ccb9d5 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -247,7 +247,7 @@ s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const w = RelativeRect.getHeight() - border_size * 2 - thumb_size; p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset; } - return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range()) + min_pos : 0; + return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range() + 0.5f) + min_pos : 0; } void GUIScrollBar::setPos(const s32 &pos) @@ -272,7 +272,8 @@ void GUIScrollBar::setPos(const s32 &pos) f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range() : 1.0f; - draw_center = s32((f32(scroll_pos) * f) + (f32(thumb_size) * 0.5f)) + border_size; + draw_center = s32((f32(scroll_pos - min_pos) * f) + (f32(thumb_size) * 0.5f)) + + border_size; } void GUIScrollBar::setSmallStep(const s32 &step) @@ -315,6 +316,12 @@ void GUIScrollBar::setPageSize(const s32 &size) setPos(scroll_pos); } +void GUIScrollBar::setArrowsVisible(ArrowVisibility visible) +{ + arrow_visibility = visible; + refreshControls(); +} + s32 GUIScrollBar::getPos() const { return scroll_pos; @@ -419,7 +426,21 @@ void GUIScrollBar::refreshControls() down_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT); } - bool visible = (border_size != 0); + + bool visible; + if (arrow_visibility == DEFAULT) + visible = (border_size != 0); + else if (arrow_visibility == HIDE) { + visible = false; + border_size = 0; + } else { + visible = true; + if (is_horizontal) + border_size = RelativeRect.getHeight(); + else + border_size = RelativeRect.getWidth(); + } + up_button->setVisible(visible); down_button->setVisible(visible); } diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index 349411fc1..29493bb99 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -23,6 +23,13 @@ public: GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id, core::rect<s32> rectangle, bool horizontal, bool auto_scale); + enum ArrowVisibility + { + HIDE, + SHOW, + DEFAULT + }; + virtual void draw(); virtual void updateAbsolutePosition(); virtual bool OnEvent(const SEvent &event); @@ -39,6 +46,7 @@ public: void setLargeStep(const s32 &step); void setPos(const s32 &pos); void setPageSize(const s32 &size); + void setArrowsVisible(ArrowVisibility visible); private: void refreshControls(); @@ -47,6 +55,7 @@ private: IGUIButton *up_button; IGUIButton *down_button; + ArrowVisibility arrow_visibility = DEFAULT; bool is_dragging; bool is_horizontal; bool is_auto_scaling; diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index 45d2ee139..9428cde83 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -19,6 +19,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiVolumeChange.h" #include "debug.h" +#include "guiButton.h" #include "serialization.h" #include <string> #include <IGUICheckBox.h> @@ -103,8 +104,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 80 * s, 30 * s); rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s); const wchar_t *text = wgettext("Exit"); - Environment->addButton(rect, this, ID_soundExitButton, - text); + GUIButton::addButton(Environment, rect, this, ID_soundExitButton, text); delete[] text; } { diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 30417943d..8fb6c6f0f 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -134,6 +134,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) return retval; m_jni_field_name = field_name; + /*~ Imperative, as in "Enter/type in text". + Don't forget the space. */ std::string message = gettext("Enter "); std::string label = wide_to_utf8(getLabelByID(hovered->getID())); if (label.empty()) diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index 1f9adda22..2a3f24a3f 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -230,7 +230,7 @@ private: int m_move_id = -1; bool m_move_has_really_moved = false; - s64 m_move_downtime = 0; + u64 m_move_downtime = 0; bool m_move_sent_as_mouse_event = false; v2s32 m_move_downlocation = v2s32(-10000, -10000); @@ -296,7 +296,7 @@ private: // doubleclick detection variables struct key_event { - unsigned int down_time; + u64 down_time; s32 x; s32 y; }; diff --git a/src/hud.cpp b/src/hud.cpp index 8ada65274..7711e3a4a 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -43,6 +43,8 @@ const struct EnumString es_HudElementStat[] = {HUD_STAT_ALIGN, "alignment"}, {HUD_STAT_OFFSET, "offset"}, {HUD_STAT_WORLD_POS, "world_pos"}, + {HUD_STAT_SIZE, "size"}, + {HUD_STAT_Z_INDEX, "z_index"}, {0, NULL}, }; @@ -74,7 +74,8 @@ enum HudElementStat { HUD_STAT_ALIGN, HUD_STAT_OFFSET, HUD_STAT_WORLD_POS, - HUD_STAT_SIZE + HUD_STAT_SIZE, + HUD_STAT_Z_INDEX, }; struct HudElement { @@ -90,6 +91,7 @@ struct HudElement { v2f offset; v3f world_pos; v2s32 size; + s16 z_index = 0; }; extern const EnumString es_HudElementType[]; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 57b561477..5a24f95a4 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -861,7 +861,7 @@ void ICraftAction::apply(InventoryManager *mgr, } // Put the replacements in the inventory or drop them on the floor, if - // the invenotry is full + // the inventory is full for (auto &output_replacement : output_replacements) { if (list_main) output_replacement = list_main->addItem(output_replacement); diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index 43fc69287..cf64934a2 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -327,6 +327,8 @@ namespace gui (const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0, const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false ); + inline s32 getAscender() const { return font_metrics.ascender; } + protected: bool use_monochrome; bool use_transparency; diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index 5a0f82673..bf61cd64e 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -32,21 +32,15 @@ StaticText::StaticText(const EnrichedString &text, bool border, bool background) : IGUIStaticText(environment, parent, id, rectangle), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT), - Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background), + Border(border), WordWrap(false), Background(background), RestrainTextInside(true), RightToLeft(false), - OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)), OverrideFont(0), LastBreakFont(0) { #ifdef _DEBUG setDebugName("StaticText"); #endif - Text = text.c_str(); - cText = text; - if (environment && environment->getSkin()) - { - BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE); - } + setText(text); } @@ -73,12 +67,7 @@ void StaticText::draw() // draw background if (Background) - { - if ( !OverrideBGColorEnabled ) // skin-colors can change - BGColor = skin->getColor(gui::EGDC_3D_FACE); - - driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect); - } + driver->draw2DRectangle(getBackgroundColor(), frameRect, &AbsoluteClippingRect); // draw the border @@ -89,97 +78,60 @@ void StaticText::draw() } // draw the text - if (cText.size()) - { - IGUIFont* font = getActiveFont(); - - if (font) + IGUIFont *font = getActiveFont(); + if (font && BrokenText.size()) { + if (font != LastBreakFont) + updateText(); + + core::rect<s32> r = frameRect; + s32 height_line = font->getDimension(L"A").Height + font->getKerningHeight(); + s32 height_total = height_line * BrokenText.size(); + if (VAlign == EGUIA_CENTER && WordWrap) { - if (!WordWrap) - { - // TODO: add colors here - if (VAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y - - font->getDimension(L"A").Height - font->getKerningHeight(); - } - if (HAlign == EGUIA_LOWERRIGHT) - { - frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X - - font->getDimension(cText.c_str()).Width; - } + r.UpperLeftCorner.Y = r.getCenter().Y - (height_total / 2); + } + else if (VAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.Y = r.LowerRightCorner.Y - height_total; + } + if (HAlign == EGUIA_LOWERRIGHT) + { + r.UpperLeftCorner.X = r.LowerRightCorner.X - + getTextWidth(); + } -#if USE_FREETYPE - if (font->getType() == irr::gui::EGFT_CUSTOM) { - irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); - tmp->draw(cText, frameRect, - OverrideColorEnabled ? OverrideColor : - skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT), - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - } else -#endif - { - font->draw(Text.c_str(), frameRect, - skin->getColor(EGDC_BUTTON_TEXT), - HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - } - } - else + irr::video::SColor previous_color(255, 255, 255, 255); + for (const EnrichedString &str : BrokenText) { + if (HAlign == EGUIA_LOWERRIGHT) { - if (font != LastBreakFont) - breakText(); - - core::rect<s32> r = frameRect; - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); - s32 totalHeight = height * BrokenText.size(); - if (VAlign == EGUIA_CENTER) - { - r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2); - } - else if (VAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight; - } - - irr::video::SColor previous_color(255, 255, 255, 255); - for (u32 i=0; i<BrokenText.size(); ++i) - { - if (HAlign == EGUIA_LOWERRIGHT) - { - r.UpperLeftCorner.X = frameRect.LowerRightCorner.X - - font->getDimension(BrokenText[i].c_str()).Width; - } - - EnrichedString str = BrokenText[i]; + r.UpperLeftCorner.X = frameRect.LowerRightCorner.X - + font->getDimension(str.c_str()).Width; + } - //str = colorizeText(BrokenText[i].c_str(), colors, previous_color); - //if (!colors.empty()) - // previous_color = colors[colors.size() - 1]; + //str = colorizeText(BrokenText[i].c_str(), colors, previous_color); + //if (!colors.empty()) + // previous_color = colors[colors.size() - 1]; #if USE_FREETYPE - if (font->getType() == irr::gui::EGFT_CUSTOM) { - irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); - tmp->draw(str, - r, previous_color, // FIXME - HAlign == EGUIA_CENTER, false, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - } else + if (font->getType() == irr::gui::EGFT_CUSTOM) { + irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font); + tmp->draw(str, + r, previous_color, // FIXME + HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, + (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + } else #endif - { - // Draw non-colored text - font->draw(str.c_str(), - r, skin->getColor(EGDC_BUTTON_TEXT), - HAlign == EGUIA_CENTER, false, - (RestrainTextInside ? &AbsoluteClippingRect : NULL)); - } + { + // Draw non-colored text + font->draw(str.c_str(), + r, str.getDefaultColor(), // TODO: Implement colorization + HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, + (RestrainTextInside ? &AbsoluteClippingRect : NULL)); + } - r.LowerRightCorner.Y += height; - r.UpperLeftCorner.Y += height; - } - } + r.LowerRightCorner.Y += height_line; + r.UpperLeftCorner.Y += height_line; } } @@ -201,7 +153,7 @@ void StaticText::setOverrideFont(IGUIFont* font) if (OverrideFont) OverrideFont->grab(); - breakText(); + updateText(); } //! Gets the override font (if any) @@ -224,16 +176,15 @@ IGUIFont* StaticText::getActiveFont() const //! Sets another color for the text. void StaticText::setOverrideColor(video::SColor color) { - OverrideColor = color; - OverrideColorEnabled = true; + ColoredText.setDefaultColor(color); + updateText(); } //! Sets another color for the text. void StaticText::setBackgroundColor(video::SColor color) { - BGColor = color; - OverrideBGColorEnabled = true; + ColoredText.setBackground(color); Background = true; } @@ -248,7 +199,10 @@ void StaticText::setDrawBackground(bool draw) //! Gets the background color video::SColor StaticText::getBackgroundColor() const { - return BGColor; + IGUISkin *skin = Environment->getSkin(); + + return (ColoredText.hasBackground() || !skin) ? + ColoredText.getBackground() : skin->getColor(gui::EGDC_3D_FACE); } @@ -298,7 +252,7 @@ const video::SColor& StaticText::getOverrideColor() const video::SColor StaticText::getOverrideColor() const #endif { - return OverrideColor; + return ColoredText.getDefaultColor(); } @@ -306,13 +260,13 @@ video::SColor StaticText::getOverrideColor() const //! color in the gui skin. void StaticText::enableOverrideColor(bool enable) { - OverrideColorEnabled = enable; + // TODO } bool StaticText::isOverrideColorEnabled() const { - return OverrideColorEnabled; + return true; } @@ -321,7 +275,7 @@ bool StaticText::isOverrideColorEnabled() const void StaticText::setWordWrap(bool enable) { WordWrap = enable; - breakText(); + updateText(); } @@ -336,7 +290,7 @@ void StaticText::setRightToLeft(bool rtl) if (RightToLeft != rtl) { RightToLeft = rtl; - breakText(); + updateText(); } } @@ -348,12 +302,23 @@ bool StaticText::isRightToLeft() const //! Breaks the single text line. -void StaticText::breakText() +// Updates the font colors +void StaticText::updateText() { - if (!WordWrap) + const EnrichedString &cText = ColoredText; + BrokenText.clear(); + + if (cText.hasBackground()) + setBackgroundColor(cText.getBackground()); + else + setDrawBackground(false); + + if (!WordWrap) { + BrokenText.push_back(cText); return; + } - BrokenText.clear(); + // Update word wrap IGUISkin* skin = Environment->getSkin(); IGUIFont* font = getActiveFont(); @@ -574,25 +539,20 @@ void StaticText::breakText() //! Sets the new caption of this element. void StaticText::setText(const wchar_t* text) { - setText(EnrichedString(text)); + setText(EnrichedString(text, getOverrideColor())); } -//! Sets the new caption of this element. void StaticText::setText(const EnrichedString &text) { - IGUIElement::setText(text.c_str()); - cText = text; - if (text.hasBackground()) { - setBackgroundColor(text.getBackground()); - } - breakText(); + ColoredText = text; + IGUIElement::setText(ColoredText.c_str()); + updateText(); } - void StaticText::updateAbsolutePosition() { IGUIElement::updateAbsolutePosition(); - breakText(); + updateText(); } @@ -603,39 +563,31 @@ s32 StaticText::getTextHeight() const if (!font) return 0; - s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); - - if (WordWrap) - height *= BrokenText.size(); - - return height; + if (WordWrap) { + s32 height = font->getDimension(L"A").Height + font->getKerningHeight(); + return height * BrokenText.size(); + } + // There may be intentional new lines without WordWrap + return font->getDimension(BrokenText[0].c_str()).Height; } s32 StaticText::getTextWidth() const { - IGUIFont * font = getActiveFont(); - if(!font) + IGUIFont *font = getActiveFont(); + if (!font) return 0; - if(WordWrap) - { - s32 widest = 0; + s32 widest = 0; - for(u32 line = 0; line < BrokenText.size(); ++line) - { - s32 width = font->getDimension(BrokenText[line].c_str()).Width; - - if(width > widest) - widest = width; - } + for (const EnrichedString &line : BrokenText) { + s32 width = font->getDimension(line.c_str()).Width; - return widest; - } - else - { - return font->getDimension(cText.c_str()).Width; + if (width > widest) + widest = width; } + + return widest; } @@ -647,14 +599,14 @@ void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWri IGUIStaticText::serializeAttributes(out,options); out->addBool ("Border", Border); - out->addBool ("OverrideColorEnabled",OverrideColorEnabled); - out->addBool ("OverrideBGColorEnabled",OverrideBGColorEnabled); + out->addBool ("OverrideColorEnabled",true); + out->addBool ("OverrideBGColorEnabled",ColoredText.hasBackground()); out->addBool ("WordWrap", WordWrap); out->addBool ("Background", Background); out->addBool ("RightToLeft", RightToLeft); out->addBool ("RestrainTextInside", RestrainTextInside); - out->addColor ("OverrideColor", OverrideColor); - out->addColor ("BGColor", BGColor); + out->addColor ("OverrideColor", ColoredText.getDefaultColor()); + out->addColor ("BGColor", ColoredText.getBackground()); out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); @@ -668,14 +620,14 @@ void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWr IGUIStaticText::deserializeAttributes(in,options); Border = in->getAttributeAsBool("Border"); - enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); - OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled"); setWordWrap(in->getAttributeAsBool("WordWrap")); Background = in->getAttributeAsBool("Background"); RightToLeft = in->getAttributeAsBool("RightToLeft"); RestrainTextInside = in->getAttributeAsBool("RestrainTextInside"); - OverrideColor = in->getAttributeAsColor("OverrideColor"); - BGColor = in->getAttributeAsColor("BGColor"); + if (in->getAttributeAsBool("OverrideColorEnabled")) + ColoredText.setDefaultColor(in->getAttributeAsColor("OverrideColor")); + if (in->getAttributeAsBool("OverrideBGColorEnabled")) + ColoredText.setBackground(in->getAttributeAsColor("BGColor")); setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 43c587284..1f111ea56 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -34,7 +34,8 @@ namespace gui { public: - //! constructor + // StaticText is translated by EnrichedString. + // No need to use translate_string() StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment, IGUIElement* parent, s32 id, const core::rect<s32>& rectangle, bool background = false); @@ -201,23 +202,20 @@ namespace gui private: //! Breaks the single text line. - void breakText(); + void updateText(); EGUI_ALIGNMENT HAlign, VAlign; bool Border; - bool OverrideColorEnabled; - bool OverrideBGColorEnabled; bool WordWrap; bool Background; bool RestrainTextInside; bool RightToLeft; - video::SColor OverrideColor, BGColor; gui::IGUIFont* OverrideFont; gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated. - EnrichedString cText; - core::array< EnrichedString > BrokenText; + EnrichedString ColoredText; + std::vector<EnrichedString> BrokenText; }; @@ -274,10 +272,7 @@ inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedS inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text) { - auto color = static_text->isOverrideColorEnabled() - ? static_text->getOverrideColor() - : irr::video::SColor(255, 255, 255, 255); - setStaticText(static_text, EnrichedString(text, color)); + setStaticText(static_text, EnrichedString(text, static_text->getOverrideColor())); } #endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/light.cpp b/src/light.cpp index 9b6f5c210..8196fedff 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -29,44 +29,48 @@ static u8 light_LUT[LIGHT_SUN + 1]; // The const ref to light_LUT is what is actually used in the code const u8 *light_decode_table = light_LUT; + struct LightingParams { - float a, b, c; // polynomial coefficients - float boost, center, sigma; // normal boost parameters - float gamma; + float a, b, c; // Lighting curve polynomial coefficients + float boost, center, sigma; // Lighting curve parametric boost + float gamma; // Lighting curve gamma correction }; static LightingParams params; + float decode_light_f(float x) { - if (x >= 1.0f) // x is equal to 1.0f half the time + if (x >= 1.0f) // x is often 1.0f return 1.0f; x = std::fmax(x, 0.0f); float brightness = ((params.a * x + params.b) * x + params.c) * x; - brightness += params.boost * std::exp(-0.5f * sqr((x - params.center) / params.sigma)); - if (brightness <= 0.0f) // may happen if parameters are insane + brightness += params.boost * + std::exp(-0.5f * sqr((x - params.center) / params.sigma)); + if (brightness <= 0.0f) // May happen if parameters are extreme return 0.0f; if (brightness >= 1.0f) return 1.0f; return powf(brightness, 1.0f / params.gamma); } + // Initialize or update the light value tables using the specified gamma void set_light_table(float gamma) { -// Lighting curve derivatives - const float alpha = g_settings->getFloat("lighting_alpha"); - const float beta = g_settings->getFloat("lighting_beta"); -// Lighting curve coefficients +// Lighting curve bounding gradients + const float alpha = rangelim(g_settings->getFloat("lighting_alpha"), 0.0f, 3.0f); + const float beta = rangelim(g_settings->getFloat("lighting_beta"), 0.0f, 3.0f); +// Lighting curve polynomial coefficients params.a = alpha + beta - 2.0f; params.b = 3.0f - 2.0f * alpha - beta; params.c = alpha; -// Mid boost - params.boost = g_settings->getFloat("lighting_boost"); - params.center = g_settings->getFloat("lighting_boost_center"); - params.sigma = g_settings->getFloat("lighting_boost_spread"); -// Gamma correction - params.gamma = rangelim(gamma, 0.5f, 10.0f); +// Lighting curve parametric boost + params.boost = rangelim(g_settings->getFloat("lighting_boost"), 0.0f, 0.4f); + params.center = rangelim(g_settings->getFloat("lighting_boost_center"), 0.0f, 1.0f); + params.sigma = rangelim(g_settings->getFloat("lighting_boost_spread"), 0.0f, 0.4f); +// Lighting curve gamma correction + params.gamma = rangelim(gamma, 0.33f, 3.0f); // Boundary values should be fixed light_LUT[0] = 0; diff --git a/src/map.cpp b/src/map.cpp index 0a7099a06..40aba067e 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -179,7 +179,7 @@ void Map::setNode(v3s16 p, MapNode & n) v3s16 blockpos = getNodeBlockPos(p); MapBlock *block = getBlockNoCreate(blockpos); v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; - // Never allow placing CONTENT_IGNORE, it fucks up stuff + // Never allow placing CONTENT_IGNORE, it causes problems if(n.getContent() == CONTENT_IGNORE){ bool temp_bool; errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE" @@ -761,8 +761,8 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks, // set level to last 3 bits, flowing down bit to 4th bit n0.param2 = (flowing_down ? LIQUID_FLOW_DOWN_MASK : 0x00) | (new_node_level & LIQUID_LEVEL_MASK); } else { - // set the liquid level and flow bit to 0 - n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); + // set the liquid level and flow bits to 0 + n0.param2 &= ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK); } // change the node. @@ -1285,8 +1285,7 @@ ServerMap::~ServerMap() Close database if it was opened */ delete dbase; - if (dbase_ro) - delete dbase_ro; + delete dbase_ro; #if 0 /* @@ -1762,13 +1761,6 @@ plan_b: //return (s16)level; } -bool ServerMap::loadFromFolders() { - if (!dbase->initialized() && - !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite")) - return true; - return false; -} - void ServerMap::createDirs(const std::string &path) { if (!fs::CreateAllDirs(path)) { @@ -1778,80 +1770,6 @@ void ServerMap::createDirs(const std::string &path) } } -std::string ServerMap::getSectorDir(v2s16 pos, int layout) -{ - char cc[9]; - switch(layout) - { - case 1: - porting::mt_snprintf(cc, sizeof(cc), "%.4x%.4x", - (unsigned int) pos.X & 0xffff, - (unsigned int) pos.Y & 0xffff); - - return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc; - case 2: - porting::mt_snprintf(cc, sizeof(cc), (std::string("%.3x") + DIR_DELIM + "%.3x").c_str(), - (unsigned int) pos.X & 0xfff, - (unsigned int) pos.Y & 0xfff); - - return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc; - default: - assert(false); - return ""; - } -} - -v2s16 ServerMap::getSectorPos(const std::string &dirname) -{ - unsigned int x = 0, y = 0; - int r; - std::string component; - fs::RemoveLastPathComponent(dirname, &component, 1); - if(component.size() == 8) - { - // Old layout - r = sscanf(component.c_str(), "%4x%4x", &x, &y); - } - else if(component.size() == 3) - { - // New layout - fs::RemoveLastPathComponent(dirname, &component, 2); - r = sscanf(component.c_str(), (std::string("%3x") + DIR_DELIM + "%3x").c_str(), &x, &y); - // Sign-extend the 12 bit values up to 16 bits... - if(x & 0x800) x |= 0xF000; - if(y & 0x800) y |= 0xF000; - } - else - { - r = -1; - } - - FATAL_ERROR_IF(r != 2, "getSectorPos()"); - v2s16 pos((s16)x, (s16)y); - return pos; -} - -v3s16 ServerMap::getBlockPos(const std::string §ordir, const std::string &blockfile) -{ - v2s16 p2d = getSectorPos(sectordir); - - if(blockfile.size() != 4){ - throw InvalidFilenameException("Invalid block filename"); - } - unsigned int y; - int r = sscanf(blockfile.c_str(), "%4x", &y); - if(r != 1) - throw InvalidFilenameException("Invalid block filename"); - return v3s16(p2d.X, y, p2d.Y); -} - -std::string ServerMap::getBlockFilename(v3s16 p) -{ - char cc[5]; - porting::mt_snprintf(cc, sizeof(cc), "%.4x", (unsigned int)p.Y&0xffff); - return cc; -} - void ServerMap::save(ModifiedState save_level) { if (!m_map_saving_enabled) { @@ -1921,10 +1839,6 @@ void ServerMap::save(ModifiedState save_level) void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst) { - if (loadFromFolders()) { - errorstream << "Map::listAllLoadableBlocks(): Result will be missing " - << "all blocks that are stored in flat files." << std::endl; - } dbase->listAllLoadableBlocks(dst); if (dbase_ro) dbase_ro->listAllLoadableBlocks(dst); @@ -2018,83 +1932,6 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db) return ret; } -void ServerMap::loadBlock(const std::string §ordir, const std::string &blockfile, - MapSector *sector, bool save_after_load) -{ - std::string fullpath = sectordir + DIR_DELIM + blockfile; - try { - std::ifstream is(fullpath.c_str(), std::ios_base::binary); - if (!is.good()) - throw FileNotGoodException("Cannot open block file"); - - v3s16 p3d = getBlockPos(sectordir, blockfile); - v2s16 p2d(p3d.X, p3d.Z); - - assert(sector->getPos() == p2d); - - u8 version = SER_FMT_VER_INVALID; - is.read((char*)&version, 1); - - if(is.fail()) - throw SerializationError("ServerMap::loadBlock(): Failed" - " to read MapBlock version"); - - /*u32 block_size = MapBlock::serializedLength(version); - SharedBuffer<u8> data(block_size); - is.read((char*)*data, block_size);*/ - - // This will always return a sector because we're the server - //MapSector *sector = emergeSector(p2d); - - MapBlock *block = NULL; - bool created_new = false; - block = sector->getBlockNoCreateNoEx(p3d.Y); - if(block == NULL) - { - block = sector->createBlankBlockNoInsert(p3d.Y); - created_new = true; - } - - // Read basic data - block->deSerialize(is, version, true); - - // If it's a new block, insert it to the map - if (created_new) { - sector->insertBlock(block); - ReflowScan scanner(this, m_emerge->ndef); - scanner.scan(block, &m_transforming_liquid); - } - - /* - Save blocks loaded in old format in new format - */ - - if(version < SER_FMT_VER_HIGHEST_WRITE || save_after_load) - { - saveBlock(block); - - // Should be in database now, so delete the old file - fs::RecursiveDelete(fullpath); - } - - // We just loaded it from the disk, so it's up-to-date. - block->resetModified(); - - } - catch(SerializationError &e) - { - warningstream<<"Invalid block data on disk " - <<"fullpath="<<fullpath - <<" (SerializationError). " - <<"what()="<<e.what() - <<std::endl; - // Ignoring. A new one will be generated. - abort(); - - // TODO: Backup file; name is in fullpath. - } -} - void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) { try { @@ -2172,39 +2009,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos) loadBlock(&ret, blockpos, createSector(p2d), false); } } else { - // Not found in database, try the files - - // The directory layout we're going to load from. - // 1 - original sectors/xxxxzzzz/ - // 2 - new sectors2/xxx/zzz/ - // If we load from anything but the latest structure, we will - // immediately save to the new one, and remove the old. - std::string sectordir1 = getSectorDir(p2d, 1); - std::string sectordir; - if (fs::PathExists(sectordir1)) { - sectordir = sectordir1; - } else { - sectordir = getSectorDir(p2d, 2); - } - - /* - Make sure sector is loaded - */ - - MapSector *sector = getSectorNoGenerate(p2d); - - /* - Make sure file exists - */ - - std::string blockfilename = getBlockFilename(blockpos); - if (!fs::PathExists(sectordir + DIR_DELIM + blockfilename)) - return NULL; - - /* - Load block and save it to the database - */ - loadBlock(sectordir, blockfilename, sector, true); + return NULL; } MapBlock *block = getBlockNoCreateNoEx(blockpos); @@ -380,21 +380,12 @@ public: names when saving */ void createDirs(const std::string &path); - // returns something like "map/sectors/xxxxxxxx" - std::string getSectorDir(v2s16 pos, int layout = 2); - // dirname: final directory name - v2s16 getSectorPos(const std::string &dirname); - v3s16 getBlockPos(const std::string §ordir, const std::string &blockfile); - static std::string getBlockFilename(v3s16 p); /* Database functions */ static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); - // Returns true if the database file does not exist - bool loadFromFolders(); - // Call these before and after saving of blocks void beginSave(); void endSave(); @@ -407,9 +398,6 @@ public: bool saveBlock(MapBlock *block); static bool saveBlock(MapBlock *block, MapDatabase *db); - // This will generate a sector with getSector if not found. - void loadBlock(const std::string §ordir, const std::string &blockfile, - MapSector *sector, bool save_after_load=false); MapBlock* loadBlock(v3s16 p); // Database version void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp index a0ff1c754..7ef4bf12e 100644 --- a/src/map_settings_manager.cpp +++ b/src/map_settings_manager.cpp @@ -32,6 +32,7 @@ MapSettingsManager::MapSettingsManager(Settings *user_settings, m_user_settings(user_settings) { assert(m_user_settings != NULL); + Mapgen::setDefaultSettings(m_map_settings); } diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp index fa34b7273..a9df4506f 100644 --- a/src/mapgen/cavegen.cpp +++ b/src/mapgen/cavegen.cpp @@ -280,18 +280,18 @@ CavesRandomWalk::CavesRandomWalk( int water_level, content_t water_source, content_t lava_source, - int lava_depth, + float large_cave_flooded, BiomeGen *biomegen) { assert(ndef); - this->ndef = ndef; - this->gennotify = gennotify; - this->seed = seed; - this->water_level = water_level; - this->np_caveliquids = &nparams_caveliquids; - this->lava_depth = lava_depth; - this->bmgn = biomegen; + this->ndef = ndef; + this->gennotify = gennotify; + this->seed = seed; + this->water_level = water_level; + this->np_caveliquids = &nparams_caveliquids; + this->large_cave_flooded = large_cave_flooded; + this->bmgn = biomegen; c_water_source = water_source; if (c_water_source == CONTENT_IGNORE) @@ -322,7 +322,7 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, this->ystride = nmax.X - nmin.X + 1; - flooded = ps->range(1, 2) == 2; + flooded = ps->range(1, 1000) <= large_cave_flooded * 1000.0f; // If flooded: // Get biome at mapchunk midpoint. If cave liquid defined for biome, use it. @@ -364,12 +364,13 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, // Area starting point in nodes of = node_min; - // Allow a bit more - //(this should be more than the maximum radius of the tunnel) - const s16 insure = 10; + // Allow caves to extend up to 16 nodes beyond the mapchunk edge, to allow + // connecting with caves of neighbor mapchunks. + // 'insure' is needed to avoid many 'out of voxelmanip' cave nodes. + const s16 insure = 2; s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1); - ar += v3s16(1, 0, 1) * more * 2; - of -= v3s16(1, 0, 1) * more; + ar += v3s16(1, 1, 1) * more * 2; + of -= v3s16(1, 1, 1) * more; route_y_min = 0; // Allow half a diameter + 7 over stone surface @@ -527,12 +528,12 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) if (use_biome_liquid) { liquidnode = c_biome_liquid; } else { - // TODO remove this. Cave liquids are now defined and located using biome - // definitions. // If cave liquid not defined by biome, fallback to old hardcoded behaviour. + // TODO 'np_caveliquids' is deprecated and should eventually be removed. + // Cave liquids are now defined and located using biome definitions. float nval = NoisePerlin3D(np_caveliquids, startp.X, startp.Y, startp.Z, seed); - liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ? + liquidnode = (nval < 0.40f && node_max.Y < water_level - 256) ? lavanode : waternode; } } diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h index 3f1730ddb..ff09f9423 100644 --- a/src/mapgen/cavegen.h +++ b/src/mapgen/cavegen.h @@ -116,15 +116,13 @@ public: s16 *heightmap; BiomeGen *bmgn; - // configurable parameters s32 seed; int water_level; - // TODO 'lava_depth' and 'np_caveliquids' are deprecated and should be removed. + float large_cave_flooded; + // TODO 'np_caveliquids' is deprecated and should eventually be removed. // Cave liquids are now defined and located using biome definitions. - int lava_depth; NoiseParams *np_caveliquids; - // intermediate state variables u16 ystride; s16 min_tunnel_diameter; @@ -161,7 +159,7 @@ public: CavesRandomWalk(const NodeDefManager *ndef, GenerateNotifier *gennotify = NULL, s32 seed = 0, int water_level = 1, content_t water_source = CONTENT_IGNORE, content_t lava_source = CONTENT_IGNORE, - int lava_depth = -256, BiomeGen *biomegen = NULL); + float large_cave_flooded = 0.5f, BiomeGen *biomegen = NULL); // vm and ps are mandatory parameters. // If heightmap is NULL, the surface level at all points is assumed to diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 6d5e721ce..79c429ff6 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -215,6 +215,17 @@ void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hid } } +void Mapgen::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mg_flags", flagdesc_mapgen, + MG_CAVES | MG_DUNGEONS | MG_LIGHT | MG_DECORATIONS | MG_BIOMES); + + for (int i = 0; i < (int)MAPGEN_INVALID; ++i) { + MapgenParams *params = createMapgenParams((MapgenType)i); + params->setDefaultSettings(settings); + delete params; + } +} u32 Mapgen::getBlockSeed(v3s16 p, s32 seed) { @@ -831,7 +842,9 @@ void MapgenBasic::dustTopNodes() void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y) { - if (node_min.Y > max_stone_y) + // cave_width >= 10 is used to disable generation and avoid the intensive + // 3D noise calculations. Tunnels already have zero width when cave_width > 1. + if (node_min.Y > max_stone_y || cave_width >= 10.0f) return; CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize, @@ -841,20 +854,33 @@ void MapgenBasic::generateCavesNoiseIntersection(s16 max_stone_y) } -void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_depth) +void MapgenBasic::generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_ymax) { - if (node_min.Y > max_stone_y || node_max.Y > large_cave_depth) + if (node_min.Y > max_stone_y) return; PseudoRandom ps(blockseed + 21343); - u32 bruises_count = ps.range(0, 2); + // Small randomwalk caves + u32 num_small_caves = ps.range(small_cave_num_min, small_cave_num_max); - for (u32 i = 0; i < bruises_count; i++) { + for (u32 i = 0; i < num_small_caves; i++) { CavesRandomWalk cave(ndef, &gennotify, seed, water_level, - c_water_source, c_lava_source, lava_depth, biomegen); + c_water_source, c_lava_source, large_cave_flooded, biomegen); + cave.makeCave(vm, node_min, node_max, &ps, false, max_stone_y, heightmap); + } + + if (node_max.Y > large_cave_ymax) + return; - cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, - heightmap); + // Large randomwalk caves below 'large_cave_ymax'. + // 'large_cave_ymax' can differ from the 'large_cave_depth' mapgen parameter, + // it is set to world base to disable large caves in or near caverns. + u32 num_large_caves = ps.range(large_cave_num_min, large_cave_num_max); + + for (u32 i = 0; i < num_large_caves; i++) { + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, + c_water_source, c_lava_source, large_cave_flooded, biomegen); + cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); } } @@ -873,7 +899,8 @@ bool MapgenBasic::generateCavernsNoise(s16 max_stone_y) void MapgenBasic::generateDungeons(s16 max_stone_y) { - if (max_stone_y < node_min.Y) + if (node_min.Y > max_stone_y || node_min.Y > dungeon_ymax || + node_max.Y < dungeon_ymin) return; u16 num_dungeons = std::fmax(std::floor( @@ -1052,7 +1079,7 @@ void MapgenParams::writeParams(Settings *settings) const settings->setS16("water_level", water_level); settings->setS16("mapgen_limit", mapgen_limit); settings->setS16("chunksize", chunksize); - settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX); + settings->setFlagStr("mg_flags", flags, flagdesc_mapgen); if (bparams) bparams->writeParams(settings); diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 0ac26d538..dc325c791 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -30,10 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MAPGEN_DEFAULT_NAME "v7" /////////////////// Mapgen flags -#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags +#define MG_TREES 0x01 // Obsolete. Moved into mgv6 flags #define MG_CAVES 0x02 #define MG_DUNGEONS 0x04 -#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags +#define MG_FLAT 0x08 // Obsolete. Moved into mgv6 flags #define MG_LIGHT 0x10 #define MG_DECORATIONS 0x20 #define MG_BIOMES 0x40 @@ -124,7 +124,9 @@ struct MapgenParams { u64 seed = 0; s16 water_level = 1; s16 mapgen_limit = MAX_MAP_GENERATION_LIMIT; - u32 flags = MG_CAVES | MG_LIGHT | MG_DECORATIONS | MG_BIOMES; + // Flags set in readParams + u32 flags = 0; + u32 spflags = 0; BiomeParams *bparams = nullptr; @@ -133,6 +135,8 @@ struct MapgenParams { virtual void readParams(const Settings *settings); virtual void writeParams(Settings *settings) const; + // Default settings for g_settings such as flags + virtual void setDefaultSettings(Settings *settings) {}; s32 getSpawnRangeMax(); @@ -214,6 +218,7 @@ public: EmergeManager *emerge); static MapgenParams *createMapgenParams(MapgenType mgtype); static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden); + static void setDefaultSettings(Settings *settings); private: // isLiquidHorizontallyFlowable() is a helper function for updateLiquid() @@ -244,7 +249,7 @@ public: virtual void generateBiomes(); virtual void dustTopNodes(); virtual void generateCavesNoiseIntersection(s16 max_stone_y); - virtual void generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_depth); + virtual void generateCavesRandomWalk(s16 max_stone_y, s16 large_cave_ymax); virtual bool generateCavernsNoise(s16 max_stone_y); virtual void generateDungeons(s16 max_stone_y); @@ -280,7 +285,12 @@ protected: float cavern_limit; float cavern_taper; float cavern_threshold; - // TODO 'lava_depth' is deprecated and should be removed. Cave liquids are - // now defined and located using biome definitions. - int lava_depth; + int small_cave_num_min; + int small_cave_num_max; + int large_cave_num_min; + int large_cave_num_max; + float large_cave_flooded; + s16 large_cave_depth; + s16 dungeon_ymin; + s16 dungeon_ymax; }; diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index 12ce07da4..0dc1d33be 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -58,15 +58,19 @@ MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager river_depth = params->river_depth; valley_width = params->valley_width; - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; grad_wl = 1 - water_level; @@ -148,14 +152,18 @@ void MapgenCarpathianParams::readParams(const Settings *settings) settings->getFloatNoEx("mgcarpathian_river_depth", river_depth); settings->getFloatNoEx("mgcarpathian_valley_width", valley_width); - settings->getFloatNoEx("mgcarpathian_cave_width", cave_width); - settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth); - settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit); - settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgcarpathian_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgcarpathian_dungeon_ymax", dungeon_ymax); + settings->getFloatNoEx("mgcarpathian_cave_width", cave_width); + settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgcarpathian_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgcarpathian_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgcarpathian_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgcarpathian_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgcarpathian_large_cave_flooded", large_cave_flooded); + settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit); + settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgcarpathian_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgcarpathian_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); settings->getNoiseParams("mgcarpathian_np_height1", np_height1); @@ -179,21 +187,25 @@ void MapgenCarpathianParams::readParams(const Settings *settings) void MapgenCarpathianParams::writeParams(Settings *settings) const { - settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX); + settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian); settings->setFloat("mgcarpathian_base_level", base_level); settings->setFloat("mgcarpathian_river_width", river_width); settings->setFloat("mgcarpathian_river_depth", river_depth); settings->setFloat("mgcarpathian_valley_width", valley_width); - settings->setFloat("mgcarpathian_cave_width", cave_width); - settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth); - settings->setS16("mgcarpathian_lava_depth", lava_depth); - settings->setS16("mgcarpathian_cavern_limit", cavern_limit); - settings->setS16("mgcarpathian_cavern_taper", cavern_taper); - settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold); - settings->setS16("mgcarpathian_dungeon_ymin", dungeon_ymin); - settings->setS16("mgcarpathian_dungeon_ymax", dungeon_ymax); + settings->setFloat("mgcarpathian_cave_width", cave_width); + settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth); + settings->setU16("mgcarpathian_small_cave_num_min", small_cave_num_min); + settings->setU16("mgcarpathian_small_cave_num_max", small_cave_num_max); + settings->setU16("mgcarpathian_large_cave_num_min", large_cave_num_min); + settings->setU16("mgcarpathian_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgcarpathian_large_cave_flooded", large_cave_flooded); + settings->setS16("mgcarpathian_cavern_limit", cavern_limit); + settings->setS16("mgcarpathian_cavern_taper", cavern_taper); + settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold); + settings->setS16("mgcarpathian_dungeon_ymin", dungeon_ymin); + settings->setS16("mgcarpathian_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); settings->setNoiseParams("mgcarpathian_np_height1", np_height1); @@ -215,6 +227,12 @@ void MapgenCarpathianParams::writeParams(Settings *settings) const } +void MapgenCarpathianParams::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgcarpathian_spflags", flagdesc_mapgen_carpathian, + MGCARPATHIAN_CAVERNS); +} + //////////////////////////////////////////////////////////////////////////////// @@ -301,8 +319,7 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data) m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h index 1fbac4bfd..acd379958 100644 --- a/src/mapgen/mapgen_carpathian.h +++ b/src/mapgen/mapgen_carpathian.h @@ -37,15 +37,18 @@ struct MapgenCarpathianParams : public MapgenParams float river_depth = 24.0f; float valley_width = 0.25f; - u32 spflags = MGCARPATHIAN_CAVERNS; - float cave_width = 0.09f; - s16 large_cave_depth = -33; - s16 lava_depth = -256; - s16 cavern_limit = -256; - s16 cavern_taper = 256; - float cavern_threshold = 0.7f; - s16 dungeon_ymin = -31000; - s16 dungeon_ymax = 31000; + float cave_width = 0.09f; + s16 large_cave_depth = -33; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; + s16 dungeon_ymin = -31000; + s16 dungeon_ymax = 31000; NoiseParams np_filler_depth; NoiseParams np_height1; @@ -70,6 +73,7 @@ struct MapgenCarpathianParams : public MapgenParams void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; class MapgenCarpathian : public MapgenBasic @@ -89,10 +93,6 @@ private: float river_depth; float valley_width; - s16 large_cave_depth; - s16 dungeon_ymin; - s16 dungeon_ymax; - Noise *noise_height1; Noise *noise_height2; Noise *noise_height3; diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 773b7b10f..879435948 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -51,17 +51,21 @@ FlagDesc flagdesc_mapgen_flat[] = { MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeManager *emerge) : MapgenBasic(MAPGEN_FLAT, params, emerge) { - spflags = params->spflags; - ground_level = params->ground_level; - large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; - cave_width = params->cave_width; - lake_threshold = params->lake_threshold; - lake_steepness = params->lake_steepness; - hill_threshold = params->hill_threshold; - hill_steepness = params->hill_steepness; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + spflags = params->spflags; + ground_level = params->ground_level; + large_cave_depth = params->large_cave_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; + cave_width = params->cave_width; + lake_threshold = params->lake_threshold; + lake_steepness = params->lake_steepness; + hill_threshold = params->hill_threshold; + hill_steepness = params->hill_steepness; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // 2D noise noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); @@ -96,17 +100,21 @@ MapgenFlatParams::MapgenFlatParams(): void MapgenFlatParams::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat); - settings->getS16NoEx("mgflat_ground_level", ground_level); - settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgflat_lava_depth", lava_depth); - settings->getFloatNoEx("mgflat_cave_width", cave_width); - settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold); - settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); - settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); - settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); - settings->getS16NoEx("mgflat_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgflat_dungeon_ymax", dungeon_ymax); + settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat); + settings->getS16NoEx("mgflat_ground_level", ground_level); + settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgflat_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgflat_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgflat_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgflat_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgflat_large_cave_flooded", large_cave_flooded); + settings->getFloatNoEx("mgflat_cave_width", cave_width); + settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold); + settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); + settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); + settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); + settings->getS16NoEx("mgflat_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgflat_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgflat_np_terrain", np_terrain); settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth); @@ -118,17 +126,21 @@ void MapgenFlatParams::readParams(const Settings *settings) void MapgenFlatParams::writeParams(Settings *settings) const { - settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat, U32_MAX); - settings->setS16("mgflat_ground_level", ground_level); - settings->setS16("mgflat_large_cave_depth", large_cave_depth); - settings->setS16("mgflat_lava_depth", lava_depth); - settings->setFloat("mgflat_cave_width", cave_width); - settings->setFloat("mgflat_lake_threshold", lake_threshold); - settings->setFloat("mgflat_lake_steepness", lake_steepness); - settings->setFloat("mgflat_hill_threshold", hill_threshold); - settings->setFloat("mgflat_hill_steepness", hill_steepness); - settings->setS16("mgflat_dungeon_ymin", dungeon_ymin); - settings->setS16("mgflat_dungeon_ymax", dungeon_ymax); + settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat); + settings->setS16("mgflat_ground_level", ground_level); + settings->setS16("mgflat_large_cave_depth", large_cave_depth); + settings->setU16("mgflat_small_cave_num_min", small_cave_num_min); + settings->setU16("mgflat_small_cave_num_max", small_cave_num_max); + settings->setU16("mgflat_large_cave_num_min", large_cave_num_min); + settings->setU16("mgflat_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgflat_large_cave_flooded", large_cave_flooded); + settings->setFloat("mgflat_cave_width", cave_width); + settings->setFloat("mgflat_lake_threshold", lake_threshold); + settings->setFloat("mgflat_lake_steepness", lake_steepness); + settings->setFloat("mgflat_hill_threshold", hill_threshold); + settings->setFloat("mgflat_hill_steepness", hill_steepness); + settings->setS16("mgflat_dungeon_ymin", dungeon_ymin); + settings->setS16("mgflat_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgflat_np_terrain", np_terrain); settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth); @@ -138,6 +150,12 @@ void MapgenFlatParams::writeParams(Settings *settings) const } +void MapgenFlatParams::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgflat_spflags", flagdesc_mapgen_flat, 0); +} + + ///////////////////////////////////////////////////////////////// @@ -219,8 +237,7 @@ void MapgenFlat::makeChunk(BlockMakeData *data) // Generate the registered ores m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h index d2598695f..c314c7605 100644 --- a/src/mapgen/mapgen_flat.h +++ b/src/mapgen/mapgen_flat.h @@ -32,10 +32,13 @@ extern FlagDesc flagdesc_mapgen_flat[]; struct MapgenFlatParams : public MapgenParams { - u32 spflags = 0; s16 ground_level = 8; s16 large_cave_depth = -33; - s16 lava_depth = -256; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; float cave_width = 0.09f; float lake_threshold = -0.45f; float lake_steepness = 48.0f; @@ -55,6 +58,7 @@ struct MapgenFlatParams : public MapgenParams void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; class MapgenFlat : public MapgenBasic @@ -71,13 +75,10 @@ public: private: s16 ground_level; - s16 large_cave_depth; float lake_threshold; float lake_steepness; float hill_threshold; float hill_steepness; - s16 dungeon_ymin; - s16 dungeon_ymax; Noise *noise_terrain; }; diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index 091dbacfa..96febb4f4 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -51,21 +51,25 @@ FlagDesc flagdesc_mapgen_fractal[] = { MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge) : MapgenBasic(MAPGEN_FRACTAL, params, emerge) { - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; - fractal = params->fractal; - iterations = params->iterations; - scale = params->scale; - offset = params->offset; - slice_w = params->slice_w; - julia_x = params->julia_x; - julia_y = params->julia_y; - julia_z = params->julia_z; - julia_w = params->julia_w; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; + fractal = params->fractal; + iterations = params->iterations; + scale = params->scale; + offset = params->offset; + slice_w = params->slice_w; + julia_x = params->julia_x; + julia_y = params->julia_y; + julia_z = params->julia_z; + julia_w = params->julia_w; //// 2D noise if (spflags & MGFRACTAL_TERRAIN) @@ -86,9 +90,7 @@ MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge) MapgenFractal::~MapgenFractal() { - if (noise_seabed) - delete noise_seabed; - + delete noise_seabed; delete noise_filler_depth; } @@ -105,21 +107,25 @@ MapgenFractalParams::MapgenFractalParams(): void MapgenFractalParams::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); - settings->getFloatNoEx("mgfractal_cave_width", cave_width); - settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgfractal_lava_depth", lava_depth); - settings->getS16NoEx("mgfractal_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax); - settings->getU16NoEx("mgfractal_fractal", fractal); - settings->getU16NoEx("mgfractal_iterations", iterations); - settings->getV3FNoEx("mgfractal_scale", scale); - settings->getV3FNoEx("mgfractal_offset", offset); - settings->getFloatNoEx("mgfractal_slice_w", slice_w); - settings->getFloatNoEx("mgfractal_julia_x", julia_x); - settings->getFloatNoEx("mgfractal_julia_y", julia_y); - settings->getFloatNoEx("mgfractal_julia_z", julia_z); - settings->getFloatNoEx("mgfractal_julia_w", julia_w); + settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); + settings->getFloatNoEx("mgfractal_cave_width", cave_width); + settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgfractal_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgfractal_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgfractal_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgfractal_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgfractal_large_cave_flooded", large_cave_flooded); + settings->getS16NoEx("mgfractal_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgfractal_dungeon_ymax", dungeon_ymax); + settings->getU16NoEx("mgfractal_fractal", fractal); + settings->getU16NoEx("mgfractal_iterations", iterations); + settings->getV3FNoEx("mgfractal_scale", scale); + settings->getV3FNoEx("mgfractal_offset", offset); + settings->getFloatNoEx("mgfractal_slice_w", slice_w); + settings->getFloatNoEx("mgfractal_julia_x", julia_x); + settings->getFloatNoEx("mgfractal_julia_y", julia_y); + settings->getFloatNoEx("mgfractal_julia_z", julia_z); + settings->getFloatNoEx("mgfractal_julia_w", julia_w); settings->getNoiseParams("mgfractal_np_seabed", np_seabed); settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth); @@ -131,21 +137,25 @@ void MapgenFractalParams::readParams(const Settings *settings) void MapgenFractalParams::writeParams(Settings *settings) const { - settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal, U32_MAX); - settings->setFloat("mgfractal_cave_width", cave_width); - settings->setS16("mgfractal_large_cave_depth", large_cave_depth); - settings->setS16("mgfractal_lava_depth", lava_depth); - settings->setS16("mgfractal_dungeon_ymin", dungeon_ymin); - settings->setS16("mgfractal_dungeon_ymax", dungeon_ymax); - settings->setU16("mgfractal_fractal", fractal); - settings->setU16("mgfractal_iterations", iterations); - settings->setV3F("mgfractal_scale", scale); - settings->setV3F("mgfractal_offset", offset); - settings->setFloat("mgfractal_slice_w", slice_w); - settings->setFloat("mgfractal_julia_x", julia_x); - settings->setFloat("mgfractal_julia_y", julia_y); - settings->setFloat("mgfractal_julia_z", julia_z); - settings->setFloat("mgfractal_julia_w", julia_w); + settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); + settings->setFloat("mgfractal_cave_width", cave_width); + settings->setS16("mgfractal_large_cave_depth", large_cave_depth); + settings->setU16("mgfractal_small_cave_num_min", small_cave_num_min); + settings->setU16("mgfractal_small_cave_num_max", small_cave_num_max); + settings->setU16("mgfractal_large_cave_num_min", large_cave_num_min); + settings->setU16("mgfractal_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgfractal_large_cave_flooded", large_cave_flooded); + settings->setS16("mgfractal_dungeon_ymin", dungeon_ymin); + settings->setS16("mgfractal_dungeon_ymax", dungeon_ymax); + settings->setU16("mgfractal_fractal", fractal); + settings->setU16("mgfractal_iterations", iterations); + settings->setV3F("mgfractal_scale", scale); + settings->setV3F("mgfractal_offset", offset); + settings->setFloat("mgfractal_slice_w", slice_w); + settings->setFloat("mgfractal_julia_x", julia_x); + settings->setFloat("mgfractal_julia_y", julia_y); + settings->setFloat("mgfractal_julia_z", julia_z); + settings->setFloat("mgfractal_julia_w", julia_w); settings->setNoiseParams("mgfractal_np_seabed", np_seabed); settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth); @@ -155,6 +165,13 @@ void MapgenFractalParams::writeParams(Settings *settings) const } +void MapgenFractalParams::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgfractal_spflags", flagdesc_mapgen_fractal, + MGFRACTAL_TERRAIN); +} + + ///////////////////////////////////////////////////////////////// @@ -237,8 +254,7 @@ void MapgenFractal::makeChunk(BlockMakeData *data) m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h index 82622d4d9..971dfd822 100644 --- a/src/mapgen/mapgen_fractal.h +++ b/src/mapgen/mapgen_fractal.h @@ -35,10 +35,13 @@ extern FlagDesc flagdesc_mapgen_fractal[]; struct MapgenFractalParams : public MapgenParams { - u32 spflags = MGFRACTAL_TERRAIN; float cave_width = 0.09f; s16 large_cave_depth = -33; - s16 lava_depth = -256; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; s16 dungeon_ymin = -31000; s16 dungeon_ymax = 31000; u16 fractal = 1; @@ -62,6 +65,7 @@ struct MapgenFractalParams : public MapgenParams void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; @@ -81,10 +85,6 @@ public: private: u16 formula; bool julia; - - s16 large_cave_depth; - s16 dungeon_ymin; - s16 dungeon_ymax; u16 fractal; u16 iterations; v3f scale; diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index bf99fd335..447fe8c50 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -48,15 +48,19 @@ FlagDesc flagdesc_mapgen_v5[] = { MapgenV5::MapgenV5(MapgenV5Params *params, EmergeManager *emerge) : MapgenBasic(MAPGEN_V5, params, emerge) { - spflags = params->spflags; - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // Terrain noise noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); @@ -98,15 +102,19 @@ MapgenV5Params::MapgenV5Params(): void MapgenV5Params::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5); - settings->getFloatNoEx("mgv5_cave_width", cave_width); - settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgv5_lava_depth", lava_depth); - settings->getS16NoEx("mgv5_cavern_limit", cavern_limit); - settings->getS16NoEx("mgv5_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgv5_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgv5_dungeon_ymax", dungeon_ymax); + settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5); + settings->getFloatNoEx("mgv5_cave_width", cave_width); + settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgv5_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgv5_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgv5_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgv5_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgv5_large_cave_flooded", large_cave_flooded); + settings->getS16NoEx("mgv5_cavern_limit", cavern_limit); + settings->getS16NoEx("mgv5_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgv5_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgv5_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth); settings->getNoiseParams("mgv5_np_factor", np_factor); @@ -121,15 +129,19 @@ void MapgenV5Params::readParams(const Settings *settings) void MapgenV5Params::writeParams(Settings *settings) const { - settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, U32_MAX); - settings->setFloat("mgv5_cave_width", cave_width); - settings->setS16("mgv5_large_cave_depth", large_cave_depth); - settings->setS16("mgv5_lava_depth", lava_depth); - settings->setS16("mgv5_cavern_limit", cavern_limit); - settings->setS16("mgv5_cavern_taper", cavern_taper); - settings->setFloat("mgv5_cavern_threshold", cavern_threshold); - settings->setS16("mgv5_dungeon_ymin", dungeon_ymin); - settings->setS16("mgv5_dungeon_ymax", dungeon_ymax); + settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5); + settings->setFloat("mgv5_cave_width", cave_width); + settings->setS16("mgv5_large_cave_depth", large_cave_depth); + settings->setU16("mgv5_small_cave_num_min", small_cave_num_min); + settings->setU16("mgv5_small_cave_num_max", small_cave_num_max); + settings->setU16("mgv5_large_cave_num_min", large_cave_num_min); + settings->setU16("mgv5_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgv5_large_cave_flooded", large_cave_flooded); + settings->setS16("mgv5_cavern_limit", cavern_limit); + settings->setS16("mgv5_cavern_taper", cavern_taper); + settings->setFloat("mgv5_cavern_threshold", cavern_threshold); + settings->setS16("mgv5_dungeon_ymin", dungeon_ymin); + settings->setS16("mgv5_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth); settings->setNoiseParams("mgv5_np_factor", np_factor); @@ -142,6 +154,15 @@ void MapgenV5Params::writeParams(Settings *settings) const } +void MapgenV5Params::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgv5_spflags", flagdesc_mapgen_v5, MGV5_CAVERNS); +} + + +///////////////////////////////////////////////////////////////// + + int MapgenV5::getSpawnLevelAtPoint(v2s16 p) { @@ -240,8 +261,7 @@ void MapgenV5::makeChunk(BlockMakeData *data) m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons and desert temples - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h index 1a3b6d3c3..17bc466f0 100644 --- a/src/mapgen/mapgen_v5.h +++ b/src/mapgen/mapgen_v5.h @@ -31,10 +31,13 @@ extern FlagDesc flagdesc_mapgen_v5[]; struct MapgenV5Params : public MapgenParams { - u32 spflags = MGV5_CAVERNS; float cave_width = 0.09f; s16 large_cave_depth = -256; - s16 lava_depth = -256; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; s16 cavern_limit = -256; s16 cavern_taper = 256; float cavern_threshold = 0.7f; @@ -55,6 +58,7 @@ struct MapgenV5Params : public MapgenParams void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; class MapgenV5 : public MapgenBasic @@ -70,10 +74,6 @@ public: int generateBaseTerrain(); private: - s16 large_cave_depth; - s16 dungeon_ymin; - s16 dungeon_ymax; - Noise *noise_factor; Noise *noise_height; Noise *noise_ground; diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 4e876fc53..653adc8ec 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -190,7 +190,7 @@ void MapgenV6Params::readParams(const Settings *settings) void MapgenV6Params::writeParams(Settings *settings) const { - settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, U32_MAX); + settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6); settings->setFloat("mgv6_freq_desert", freq_desert); settings->setFloat("mgv6_freq_beach", freq_beach); settings->setS16("mgv6_dungeon_ymin", dungeon_ymin); @@ -210,8 +210,16 @@ void MapgenV6Params::writeParams(Settings *settings) const } +void MapgenV6Params::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES | + MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW); +} + + //////////////////////// Some helper functions for the map generator + // Returns Y one under area minimum if not found s16 MapgenV6::find_stone_level(v2s16 p2d) { diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index 7d5229559..d8cdcb26f 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -55,8 +55,6 @@ enum BiomeV6Type struct MapgenV6Params : public MapgenParams { - u32 spflags = MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES | - MGV6_BIOMEBLEND | MGV6_MUDFLOW; float freq_desert = 0.45f; float freq_beach = 0.15f; s16 dungeon_ymin = -31000; @@ -79,6 +77,7 @@ struct MapgenV6Params : public MapgenParams { void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index c9568760f..325c4957a 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2013-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net> -Copyright (C) 2014-2018 paramat +Copyright (C) 2013-2019 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2019 paramat This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -55,26 +55,21 @@ FlagDesc flagdesc_mapgen_v7[] = { MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge) : MapgenBasic(MAPGEN_V7, params, emerge) { - spflags = params->spflags; - mount_zero_level = params->mount_zero_level; - float_mount_density = params->float_mount_density; - float_mount_exponent = params->float_mount_exponent; - floatland_level = params->floatland_level; - shadow_limit = params->shadow_limit; - - cave_width = params->cave_width; - large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; - cavern_limit = params->cavern_limit; - cavern_taper = params->cavern_taper; - cavern_threshold = params->cavern_threshold; - dungeon_ymin = params->dungeon_ymin; - dungeon_ymax = params->dungeon_ymax; - - // This is to avoid a divide-by-zero. - // Parameter will be saved to map_meta.txt in limited form. - params->float_mount_height = std::fmax(params->float_mount_height, 1.0f); - float_mount_height = params->float_mount_height; + spflags = params->spflags; + mount_zero_level = params->mount_zero_level; + + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + dungeon_ymin = params->dungeon_ymin; + dungeon_ymax = params->dungeon_ymax; // 2D noise noise_terrain_base = @@ -88,34 +83,29 @@ MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge) noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); - if (spflags & MGV7_MOUNTAINS) + if (spflags & MGV7_MOUNTAINS) { + // 2D noise noise_mount_height = - new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); - - if (spflags & MGV7_FLOATLANDS) { - noise_floatland_base = - new Noise(¶ms->np_floatland_base, seed, csize.X, csize.Z); - noise_float_base_height = - new Noise(¶ms->np_float_base_height, seed, csize.X, csize.Z); + new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); + // 3D noise, 1 up, 1 down overgeneration + noise_mountain = + new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); } if (spflags & MGV7_RIDGES) { + // 2D noise noise_ridge_uwater = - new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); - // 3D noise, 1 up, 1 down overgeneration + new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); + // 3D noise, 1 up, 1 down overgeneration noise_ridge = - new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); + new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); } - // 3D noise, 1 up, 1 down overgeneration - if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) - noise_mountain = - new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); - // 3D noise, 1 down overgeneration MapgenBasic::np_cave1 = params->np_cave1; MapgenBasic::np_cave2 = params->np_cave2; MapgenBasic::np_cavern = params->np_cavern; + // 3D noise MapgenBasic::np_dungeons = params->np_dungeons; } @@ -128,21 +118,15 @@ MapgenV7::~MapgenV7() delete noise_height_select; delete noise_filler_depth; - if (spflags & MGV7_MOUNTAINS) + if (spflags & MGV7_MOUNTAINS) { delete noise_mount_height; - - if (spflags & MGV7_FLOATLANDS) { - delete noise_floatland_base; - delete noise_float_base_height; + delete noise_mountain; } if (spflags & MGV7_RIDGES) { delete noise_ridge_uwater; delete noise_ridge; } - - if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) - delete noise_mountain; } @@ -154,8 +138,6 @@ MapgenV7Params::MapgenV7Params(): np_filler_depth (0.0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), np_mount_height (256.0, 112.0, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0), np_ridge_uwater (0.0, 1.0, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), - np_floatland_base (-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0), - np_float_base_height (48.0, 24.0, v3f(300, 300, 300), 907, 4, 0.7, 2.0), np_mountain (-0.6, 1.0, v3f(250, 350, 250), 5333, 5, 0.63, 2.0), np_ridge (0.0, 1.0, v3f(100, 100, 100), 6467, 4, 0.75, 2.0), np_cavern (0.0, 1.0, v3f(384, 128, 384), 723, 5, 0.63, 2.0), @@ -172,69 +154,70 @@ void MapgenV7Params::readParams(const Settings *settings) settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level); settings->getFloatNoEx("mgv7_cave_width", cave_width); settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgv7_lava_depth", lava_depth); - settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density); - settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height); - settings->getFloatNoEx("mgv7_float_mount_exponent", float_mount_exponent); - settings->getS16NoEx("mgv7_floatland_level", floatland_level); - settings->getS16NoEx("mgv7_shadow_limit", shadow_limit); + settings->getU16NoEx("mgv7_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgv7_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgv7_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgv7_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgv7_large_cave_flooded", large_cave_flooded); settings->getS16NoEx("mgv7_cavern_limit", cavern_limit); settings->getS16NoEx("mgv7_cavern_taper", cavern_taper); settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold); settings->getS16NoEx("mgv7_dungeon_ymin", dungeon_ymin); settings->getS16NoEx("mgv7_dungeon_ymax", dungeon_ymax); - settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); - settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->getNoiseParams("mgv7_np_height_select", np_height_select); - settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base); - settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height); - settings->getNoiseParams("mgv7_np_mountain", np_mountain); - settings->getNoiseParams("mgv7_np_ridge", np_ridge); - settings->getNoiseParams("mgv7_np_cavern", np_cavern); - settings->getNoiseParams("mgv7_np_cave1", np_cave1); - settings->getNoiseParams("mgv7_np_cave2", np_cave2); - settings->getNoiseParams("mgv7_np_dungeons", np_dungeons); + settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->getNoiseParams("mgv7_np_height_select", np_height_select); + settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->getNoiseParams("mgv7_np_mountain", np_mountain); + settings->getNoiseParams("mgv7_np_ridge", np_ridge); + settings->getNoiseParams("mgv7_np_cavern", np_cavern); + settings->getNoiseParams("mgv7_np_cave1", np_cave1); + settings->getNoiseParams("mgv7_np_cave2", np_cave2); + settings->getNoiseParams("mgv7_np_dungeons", np_dungeons); } void MapgenV7Params::writeParams(Settings *settings) const { - settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX); - settings->setS16("mgv7_mount_zero_level", mount_zero_level); - settings->setFloat("mgv7_cave_width", cave_width); - settings->setS16("mgv7_large_cave_depth", large_cave_depth); - settings->setS16("mgv7_lava_depth", lava_depth); - settings->setFloat("mgv7_float_mount_density", float_mount_density); - settings->setFloat("mgv7_float_mount_height", float_mount_height); - settings->setFloat("mgv7_float_mount_exponent", float_mount_exponent); - settings->setS16("mgv7_floatland_level", floatland_level); - settings->setS16("mgv7_shadow_limit", shadow_limit); - settings->setS16("mgv7_cavern_limit", cavern_limit); - settings->setS16("mgv7_cavern_taper", cavern_taper); - settings->setFloat("mgv7_cavern_threshold", cavern_threshold); - settings->setS16("mgv7_dungeon_ymin", dungeon_ymin); - settings->setS16("mgv7_dungeon_ymax", dungeon_ymax); - - settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); - settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); - settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); - settings->setNoiseParams("mgv7_np_height_select", np_height_select); - settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); - settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); - settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); - settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base); - settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height); - settings->setNoiseParams("mgv7_np_mountain", np_mountain); - settings->setNoiseParams("mgv7_np_ridge", np_ridge); - settings->setNoiseParams("mgv7_np_cavern", np_cavern); - settings->setNoiseParams("mgv7_np_cave1", np_cave1); - settings->setNoiseParams("mgv7_np_cave2", np_cave2); - settings->setNoiseParams("mgv7_np_dungeons", np_dungeons); + settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7); + settings->setS16("mgv7_mount_zero_level", mount_zero_level); + settings->setFloat("mgv7_cave_width", cave_width); + settings->setS16("mgv7_large_cave_depth", large_cave_depth); + settings->setU16("mgv7_small_cave_num_min", small_cave_num_min); + settings->setU16("mgv7_small_cave_num_max", small_cave_num_max); + settings->setU16("mgv7_large_cave_num_min", large_cave_num_min); + settings->setU16("mgv7_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgv7_large_cave_flooded", large_cave_flooded); + settings->setS16("mgv7_cavern_limit", cavern_limit); + settings->setS16("mgv7_cavern_taper", cavern_taper); + settings->setFloat("mgv7_cavern_threshold", cavern_threshold); + settings->setS16("mgv7_dungeon_ymin", dungeon_ymin); + settings->setS16("mgv7_dungeon_ymax", dungeon_ymax); + + settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->setNoiseParams("mgv7_np_height_select", np_height_select); + settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->setNoiseParams("mgv7_np_mountain", np_mountain); + settings->setNoiseParams("mgv7_np_ridge", np_ridge); + settings->setNoiseParams("mgv7_np_cavern", np_cavern); + settings->setNoiseParams("mgv7_np_cave1", np_cave1); + settings->setNoiseParams("mgv7_np_cave2", np_cave2); + settings->setNoiseParams("mgv7_np_dungeons", np_dungeons); +} + + +void MapgenV7Params::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgv7_spflags", flagdesc_mapgen_v7, + MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS); } @@ -360,8 +343,7 @@ void MapgenV7::makeChunk(BlockMakeData *data) m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Generate dungeons - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations @@ -375,10 +357,9 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Update liquids updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); - // Calculate lighting. - // Limit floatland shadow. - bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) && - node_min.Y <= shadow_limit && node_max.Y >= shadow_limit); + // Calculate lighting + // TODO disable in and just below floatlands + bool propagate_shadow = true; if (flags & MG_LIGHT) calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), @@ -447,53 +428,6 @@ bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) } -bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y) -{ - // Make rim 2 nodes thick to match floatland base terrain - float density_gradient = (y >= floatland_level) ? - -std::pow((float)(y - floatland_level) / float_mount_height, - float_mount_exponent) : - -std::pow((float)(floatland_level - 1 - y) / float_mount_height, - float_mount_exponent); - - float floatn = noise_mountain->result[idx_xyz] + float_mount_density; - - return floatn + density_gradient >= 0.0f; -} - - -void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, - int idx_xz) -{ - // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT - s16 base_min = MAX_MAP_GENERATION_LIMIT + 1; - s16 base_max = MAX_MAP_GENERATION_LIMIT; - - float n_base = noise_floatland_base->result[idx_xz]; - if (n_base > 0.0f) { - float n_base_height = - std::fmax(noise_float_base_height->result[idx_xz], 1.0f); - float amp = n_base * n_base_height; - float ridge = n_base_height / 3.0f; - base_min = floatland_level - amp / 1.5f; - - if (amp > ridge * 2.0f) { - // Lake bed - base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f; - } else { - // Hills and ridges - float diff = std::fabs(amp - ridge) / ridge; - // Smooth ridges using the 'smoothstep function' - float smooth_diff = diff * diff * (3.0f - 2.0f * diff); - base_max = floatland_level + ridge - smooth_diff * ridge; - } - } - - *float_base_min = base_min; - *float_base_max = base_max; -} - - int MapgenV7::generateTerrain() { MapNode n_air(CONTENT_AIR); @@ -508,17 +442,9 @@ int MapgenV7::generateTerrain() noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap); noise_height_select->perlinMap2D(node_min.X, node_min.Z); - if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) { - noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); - } - if (spflags & MGV7_MOUNTAINS) { noise_mount_height->perlinMap2D(node_min.X, node_min.Z); - } - - if (spflags & MGV7_FLOATLANDS) { - noise_floatland_base->perlinMap2D(node_min.X, node_min.Z); - noise_float_base_height->perlinMap2D(node_min.X, node_min.Z); + noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); } //// Place nodes @@ -532,13 +458,6 @@ int MapgenV7::generateTerrain() if (surface_y > stone_surface_max_y) stone_surface_max_y = surface_y; - // Get extent of floatland base terrain - // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT - s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1; - s16 float_base_max = MAX_MAP_GENERATION_LIMIT; - if (spflags & MGV7_FLOATLANDS) - floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d); - u32 vi = vm->m_area.index(x, node_min.Y - 1, z); u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); @@ -556,16 +475,8 @@ int MapgenV7::generateTerrain() vm->m_data[vi] = n_stone; // Mountain terrain if (y > stone_surface_max_y) stone_surface_max_y = y; - } else if ((spflags & MGV7_FLOATLANDS) && - ((y >= float_base_min && y <= float_base_max) || - getFloatlandMountainFromMap(index3d, index2d, y))) { - vm->m_data[vi] = n_stone; // Floatland terrain - stone_surface_max_y = node_max.Y; } else if (y <= water_level) { - vm->m_data[vi] = n_water; // Ground level water - } else if ((spflags & MGV7_FLOATLANDS) && - (y >= float_base_max && y <= floatland_level)) { - vm->m_data[vi] = n_water; // Floatland water + vm->m_data[vi] = n_water; } else { vm->m_data[vi] = n_air; } @@ -578,8 +489,8 @@ int MapgenV7::generateTerrain() void MapgenV7::generateRidgeTerrain() { - if (node_max.Y < water_level - 16 || - ((spflags & MGV7_FLOATLANDS) && node_max.Y > shadow_limit)) + // TODO disable river canyons in floatlands + if (node_max.Y < water_level - 16) return; noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h index 50039b16a..0605c5c97 100644 --- a/src/mapgen/mapgen_v7.h +++ b/src/mapgen/mapgen_v7.h @@ -35,17 +35,15 @@ extern FlagDesc flagdesc_mapgen_v7[]; struct MapgenV7Params : public MapgenParams { - u32 spflags = MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS; s16 mount_zero_level = 0; - float float_mount_density = 0.6f; - float float_mount_height = 128.0f; - float float_mount_exponent = 0.75f; - s16 floatland_level = 1280; - s16 shadow_limit = 1024; float cave_width = 0.09f; s16 large_cave_depth = -33; - s16 lava_depth = -256; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; s16 cavern_limit = -256; s16 cavern_taper = 256; float cavern_threshold = 0.7f; @@ -59,8 +57,6 @@ struct MapgenV7Params : public MapgenParams { NoiseParams np_filler_depth; NoiseParams np_mount_height; NoiseParams np_ridge_uwater; - NoiseParams np_floatland_base; - NoiseParams np_float_base_height; NoiseParams np_mountain; NoiseParams np_ridge; NoiseParams np_cavern; @@ -73,6 +69,7 @@ struct MapgenV7Params : public MapgenParams { void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; @@ -90,23 +87,12 @@ public: float baseTerrainLevelFromMap(int index); bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z); bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y); - bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y); - void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz); int generateTerrain(); void generateRidgeTerrain(); private: s16 mount_zero_level; - float float_mount_density; - float float_mount_height; - float float_mount_exponent; - s16 floatland_level; - s16 shadow_limit; - - s16 large_cave_depth; - s16 dungeon_ymin; - s16 dungeon_ymax; Noise *noise_terrain_base; Noise *noise_terrain_alt; @@ -114,8 +100,6 @@ private: Noise *noise_height_select; Noise *noise_mount_height; Noise *noise_ridge_uwater; - Noise *noise_floatland_base; - Noise *noise_float_base_height; Noise *noise_mountain; Noise *noise_ridge; }; diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index d0b36f29b..ff908b7bb 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -67,7 +67,11 @@ MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeManager *emerge) cave_width = params->cave_width; large_cave_depth = params->large_cave_depth; - lava_depth = params->lava_depth; + small_cave_num_min = params->small_cave_num_min; + small_cave_num_max = params->small_cave_num_max; + large_cave_num_min = params->large_cave_num_min; + large_cave_num_max = params->large_cave_num_max; + large_cave_flooded = params->large_cave_flooded; cavern_limit = params->cavern_limit; cavern_taper = params->cavern_taper; cavern_threshold = params->cavern_threshold; @@ -124,18 +128,22 @@ MapgenValleysParams::MapgenValleysParams(): void MapgenValleysParams::readParams(const Settings *settings) { - settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); - settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill); - settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth); - settings->getS16NoEx("mgvalleys_lava_depth", lava_depth); - settings->getU16NoEx("mgvalleys_river_depth", river_depth); - settings->getU16NoEx("mgvalleys_river_size", river_size); - settings->getFloatNoEx("mgvalleys_cave_width", cave_width); - settings->getS16NoEx("mgvalleys_cavern_limit", cavern_limit); - settings->getS16NoEx("mgvalleys_cavern_taper", cavern_taper); - settings->getFloatNoEx("mgvalleys_cavern_threshold", cavern_threshold); - settings->getS16NoEx("mgvalleys_dungeon_ymin", dungeon_ymin); - settings->getS16NoEx("mgvalleys_dungeon_ymax", dungeon_ymax); + settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); + settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill); + settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgvalleys_small_cave_num_min", small_cave_num_min); + settings->getU16NoEx("mgvalleys_small_cave_num_max", small_cave_num_max); + settings->getU16NoEx("mgvalleys_large_cave_num_min", large_cave_num_min); + settings->getU16NoEx("mgvalleys_large_cave_num_max", large_cave_num_max); + settings->getFloatNoEx("mgvalleys_large_cave_flooded", large_cave_flooded); + settings->getU16NoEx("mgvalleys_river_depth", river_depth); + settings->getU16NoEx("mgvalleys_river_size", river_size); + settings->getFloatNoEx("mgvalleys_cave_width", cave_width); + settings->getS16NoEx("mgvalleys_cavern_limit", cavern_limit); + settings->getS16NoEx("mgvalleys_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgvalleys_cavern_threshold", cavern_threshold); + settings->getS16NoEx("mgvalleys_dungeon_ymin", dungeon_ymin); + settings->getS16NoEx("mgvalleys_dungeon_ymax", dungeon_ymax); settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); @@ -154,18 +162,22 @@ void MapgenValleysParams::readParams(const Settings *settings) void MapgenValleysParams::writeParams(Settings *settings) const { - settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys, U32_MAX); - settings->setU16("mgvalleys_altitude_chill", altitude_chill); - settings->setS16("mgvalleys_large_cave_depth", large_cave_depth); - settings->setS16("mgvalleys_lava_depth", lava_depth); - settings->setU16("mgvalleys_river_depth", river_depth); - settings->setU16("mgvalleys_river_size", river_size); - settings->setFloat("mgvalleys_cave_width", cave_width); - settings->setS16("mgvalleys_cavern_limit", cavern_limit); - settings->setS16("mgvalleys_cavern_taper", cavern_taper); - settings->setFloat("mgvalleys_cavern_threshold", cavern_threshold); - settings->setS16("mgvalleys_dungeon_ymin", dungeon_ymin); - settings->setS16("mgvalleys_dungeon_ymax", dungeon_ymax); + settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); + settings->setU16("mgvalleys_altitude_chill", altitude_chill); + settings->setS16("mgvalleys_large_cave_depth", large_cave_depth); + settings->setU16("mgvalleys_small_cave_num_min", small_cave_num_min); + settings->setU16("mgvalleys_small_cave_num_max", small_cave_num_max); + settings->setU16("mgvalleys_large_cave_num_min", large_cave_num_min); + settings->setU16("mgvalleys_large_cave_num_max", large_cave_num_max); + settings->setFloat("mgvalleys_large_cave_flooded", large_cave_flooded); + settings->setU16("mgvalleys_river_depth", river_depth); + settings->setU16("mgvalleys_river_size", river_size); + settings->setFloat("mgvalleys_cave_width", cave_width); + settings->setS16("mgvalleys_cavern_limit", cavern_limit); + settings->setS16("mgvalleys_cavern_taper", cavern_taper); + settings->setFloat("mgvalleys_cavern_threshold", cavern_threshold); + settings->setS16("mgvalleys_dungeon_ymin", dungeon_ymin); + settings->setS16("mgvalleys_dungeon_ymax", dungeon_ymax); settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); @@ -182,6 +194,17 @@ void MapgenValleysParams::writeParams(Settings *settings) const } +void MapgenValleysParams::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgvalleys_spflags", flagdesc_mapgen_valleys, + MGVALLEYS_ALT_CHILL | MGVALLEYS_HUMID_RIVERS | + MGVALLEYS_VARY_RIVER_DEPTH | MGVALLEYS_ALT_DRY); +} + + +///////////////////////////////////////////////////////////////// + + void MapgenValleys::makeChunk(BlockMakeData *data) { // Pre-conditions @@ -248,8 +271,7 @@ void MapgenValleys::makeChunk(BlockMakeData *data) m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); // Dungeon creation - if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin && - full_node_max.Y <= dungeon_ymax) + if (flags & MG_DUNGEONS) generateDungeons(stone_surface_max_y); // Generate the registered decorations @@ -368,7 +390,7 @@ int MapgenValleys::generateTerrain() // Rivers are placed where 'river' is negative if (river < 0.0f) { - // Use the the function -sqrt(1-x^2) which models a circle + // Use the function -sqrt(1-x^2) which models a circle float tr = river / river_size_factor + 1.0f; float depth = (river_depth_bed * std::sqrt(std::fmax(0.0f, 1.0f - tr * tr))); diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h index ab80dc5c9..1aec68842 100644 --- a/src/mapgen/mapgen_valleys.h +++ b/src/mapgen/mapgen_valleys.h @@ -41,15 +41,17 @@ extern FlagDesc flagdesc_mapgen_valleys[]; struct MapgenValleysParams : public MapgenParams { - u32 spflags = MGVALLEYS_ALT_CHILL | MGVALLEYS_HUMID_RIVERS | - MGVALLEYS_VARY_RIVER_DEPTH | MGVALLEYS_ALT_DRY; u16 altitude_chill = 90; u16 river_depth = 4; u16 river_size = 5; float cave_width = 0.09f; s16 large_cave_depth = -33; - s16 lava_depth = 1; + u16 small_cave_num_min = 0; + u16 small_cave_num_max = 0; + u16 large_cave_num_min = 0; + u16 large_cave_num_max = 2; + float large_cave_flooded = 0.5f; s16 cavern_limit = -256; s16 cavern_taper = 192; float cavern_threshold = 0.6f; @@ -74,6 +76,7 @@ struct MapgenValleysParams : public MapgenParams { void readParams(const Settings *settings); void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); }; @@ -96,10 +99,6 @@ private: float river_depth_bed; float river_size_factor; - s16 large_cave_depth; - s16 dungeon_ymin; - s16 dungeon_ymax; - Noise *noise_inter_valley_fill; Noise *noise_inter_valley_slope; Noise *noise_rivers; diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 557b376c3..bf7e79a71 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -846,7 +846,7 @@ void MapNode::deSerialize_pre22(const u8 *source, u8 version) { // In these versions, CONTENT_IGNORE and CONTENT_AIR // are 255 and 254 - // Version 19 is fucked up with sometimes the old values and sometimes not + // Version 19 is messed up with sometimes the old values and sometimes not if(param0 == 255) param0 = CONTENT_IGNORE; else if(param0 == 254) diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 498583df9..431455b76 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -114,9 +114,9 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MODCHANNEL_MSG", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelMsg }, // 0x57 { "TOCLIENT_MODCHANNEL_SIGNAL", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ModChannelSignal }, // 0x58 { "TOCLIENT_NODEMETA_CHANGED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodemetaChanged }, // 0x59 - null_command_handler, - null_command_handler, - null_command_handler, + { "TOCLIENT_SET_SUN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetSun }, // 0x5a + { "TOCLIENT_SET_MOON", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetMoon }, // 0x5b + { "TOCLIENT_SET_STARS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetStars }, // 0x5c null_command_handler, null_command_handler, null_command_handler, diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index b6e9defb0..432fb415e 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/srp.h" #include "tileanimation.h" #include "gettext.h" +#include "skyparams.h" void Client::handleCommand_Deprecated(NetworkPacket* pkt) { @@ -134,6 +135,9 @@ void Client::handleCommand_AuthAccept(NetworkPacket* pkt) << m_recommended_send_interval<<std::endl; // Reply to server + /*~ DO NOT TRANSLATE THIS LITERALLY! + This is a special string which needs to contain the translation's + language code (e.g. "de" for German). */ std::string lang = gettext("LANG_CODE"); if (lang == "LANG_CODE") lang = ""; @@ -778,6 +782,7 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) [25 + len] bool loop [26 + len] f32 fade [30 + len] f32 pitch + [34 + len] bool ephemeral */ s32 server_id; @@ -790,12 +795,14 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) bool loop; float fade = 0.0f; float pitch = 1.0f; + bool ephemeral = false; *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; try { *pkt >> fade; *pkt >> pitch; + *pkt >> ephemeral; } catch (PacketError &e) {}; // Start playing @@ -813,7 +820,6 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) if (cao) pos = cao->getPosition(); client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); - // TODO: Set up sound to move with object break; } default: @@ -821,8 +827,11 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) } if (client_id != -1) { - m_sounds_server_to_client[server_id] = client_id; - m_sounds_client_to_server[client_id] = server_id; + // for ephemeral sounds, server_id is not meaningful + if (!ephemeral) { + m_sounds_server_to_client[server_id] = client_id; + m_sounds_client_to_server[client_id] = server_id; + } if (object_id != 0) m_sounds_to_objects[client_id] = object_id; } @@ -1081,6 +1090,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) v2f offset; v3f world_pos; v2s32 size; + s16 z_index = 0; *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item >> dir >> align >> offset; @@ -1093,6 +1103,11 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) *pkt >> size; } catch(SerializationError &e) {}; + try { + *pkt >> z_index; + } + catch(PacketError &e) {} + ClientEvent *event = new ClientEvent(); event->type = CE_HUDADD; event->hudadd.server_id = server_id; @@ -1108,6 +1123,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt) event->hudadd.offset = new v2f(offset); event->hudadd.world_pos = new v3f(world_pos); event->hudadd.size = new v2s32(size); + event->hudadd.z_index = z_index; m_client_event_queue.push(event); } @@ -1212,49 +1228,140 @@ void Client::handleCommand_HudSetParam(NetworkPacket* pkt) player->hud_hotbar_itemcount = hotbar_itemcount; } else if (param == HUD_PARAM_HOTBAR_IMAGE) { - // If value not empty verify image exists in texture source - if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) { - errorstream << "Server sent wrong Hud hotbar image (sent value: '" - << value << "')" << std::endl; - return; - } player->hotbar_image = value; } else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { - // If value not empty verify image exists in texture source - if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) { - errorstream << "Server sent wrong Hud hotbar selected image (sent value: '" - << value << "')" << std::endl; - return; - } player->hotbar_selected_image = value; } } void Client::handleCommand_HudSetSky(NetworkPacket* pkt) { - std::string datastring(pkt->getString(0), pkt->getSize()); - std::istringstream is(datastring, std::ios_base::binary); + if (m_proto_ver < 39) { + // Handle Protocol 38 and below servers with old set_sky, + // ensuring the classic look is kept. + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); - video::SColor *bgcolor = new video::SColor(readARGB8(is)); - std::string *type = new std::string(deSerializeString(is)); - u16 count = readU16(is); - std::vector<std::string> *params = new std::vector<std::string>; + SkyboxParams skybox; + skybox.bgcolor = video::SColor(readARGB8(is)); + skybox.type = std::string(deSerializeString(is)); + u16 count = readU16(is); + + for (size_t i = 0; i < count; i++) + skybox.textures.emplace_back(deSerializeString(is)); + + skybox.clouds = true; + try { + skybox.clouds = readU8(is); + } catch (...) {} + + // Use default skybox settings: + SkyboxDefaults sky_defaults; + SunParams sun = sky_defaults.getSunDefaults(); + MoonParams moon = sky_defaults.getMoonDefaults(); + StarParams stars = sky_defaults.getStarDefaults(); + + // Fix for "regular" skies, as color isn't kept: + if (skybox.type == "regular") { + skybox.sky_color = sky_defaults.getSkyColorDefaults(); + skybox.tint_type = "default"; + skybox.moon_tint = video::SColor(255, 255, 255, 255); + skybox.sun_tint = video::SColor(255, 255, 255, 255); + } + else { + sun.visible = false; + sun.sunrise_visible = false; + moon.visible = false; + stars.visible = false; + } - for (size_t i = 0; i < count; i++) - params->push_back(deSerializeString(is)); + // Skybox, sun, moon and stars ClientEvents: + ClientEvent *sky_event = new ClientEvent(); + sky_event->type = CE_SET_SKY; + sky_event->set_sky = new SkyboxParams(skybox); + m_client_event_queue.push(sky_event); + + ClientEvent *sun_event = new ClientEvent(); + sun_event->type = CE_SET_SUN; + sun_event->sun_params = new SunParams(sun); + m_client_event_queue.push(sun_event); + + ClientEvent *moon_event = new ClientEvent(); + moon_event->type = CE_SET_MOON; + moon_event->moon_params = new MoonParams(moon); + m_client_event_queue.push(moon_event); + + ClientEvent *star_event = new ClientEvent(); + star_event->type = CE_SET_STARS; + star_event->star_params = new StarParams(stars); + m_client_event_queue.push(star_event); + } else { + SkyboxParams skybox; + u16 texture_count; + std::string texture; + + *pkt >> skybox.bgcolor >> skybox.type >> skybox.clouds >> + skybox.sun_tint >> skybox.moon_tint >> skybox.tint_type; + + if (skybox.type == "skybox") { + *pkt >> texture_count; + for (int i = 0; i < texture_count; i++) { + *pkt >> texture; + skybox.textures.emplace_back(texture); + } + } + else if (skybox.type == "regular") { + *pkt >> skybox.sky_color.day_sky >> skybox.sky_color.day_horizon + >> skybox.sky_color.dawn_sky >> skybox.sky_color.dawn_horizon + >> skybox.sky_color.night_sky >> skybox.sky_color.night_horizon + >> skybox.sky_color.indoors; + } - bool clouds = true; - try { - clouds = readU8(is); - } catch (...) {} + ClientEvent *event = new ClientEvent(); + event->type = CE_SET_SKY; + event->set_sky = new SkyboxParams(skybox); + m_client_event_queue.push(event); + } +} + +void Client::handleCommand_HudSetSun(NetworkPacket *pkt) +{ + SunParams sun; + + *pkt >> sun.visible >> sun.texture>> sun.tonemap + >> sun.sunrise >> sun.sunrise_visible >> sun.scale; ClientEvent *event = new ClientEvent(); - event->type = CE_SET_SKY; - event->set_sky.bgcolor = bgcolor; - event->set_sky.type = type; - event->set_sky.params = params; - event->set_sky.clouds = clouds; + event->type = CE_SET_SUN; + event->sun_params = new SunParams(sun); + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudSetMoon(NetworkPacket *pkt) +{ + MoonParams moon; + + *pkt >> moon.visible >> moon.texture + >> moon.tonemap >> moon.scale; + + ClientEvent *event = new ClientEvent(); + event->type = CE_SET_MOON; + event->moon_params = new MoonParams(moon); + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudSetStars(NetworkPacket *pkt) +{ + StarParams stars; + + *pkt >> stars.visible >> stars.count + >> stars.starcolor >> stars.scale; + + ClientEvent *event = new ClientEvent(); + event->type = CE_SET_STARS; + event->star_params = new StarParams(stars); + m_client_event_queue.push(event); } diff --git a/src/network/connection.cpp b/src/network/connection.cpp index a99e5b145..36124ce3c 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -41,17 +41,23 @@ namespace con /* defines used for debugging and profiling */ /******************************************************************************/ #ifdef NDEBUG -#define LOG(a) a -#define PROFILE(a) + #define LOG(a) a + #define PROFILE(a) #else -/* this mutex is used to achieve log message consistency */ -std::mutex log_message_mutex; -#define LOG(a) \ - { \ - MutexAutoLock loglock(log_message_mutex); \ - a; \ - } -#define PROFILE(a) a + #if 0 + /* this mutex is used to achieve log message consistency */ + std::mutex log_message_mutex; + #define LOG(a) \ + { \ + MutexAutoLock loglock(log_message_mutex); \ + a; \ + } + #else + // Prevent deadlocks until a solution is found after 5.2.0 (TODO) + #define LOG(a) a + #endif + + #define PROFILE(a) a #endif #define PING_TIMEOUT 5.0 @@ -1073,6 +1079,10 @@ bool UDPPeer::processReliableSendCommand( FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error"); } + // DO NOT REMOVE n_queued! It avoids a deadlock of async locked + // 'log_message_mutex' and 'm_list_mutex'. + u32 n_queued = channels[c.channelnum].outgoing_reliables_sent.size(); + LOG(dout_con<<m_connection->getDesc() << " Windowsize exceeded on reliable sending " << c.data.getSize() << " bytes" @@ -1081,7 +1091,7 @@ bool UDPPeer::processReliableSendCommand( << std::endl << "\t\tgot at most : " << packets_available << " packets" << std::endl << "\t\tpackets queued : " - << channels[c.channelnum].outgoing_reliables_sent.size() + << n_queued << std::endl); return false; diff --git a/src/network/connection.h b/src/network/connection.h index d7f1e0fe8..0b12bf701 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -612,16 +612,16 @@ class Peer { struct rttstats { float jitter_min = FLT_MAX; float jitter_max = 0.0f; - float jitter_avg = -1.0f; + float jitter_avg = -2.0f; float min_rtt = FLT_MAX; float max_rtt = 0.0f; - float avg_rtt = -1.0f; + float avg_rtt = -2.0f; rttstats() = default; }; rttstats m_rtt; - float m_last_rtt = -1.0f; + float m_last_rtt = -2.0f; // current usage count unsigned int m_usage = 0; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 5a13c1353..d3799868b 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -200,9 +200,13 @@ with this program; if not, write to the Free Software Foundation, Inc., Unknown inventory serialization fields no longer throw an error Mod-specific formspec version Player FOV override API + "ephemeral" added to TOCLIENT_PLAY_SOUND + PROTOCOL VERSION 39: + Updated set_sky packet + Adds new sun, moon and stars packets */ -#define LATEST_PROTOCOL_VERSION 38 +#define LATEST_PROTOCOL_VERSION 39 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range @@ -228,9 +232,13 @@ with this program; if not, write to the Free Software Foundation, Inc., (too much) FORMSPEC VERSION 2: Forced real coordinates - background[]: 9-slice scaling parameters + background9[]: 9-slice scaling parameters + FORMSPEC VERSION 3: + Formspec elements are drawn in the order of definition + bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor) + box[] and image[] elements enable clipping by default */ -#define FORMSPEC_API_VERSION 2 +#define FORMSPEC_API_VERSION 3 #define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" @@ -446,6 +454,7 @@ enum ToClientCommand s32[3] pos_nodes*10000 u16 object_id u8 loop (bool) + u8 ephemeral (bool) */ TOCLIENT_STOP_SOUND = 0x40, @@ -561,6 +570,7 @@ enum ToClientCommand v2f1000 offset v3f1000 world_pos v2s32 size + s16 z_index */ TOCLIENT_HUDRM = 0x4a, @@ -598,7 +608,8 @@ enum ToClientCommand TOCLIENT_SET_SKY = 0x4f, /* - u8[4] color (ARGB) + Protocol 38: + u8[4] base_color (ARGB) u8 len u8[len] type u16 count @@ -606,6 +617,24 @@ enum ToClientCommand u8 len u8[len] param u8 clouds (boolean) + + Protocol 39: + u8[4] bgcolor (ARGB) + std::string type + int texture_count + std::string[6] param + bool clouds + bool bgcolor_fog + u8[4] day_sky (ARGB) + u8[4] day_horizon (ARGB) + u8[4] dawn_sky (ARGB) + u8[4] dawn_horizon (ARGB) + u8[4] night_sky (ARGB) + u8[4] night_horizon (ARGB) + u8[4] indoors (ARGB) + u8[4] sun_tint (ARGB) + u8[4] moon_tint (ARGB) + std::string tint_type */ TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO = 0x50, @@ -681,6 +710,31 @@ enum ToClientCommand serialized and compressed node metadata */ + TOCLIENT_SET_SUN = 0x5a, + /* + bool visible + std::string texture + std::string tonemap + std::string sunrise + f32 scale + */ + + TOCLIENT_SET_MOON = 0x5b, + /* + bool visible + std::string texture + std::string tonemap + f32 scale + */ + + TOCLIENT_SET_STARS = 0x5c, + /* + bool visible + u32 count + u8[4] starcolor (ARGB) + f32 scale + */ + TOCLIENT_SRP_BYTES_S_B = 0x60, /* Belonging to AUTH_MECHANISM_SRP. diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 8c8d49955..cca2e56ea 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -203,12 +203,12 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MODCHANNEL_MSG", 0, true }, // 0x57 { "TOCLIENT_MODCHANNEL_SIGNAL", 0, true }, // 0x58 { "TOCLIENT_NODEMETA_CHANGED", 0, true }, // 0x59 - null_command_factory, // 0x5A - null_command_factory, // 0x5B - null_command_factory, // 0x5C - null_command_factory, // 0x5D - null_command_factory, // 0x5E - null_command_factory, // 0x5F + { "TOCLIENT_SET_SUN", 0, true }, // 0x5a + { "TOCLIENT_SET_MOON", 0, true }, // 0x5b + { "TOCLIENT_SET_STARS", 0, true }, // 0x5c + null_command_factory, // 0x5d + null_command_factory, // 0x5e + null_command_factory, // 0x5f { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 { "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61 }; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d8fbeebd5..23bcc867f 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -1166,6 +1166,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) u16 wear = pointed_object->punch(dir, &toolcap, playersao, time_from_last_punch); + // Callback may have changed item, so get it again + playersao->getWieldedItem(&selected_item); bool changed = selected_item.addWear(wear, m_itemdef); if (changed) playersao->setWieldedItem(selected_item); @@ -1316,6 +1318,13 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) << pointed_object->getDescription() << std::endl; // Do stuff + if (m_script->item_OnSecondaryUse( + selected_item, playersao, pointed)) { + if (playersao->setWieldedItem(selected_item)) { + SendInventory(playersao, true); + } + } + pointed_object->rightClick(playersao); } else if (m_script->item_OnPlace( selected_item, playersao, pointed)) { @@ -1376,8 +1385,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) actionstream << player->getName() << " activates " << selected_item.name << std::endl; + pointed.type = POINTEDTHING_NOTHING; // can only ever be NOTHING + if (m_script->item_OnSecondaryUse( - selected_item, playersao)) { + selected_item, playersao, pointed)) { if (playersao->setWieldedItem(selected_item)) { SendInventory(playersao, true); } diff --git a/src/noise.cpp b/src/noise.cpp index 255d3faee..9c91a6df4 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -503,23 +503,32 @@ void Noise::setOctaves(int octaves) void Noise::resizeNoiseBuf(bool is3d) { - //maximum possible spread value factor + // Maximum possible spread value factor float ofactor = (np.lacunarity > 1.0) ? pow(np.lacunarity, np.octaves - 1) : np.lacunarity; - // noise lattice point count + // Noise lattice point count // (int)(sz * spread * ofactor) is # of lattice points crossed due to length float num_noise_points_x = sx * ofactor / np.spread.X; float num_noise_points_y = sy * ofactor / np.spread.Y; float num_noise_points_z = sz * ofactor / np.spread.Z; - // protect against obviously invalid parameters + // Protect against obviously invalid parameters if (num_noise_points_x > 1000000000.f || - num_noise_points_y > 1000000000.f || - num_noise_points_z > 1000000000.f) + num_noise_points_y > 1000000000.f || + num_noise_points_z > 1000000000.f) throw InvalidNoiseParamsException(); + // Protect against an octave having a spread < 1, causing broken noise values + if (np.spread.X / ofactor < 1.0f || + np.spread.Y / ofactor < 1.0f || + np.spread.Z / ofactor < 1.0f) { + errorstream << "A noise parameter has too many octaves: " + << np.octaves << " octaves" << std::endl; + throw InvalidNoiseParamsException("A noise parameter has too many octaves"); + } + // + 2 for the two initial endpoints // + 1 for potentially crossing a boundary due to offset size_t nlx = (size_t)std::ceil(num_noise_points_x) + 3; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index a037c5f65..4cf180b18 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -37,7 +37,6 @@ std::string ObjectProperties::dump() os << ", breath_max=" << breath_max; os << ", physical=" << physical; os << ", collideWithObjects=" << collideWithObjects; - os << ", weight=" << weight; os << ", collisionbox=" << PP(collisionbox.MinEdge) << "," << PP(collisionbox.MaxEdge); os << ", visual=" << visual; os << ", mesh=" << mesh; @@ -77,7 +76,7 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, 4); // PROTOCOL_VERSION >= 37 writeU16(os, hp_max); writeU8(os, physical); - writeF32(os, weight); + writeF32(os, 0.f); // Removed property (weight) writeV3F32(os, collisionbox.MinEdge); writeV3F32(os, collisionbox.MaxEdge); writeV3F32(os, selectionbox.MinEdge); @@ -128,7 +127,7 @@ void ObjectProperties::deSerialize(std::istream &is) hp_max = readU16(is); physical = readU8(is); - weight = readF32(is); + readU32(is); // removed property (weight) collisionbox.MinEdge = readV3F32(is); collisionbox.MaxEdge = readV3F32(is); selectionbox.MinEdge = readV3F32(is); diff --git a/src/object_properties.h b/src/object_properties.h index 199182d70..3895f3379 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -31,7 +31,6 @@ struct ObjectProperties u16 breath_max = 0; bool physical = false; bool collideWithObjects = true; - float weight = 5.0f; // Values are BS=1 aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f); aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f); diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index 48e951477..8195bd643 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -58,6 +58,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define ERROR_TARGET warningstream << "Pathfinder: " #endif +#define PATHFINDER_MAX_WAYPOINTS 700 + /******************************************************************************/ /* Class definitions */ /******************************************************************************/ @@ -78,7 +80,7 @@ public: bool valid = false; /**< movement is possible */ int value = 0; /**< cost of movement */ - int direction = 0; /**< y-direction of movement */ + int y_change = 0; /**< change of y position of movement */ bool updated = false; /**< this cost has ben calculated */ }; @@ -117,16 +119,26 @@ public: bool target = false; /**< node is target position */ bool source = false; /**< node is stating position */ int totalcost = -1; /**< cost to move here from starting point */ + int estimated_cost = -1; /**< totalcost + heuristic cost to end */ v3s16 sourcedir; /**< origin of movement for current cost */ v3s16 pos; /**< real position of node */ PathCost directions[4]; /**< cost in different directions */ + bool is_closed = false; /**< for A* search: if true, is in closed list */ + bool is_open = false; /**< for A* search: if true, is in open list */ /* debug values */ bool is_element = false; /**< node is element of path detected */ - char type = 'u'; /**< type of node */ + char type = 'u'; /**< Type of pathfinding node. + * u = unknown + * i = invalid + * s = surface (walkable node) + * - = non-walkable node (e.g. air) above surface + * g = other non-walkable node + */ }; class Pathfinder; +class PathfinderCompareHeuristic; /** Abstract class to manage the map data */ class GridNodeContainer { @@ -224,8 +236,8 @@ private: PathGridnode &getIdxElem(s16 x, s16 y, s16 z); /** - * invert a 3d position - * @param pos 3d position + * invert a 3D position (change sign of coordinates) + * @param pos 3D position * @return pos *-1 */ v3s16 invert(v3s16 pos); @@ -237,38 +249,17 @@ private: */ bool isValidIndex(v3s16 index); - /** - * translate position to float position - * @param pos integer position - * @return float position - */ - v3f tov3f(v3s16 pos); - /* algorithm functions */ /** - * calculate 2d manahttan distance to target on the xz plane + * calculate 2D Manhattan distance to target * @param pos position to calc distance * @return integer distance */ int getXZManhattanDist(v3s16 pos); /** - * get best direction based uppon heuristics - * @param directions list of unchecked directions - * @param g_pos mapnode to start from - * @return direction to check - */ - v3s16 getDirHeuristic(std::vector<v3s16> &directions, PathGridnode &g_pos); - - /** - * build internal data representation of search area - * @return true/false if costmap creation was successfull - */ - bool buildCostmap(); - - /** * calculate cost of movement * @param pos real world position to start movement * @param dir direction to move to @@ -287,22 +278,32 @@ private: bool updateAllCosts(v3s16 ipos, v3s16 srcdir, int current_cost, int level); /** - * recursive try to find a patrh to destionation - * @param ipos position to check next - * @param srcdir positionc checked last time - * @param total_cost cost of moving to ipos - * @param level current recursion depth + * try to find a path to destination using a heuristic function + * to estimate distance to target (A* search algorithm) + * @param isource start position (index pos) + * @param idestination end position (index pos) * @return true/false path to destination has been found */ - bool updateCostHeuristic(v3s16 ipos, v3s16 srcdir, int current_cost, int level); + bool updateCostHeuristic(v3s16 isource, v3s16 idestination); /** - * recursive build a vector containing all nodes from source to destination + * build a vector containing all nodes from destination to source; + * to be called after the node costs have been processed * @param path vector to add nodes to - * @param pos pos to check next - * @param level recursion depth + * @param ipos initial pos to check (index pos) + * @return true/false path has been fully built + */ + bool buildPath(std::vector<v3s16> &path, v3s16 ipos); + + /** + * go downwards from a position until some barrier + * is hit. + * @param pos position from which to go downwards + * @param max_down maximum distance to go downwards + * @return new position after movement; if too far down, + * pos is returned */ - void buildPath(std::vector<v3s16> &path, v3s16 pos, int level); + v3s16 walkDownwards(v3s16 pos, unsigned int max_down); /* variables */ int m_max_index_x = 0; /**< max index of search area in x direction */ @@ -329,6 +330,8 @@ private: ServerEnvironment *m_env = 0; /**< minetest environment pointer */ + friend class PathfinderCompareHeuristic; + #ifdef PATHFINDER_DEBUG /** @@ -378,6 +381,31 @@ private: #endif }; +/** Helper class for the open list priority queue in the A* pathfinder + * to sort the pathfinder nodes by cost. + */ +class PathfinderCompareHeuristic +{ + private: + Pathfinder *myPathfinder; + public: + PathfinderCompareHeuristic(Pathfinder *pf) + { + myPathfinder = pf; + } + bool operator() (v3s16 pos1, v3s16 pos2) { + v3s16 ipos1 = myPathfinder->getIndexPos(pos1); + v3s16 ipos2 = myPathfinder->getIndexPos(pos2); + PathGridnode &g_pos1 = myPathfinder->getIndexElement(ipos1); + PathGridnode &g_pos2 = myPathfinder->getIndexElement(ipos2); + if (!g_pos1.valid) + return false; + if (!g_pos2.valid) + return false; + return g_pos1.estimated_cost > g_pos2.estimated_cost; + } +}; + /******************************************************************************/ /* implementation */ /******************************************************************************/ @@ -401,7 +429,7 @@ std::vector<v3s16> get_path(ServerEnvironment* env, PathCost::PathCost(const PathCost &b) { valid = b.valid; - direction = b.direction; + y_change = b.y_change; value = b.value; updated = b.updated; } @@ -410,7 +438,7 @@ PathCost::PathCost(const PathCost &b) PathCost &PathCost::operator= (const PathCost &b) { valid = b.valid; - direction = b.direction; + y_change = b.y_change; value = b.value; updated = b.updated; @@ -598,10 +626,11 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, //check parameters if (env == 0) { - ERROR_TARGET << "missing environment pointer" << std::endl; + ERROR_TARGET << "Missing environment pointer" << std::endl; return retval; } + //initialization m_searchdistance = searchdistance; m_env = env; m_maxjump = max_jump; @@ -615,6 +644,7 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, m_prefetch = false; } + //calculate boundaries within we're allowed to search int min_x = MYMIN(source.X, destination.X); int max_x = MYMAX(source.X, destination.X); @@ -650,7 +680,37 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, printYdir(); #endif + //fail if source or destination is walkable + const NodeDefManager *ndef = m_env->getGameDef()->ndef(); + MapNode node_at_pos = m_env->getMap().getNode(destination); + if (ndef->get(node_at_pos).walkable) { + VERBOSE_TARGET << "Destination is walkable. " << + "Pos: " << PP(destination) << std::endl; + return retval; + } + node_at_pos = m_env->getMap().getNode(source); + if (ndef->get(node_at_pos).walkable) { + VERBOSE_TARGET << "Source is walkable. " << + "Pos: " << PP(source) << std::endl; + return retval; + } + + //If source pos is hovering above air, drop + //to the first walkable node (up to m_maxdrop). + //All algorithms expect the source pos to be *directly* above + //a walkable node. + v3s16 true_source = v3s16(source); + source = walkDownwards(source, m_maxdrop); + + //If destination pos is hovering above air, go downwards + //to the first walkable node (up to m_maxjump). + //This means a hovering destination pos could be reached + //by a final upwards jump. + v3s16 true_destination = v3s16(destination); + destination = walkDownwards(destination, m_maxjump); + //validate and mark start and end pos + v3s16 StartIndex = getIndexPos(source); v3s16 EndIndex = getIndexPos(destination); @@ -658,13 +718,13 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, PathGridnode &endpos = getIndexElement(EndIndex); if (!startpos.valid) { - VERBOSE_TARGET << "invalid startpos" << + VERBOSE_TARGET << "Invalid startpos " << "Index: " << PP(StartIndex) << "Realpos: " << PP(getRealPos(StartIndex)) << std::endl; return retval; } if (!endpos.valid) { - VERBOSE_TARGET << "invalid stoppos" << + VERBOSE_TARGET << "Invalid stoppos " << "Index: " << PP(EndIndex) << "Realpos: " << PP(getRealPos(EndIndex)) << std::endl; return retval; @@ -676,16 +736,17 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, bool update_cost_retval = false; + //calculate node costs switch (algo) { case PA_DIJKSTRA: update_cost_retval = updateAllCosts(StartIndex, v3s16(0, 0, 0), 0, 0); break; case PA_PLAIN_NP: case PA_PLAIN: - update_cost_retval = updateCostHeuristic(StartIndex, v3s16(0, 0, 0), 0, 0); + update_cost_retval = updateCostHeuristic(StartIndex, EndIndex); break; default: - ERROR_TARGET << "missing PathAlgorithm"<< std::endl; + ERROR_TARGET << "Missing PathAlgorithm" << std::endl; break; } @@ -697,23 +758,50 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, #endif //find path - std::vector<v3s16> path; - buildPath(path, EndIndex, 0); + std::vector<v3s16> index_path; + buildPath(index_path, EndIndex); + //Now we have a path of index positions, + //and it's in reverse. + //The "true" start or end position might be missing + //since those have been given special treatment. #ifdef PATHFINDER_DEBUG - std::cout << "Full index path:" << std::endl; - printPath(path); + std::cout << "Index path:" << std::endl; + printPath(index_path); #endif - - //finalize path + //from here we'll make the final changes to the path std::vector<v3s16> full_path; - full_path.reserve(path.size()); - for (const v3s16 &i : path) { - full_path.push_back(getIndexElement(i).pos); + + //calculate required size + int full_path_size = index_path.size(); + if (source != true_source) { + full_path_size++; + } + if (destination != true_destination) { + full_path_size++; + } + full_path.reserve(full_path_size); + + //manually add true_source to start of path, if needed + if (source != true_source) { + full_path.push_back(true_source); + } + //convert all index positions to "normal" positions and insert + //them into full_path in reverse + std::vector<v3s16>::reverse_iterator rit = index_path.rbegin(); + for (; rit != index_path.rend(); ++rit) { + full_path.push_back(getIndexElement(*rit).pos); + } + //manually add true_destination to end of path, if needed + if (destination != true_destination) { + full_path.push_back(true_destination); } + //Done! We now have a complete path of normal positions. + + #ifdef PATHFINDER_DEBUG - std::cout << "full path:" << std::endl; + std::cout << "Full path:" << std::endl; printPath(full_path); #endif #ifdef PATHFINDER_CALC_TIME @@ -734,7 +822,7 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env, #ifdef PATHFINDER_DEBUG printPathLen(); #endif - ERROR_TARGET << "failed to update cost map"<< std::endl; + INFO_TARGET << "No path found" << std::endl; } @@ -789,15 +877,18 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) return retval; } + //test if the same-height neighbor is suitable if (ndef->get(node_below_pos2).walkable) { + //SUCCESS! retval.valid = true; retval.value = 1; - retval.direction = 0; + retval.y_change = 0; DEBUG_OUT("Pathfinder: "<< PP(pos) << " cost same height found" << std::endl); } else { - v3s16 testpos = pos2 - v3s16(0, -1, 0); + //test if we can fall a couple of nodes (m_maxdrop) + v3s16 testpos = pos2 + v3s16(0, -1, 0); MapNode node_at_pos = m_env->getMap().getNode(testpos); while ((node_at_pos.param0 != CONTENT_IGNORE) && @@ -812,15 +903,16 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) (node_at_pos.param0 != CONTENT_IGNORE) && (ndef->get(node_at_pos).walkable)) { if ((pos2.Y - testpos.Y - 1) <= m_maxdrop) { + //SUCCESS! retval.valid = true; retval.value = 2; //difference of y-pos +1 (target node is ABOVE solid node) - retval.direction = ((testpos.Y - pos2.Y) +1); + retval.y_change = ((testpos.Y - pos2.Y) +1); DEBUG_OUT("Pathfinder cost below height found" << std::endl); } else { INFO_TARGET << "Pathfinder:" - " distance to surface below to big: " + " distance to surface below too big: " << (testpos.Y - pos2.Y) << " max: " << m_maxdrop << std::endl; } @@ -831,29 +923,49 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) } } else { - v3s16 testpos = pos2; - MapNode node_at_pos = m_env->getMap().getNode(testpos); + //test if we can jump upwards (m_maxjump) + + v3s16 targetpos = pos2; // position for jump target + v3s16 jumppos = pos; // position for checking if jumping space is free + MapNode node_target = m_env->getMap().getNode(targetpos); + MapNode node_jump = m_env->getMap().getNode(jumppos); + bool headbanger = false; // true if anything blocks jumppath + + while ((node_target.param0 != CONTENT_IGNORE) && + (ndef->get(node_target).walkable) && + (targetpos.Y < m_limits.MaxEdge.Y)) { + //if the jump would hit any solid node, discard + if ((node_jump.param0 == CONTENT_IGNORE) || + (ndef->get(node_jump).walkable)) { + headbanger = true; + break; + } + targetpos += v3s16(0, 1, 0); + jumppos += v3s16(0, 1, 0); + node_target = m_env->getMap().getNode(targetpos); + node_jump = m_env->getMap().getNode(jumppos); - while ((node_at_pos.param0 != CONTENT_IGNORE) && - (ndef->get(node_at_pos).walkable) && - (testpos.Y < m_limits.MaxEdge.Y)) { - testpos += v3s16(0, 1, 0); - node_at_pos = m_env->getMap().getNode(testpos); + } + //check headbanger one last time + if ((node_jump.param0 == CONTENT_IGNORE) || + (ndef->get(node_jump).walkable)) { + headbanger = true; } - //did we find surface? - if ((testpos.Y <= m_limits.MaxEdge.Y) && - (!ndef->get(node_at_pos).walkable)) { + //did we find surface without banging our head? + if ((!headbanger) && (targetpos.Y <= m_limits.MaxEdge.Y) && + (!ndef->get(node_target).walkable)) { - if (testpos.Y - pos2.Y <= m_maxjump) { + if (targetpos.Y - pos2.Y <= m_maxjump) { + //SUCCESS! retval.valid = true; retval.value = 2; - retval.direction = (testpos.Y - pos2.Y); + retval.y_change = (targetpos.Y - pos2.Y); DEBUG_OUT("Pathfinder cost above found" << std::endl); } else { - DEBUG_OUT("Pathfinder: distance to surface above to big: " - << (testpos.Y - pos2.Y) << " max: " << m_maxjump + DEBUG_OUT("Pathfinder: distance to surface above too big: " + << (targetpos.Y - pos2.Y) << " max: " << m_maxjump << std::endl); } } @@ -929,19 +1041,20 @@ bool Pathfinder::updateAllCosts(v3s16 ipos, bool retval = false; - std::vector<v3s16> directions; - - directions.emplace_back(1,0, 0); - directions.emplace_back(-1,0, 0); - directions.emplace_back(0,0, 1); - directions.emplace_back(0,0,-1); + // the 4 cardinal directions + const static v3s16 directions[4] = { + v3s16(1,0, 0), + v3s16(-1,0, 0), + v3s16(0,0, 1), + v3s16(0,0,-1) + }; - for (v3s16 &direction : directions) { + for (v3s16 direction : directions) { if (direction != srcdir) { PathCost cost = g_pos.getCost(direction); if (cost.valid) { - direction.Y = cost.direction; + direction.Y = cost.y_change; v3s16 ipos2 = ipos + direction; @@ -1006,194 +1119,172 @@ int Pathfinder::getXZManhattanDist(v3s16 pos) return (max_x - min_x) + (max_z - min_z); } -/******************************************************************************/ -v3s16 Pathfinder::getDirHeuristic(std::vector<v3s16> &directions, PathGridnode &g_pos) -{ - int minscore = -1; - v3s16 retdir = v3s16(0, 0, 0); - v3s16 srcpos = g_pos.pos; - DEBUG_OUT("Pathfinder: remaining dirs at beginning:" - << directions.size() << std::endl); - - for (v3s16 &direction : directions) { - v3s16 pos1 = v3s16(srcpos.X + direction.X, 0, srcpos.Z+ direction.Z); - int cur_manhattan = getXZManhattanDist(pos1); - PathCost cost = g_pos.getCost(direction); - - if (!cost.updated) { - cost = calcCost(g_pos.pos, direction); - g_pos.setCost(direction, cost); +/******************************************************************************/ +bool Pathfinder::updateCostHeuristic(v3s16 isource, v3s16 idestination) +{ + // A* search algorithm. + + // The open list contains the pathfinder nodes that still need to be + // checked. The priority queue sorts the pathfinder nodes by + // estimated cost, with lowest cost on the top. + std::priority_queue<v3s16, std::vector<v3s16>, PathfinderCompareHeuristic> + openList(PathfinderCompareHeuristic(this)); + + v3s16 source = getRealPos(isource); + v3s16 destination = getRealPos(idestination); + + // initial position + openList.push(source); + + // the 4 cardinal directions + const static v3s16 directions[4] = { + v3s16(1,0, 0), + v3s16(-1,0, 0), + v3s16(0,0, 1), + v3s16(0,0,-1) + }; + + v3s16 current_pos; + PathGridnode& s_pos = getIndexElement(isource); + s_pos.source = true; + s_pos.totalcost = 0; + + // estimated cost from start to finish + int cur_manhattan = getXZManhattanDist(destination); + s_pos.estimated_cost = cur_manhattan; + + while (!openList.empty()) { + // Pick node with lowest total cost estimate. + // The "cheapest" node is always on top. + current_pos = openList.top(); + openList.pop(); + v3s16 ipos = getIndexPos(current_pos); + + // check if node is inside searchdistance and valid + if (!isValidIndex(ipos)) { + DEBUG_OUT(LVL " Pathfinder: " << PP(current_pos) << + " out of search distance, max=" << PP(m_limits.MaxEdge) << std::endl); + continue; } - if (cost.valid) { - int score = cost.value + cur_manhattan; - - if ((minscore < 0)|| (score < minscore)) { - minscore = score; - retdir = direction; - } + PathGridnode& g_pos = getIndexElement(ipos); + g_pos.is_closed = true; + g_pos.is_open = false; + if (!g_pos.valid) { + continue; } - } - if (retdir != v3s16(0, 0, 0)) { - for (std::vector<v3s16>::iterator iter = directions.begin(); - iter != directions.end(); - ++iter) { - if(*iter == retdir) { - DEBUG_OUT("Pathfinder: removing return direction" << std::endl); - directions.erase(iter); - break; - } + if (current_pos == destination) { + // destination found, terminate + g_pos.target = true; + return true; } - } - else { - DEBUG_OUT("Pathfinder: didn't find any valid direction clearing" - << std::endl); - directions.clear(); - } - DEBUG_OUT("Pathfinder: remaining dirs at end:" << directions.size() - << std::endl); - return retdir; -} - -/******************************************************************************/ -bool Pathfinder::updateCostHeuristic( v3s16 ipos, - v3s16 srcdir, - int current_cost, - int level) -{ - - PathGridnode &g_pos = getIndexElement(ipos); - g_pos.totalcost = current_cost; - g_pos.sourcedir = srcdir; - - level ++; - - //check if target has been found - if (g_pos.target) { - m_min_target_distance = current_cost; - DEBUG_OUT(LVL " Pathfinder: target found!" << std::endl); - return true; - } - bool retval = false; - - std::vector<v3s16> directions; - - directions.emplace_back(1, 0, 0); - directions.emplace_back(-1, 0, 0); - directions.emplace_back(0, 0, 1); - directions.emplace_back(0, 0, -1); - - v3s16 direction = getDirHeuristic(directions, g_pos); - - while (direction != v3s16(0, 0, 0) && (!retval)) { - - if (direction != srcdir) { - PathCost cost = g_pos.getCost(direction); - - if (cost.valid) { - direction.Y = cost.direction; - - v3s16 ipos2 = ipos + direction; - - if (!isValidIndex(ipos2)) { - DEBUG_OUT(LVL " Pathfinder: " << PP(ipos2) << - " out of range, max=" << PP(m_limits.MaxEdge) << std::endl); - direction = getDirHeuristic(directions, g_pos); - continue; - } - - PathGridnode &g_pos2 = getIndexElement(ipos2); + // for this node, check the 4 cardinal directions + for (v3s16 direction_flat : directions) { + int current_totalcost = g_pos.totalcost; - if (!g_pos2.valid) { - VERBOSE_TARGET << LVL "Pathfinder: no data for new position: " - << PP(ipos2) << std::endl; - direction = getDirHeuristic(directions, g_pos); - continue; - } - - assert(cost.value > 0); - - int new_cost = current_cost + cost.value; - - // check if there already is a smaller path - if ((m_min_target_distance > 0) && - (m_min_target_distance < new_cost)) { - DEBUG_OUT(LVL "Pathfinder:" - " already longer than best already found path " - << PP(ipos2) << std::endl); - return false; - } - - if ((g_pos2.totalcost < 0) || - (g_pos2.totalcost > new_cost)) { - DEBUG_OUT(LVL "Pathfinder: updating path at: "<< - PP(ipos2) << " from: " << g_pos2.totalcost << " to "<< - new_cost << " srcdir=" << - PP(invert(direction))<< std::endl); - if (updateCostHeuristic(ipos2, invert(direction), - new_cost, level)) { - retval = true; - } - } - else { - DEBUG_OUT(LVL "Pathfinder:" - " already found shorter path to: " - << PP(ipos2) << std::endl); - } + // get cost from current node to currently checked direction + PathCost cost = g_pos.getCost(direction_flat); + if (!cost.updated) { + cost = calcCost(current_pos, direction_flat); + g_pos.setCost(direction_flat, cost); } - else { - DEBUG_OUT(LVL "Pathfinder:" - " not moving to invalid direction: " - << PP(direction) << std::endl); + // update Y component of direction if neighbor requires jump or fall + v3s16 direction_3d = v3s16(direction_flat); + direction_3d.Y = cost.y_change; + + // get position of true neighbor + v3s16 neighbor = current_pos + direction_3d; + v3s16 ineighbor = getIndexPos(neighbor); + PathGridnode &n_pos = getIndexElement(ineighbor); + + if (cost.valid && !n_pos.is_closed && !n_pos.is_open) { + // heuristic function; estimate cost from neighbor to destination + cur_manhattan = getXZManhattanDist(neighbor); + + // add neighbor to open list + n_pos.sourcedir = invert(direction_3d); + n_pos.totalcost = current_totalcost + cost.value; + n_pos.estimated_cost = current_totalcost + cost.value + cur_manhattan; + n_pos.is_open = true; + openList.push(neighbor); } } - else { - DEBUG_OUT(LVL "Pathfinder:" - " skipping srcdir: " - << PP(direction) << std::endl); - } - direction = getDirHeuristic(directions, g_pos); } - return retval; + // no path found; all possible nodes within searchdistance have been exhausted + return false; } /******************************************************************************/ -void Pathfinder::buildPath(std::vector<v3s16> &path, v3s16 pos, int level) +bool Pathfinder::buildPath(std::vector<v3s16> &path, v3s16 ipos) { - level ++; - if (level > 700) { - ERROR_TARGET - << LVL "Pathfinder: path is too long aborting" << std::endl; - return; - } - - PathGridnode &g_pos = getIndexElement(pos); - if (!g_pos.valid) { - ERROR_TARGET - << LVL "Pathfinder: invalid next pos detected aborting" << std::endl; - return; - } + // The cost calculation should have set a source direction for all relevant nodes. + // To build the path, we go backwards from the destination until we reach the start. + for(u32 waypoints = 1; waypoints++; ) { + if (waypoints > PATHFINDER_MAX_WAYPOINTS) { + ERROR_TARGET << "Pathfinder: buildPath: path is too long (too many waypoints), aborting" << std::endl; + return false; + } + // Insert node into path + PathGridnode &g_pos = getIndexElement(ipos); + if (!g_pos.valid) { + ERROR_TARGET << "Pathfinder: buildPath: invalid next pos detected, aborting" << std::endl; + return false; + } - g_pos.is_element = true; + g_pos.is_element = true; + path.push_back(ipos); + if (g_pos.source) + // start node found, terminate + return true; - //check if source reached - if (g_pos.source) { - path.push_back(pos); - return; + // go to the node from which the pathfinder came + ipos += g_pos.sourcedir; } - buildPath(path, pos + g_pos.sourcedir, level); - path.push_back(pos); + ERROR_TARGET << "Pathfinder: buildPath: no source node found" << std::endl; + return false; } /******************************************************************************/ -v3f Pathfinder::tov3f(v3s16 pos) -{ - return v3f(BS * pos.X, BS * pos.Y, BS * pos.Z); +v3s16 Pathfinder::walkDownwards(v3s16 pos, unsigned int max_down) { + if (max_down == 0) + return pos; + v3s16 testpos = v3s16(pos); + MapNode node_at_pos = m_env->getMap().getNode(testpos); + const NodeDefManager *ndef = m_env->getGameDef()->ndef(); + unsigned int down = 0; + while ((node_at_pos.param0 != CONTENT_IGNORE) && + (!ndef->get(node_at_pos).walkable) && + (testpos.Y > m_limits.MinEdge.Y) && + (down <= max_down)) { + testpos += v3s16(0, -1, 0); + down++; + node_at_pos = m_env->getMap().getNode(testpos); + } + //did we find surface? + if ((testpos.Y >= m_limits.MinEdge.Y) && + (node_at_pos.param0 != CONTENT_IGNORE) && + (ndef->get(node_at_pos).walkable)) { + if (down == 0) { + pos = testpos; + } else if ((down - 1) <= max_down) { + //difference of y-pos +1 (target node is ABOVE solid node) + testpos += v3s16(0, 1, 0); + pos = testpos; + } + else { + VERBOSE_TARGET << "Pos too far above ground: " << + "Index: " << PP(getIndexPos(pos)) << + "Realpos: " << PP(getRealPos(getIndexPos(pos))) << std::endl; + } + } else { + DEBUG_OUT("Pathfinder: no surface found below pos" << std::endl); + } + return pos; } #ifdef PATHFINDER_DEBUG @@ -1268,7 +1359,7 @@ void Pathfinder::printYdir(PathDirections dir) for (int x = 0; x < m_max_index_x; x++) { if (getIdxElem(x, y, z).directions[dir].valid) std::cout << std::setw(4) - << getIdxElem(x, y, z).directions[dir].direction; + << getIdxElem(x, y, z).directions[dir].y_change; else std::cout << std::setw(4) << "-"; } diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 5443b7a2d..1a8fec68c 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -66,6 +66,21 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_cloud_params.height = 120.0f; m_cloud_params.thickness = 16.0f; m_cloud_params.speed = v2f(0.0f, -2.0f); + + // Skybox defaults: + + SkyboxDefaults sky_defaults; + + m_skybox_params.sky_color = sky_defaults.getSkyColorDefaults(); + m_skybox_params.type = "regular"; + m_skybox_params.clouds = true; + m_skybox_params.sun_tint = video::SColor(255, 244, 125, 29); + m_skybox_params.moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor(); + m_skybox_params.tint_type = "default"; + + m_sun_params = sky_defaults.getSunDefaults(); + m_moon_params = sky_defaults.getMoonDefaults(); + m_star_params = sky_defaults.getStarDefaults(); } void RemotePlayer::serializeExtraAttributes(std::string &output) diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 831bfe956..e4209c54f 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "cloudparams.h" +#include "skyparams.h" class PlayerSAO; @@ -83,23 +84,24 @@ public: return hud_hotbar_selected_image; } - void setSky(const video::SColor &bgcolor, const std::string &type, - const std::vector<std::string> ¶ms, bool &clouds) + void setSky(const SkyboxParams &skybox_params) { - m_sky_bgcolor = bgcolor; - m_sky_type = type; - m_sky_params = params; - m_sky_clouds = clouds; + m_skybox_params = skybox_params; } - void getSky(video::SColor *bgcolor, std::string *type, - std::vector<std::string> *params, bool *clouds) - { - *bgcolor = m_sky_bgcolor; - *type = m_sky_type; - *params = m_sky_params; - *clouds = m_sky_clouds; - } + const SkyboxParams &getSkyParams() const { return m_skybox_params; } + + void setSun(const SunParams &sun_params) { m_sun_params = sun_params; } + + const SunParams &getSunParams() const { return m_sun_params; } + + void setMoon(const MoonParams &moon_params) { m_moon_params = moon_params; } + + const MoonParams &getMoonParams() const { return m_moon_params; } + + void setStars(const StarParams &star_params) { m_star_params = star_params; } + + const StarParams &getStarParams() const { return m_star_params; } void setCloudParams(const CloudParams &cloud_params) { @@ -164,12 +166,12 @@ private: std::string hud_hotbar_image = ""; std::string hud_hotbar_selected_image = ""; - std::string m_sky_type; - video::SColor m_sky_bgcolor; - std::vector<std::string> m_sky_params; - bool m_sky_clouds; - CloudParams m_cloud_params; + SkyboxParams m_skybox_params; + SunParams m_sun_params; + MoonParams m_moon_params; + StarParams m_star_params; + session_t m_peer_id = PEER_ID_INEXISTENT; }; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index cb0253c32..accbb1a87 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -83,7 +83,7 @@ void read_item_definition(lua_State* L, int index, getboolfield(L, index, "liquids_pointable", def.liquids_pointable); warn_if_field_exists(L, index, "tool_digging_properties", - "Deprecated; use tool_capabilities"); + "Obsolete; use tool_capabilities"); lua_getfield(L, index, "tool_capabilities"); if(lua_istable(L, -1)){ @@ -208,8 +208,6 @@ void read_object_properties(lua_State *L, int index, getboolfield(L, -1, "physical", prop->physical); getboolfield(L, -1, "collide_with_objects", prop->collideWithObjects); - getfloatfield(L, -1, "weight", prop->weight); - lua_getfield(L, -1, "collisionbox"); bool collisionbox_defined = lua_istable(L, -1); if (collisionbox_defined) @@ -340,8 +338,6 @@ void push_object_properties(lua_State *L, ObjectProperties *prop) lua_setfield(L, -2, "physical"); lua_pushboolean(L, prop->collideWithObjects); lua_setfield(L, -2, "collide_with_objects"); - lua_pushnumber(L, prop->weight); - lua_setfield(L, -2, "weight"); push_aabb3f(L, prop->collisionbox); lua_setfield(L, -2, "collisionbox"); push_aabb3f(L, prop->selectionbox); @@ -643,19 +639,19 @@ ContentFeatures read_content_features(lua_State *L, int index) warningstream << "Node " << f.name.c_str() << " has a palette, but not a suitable paramtype2." << std::endl; - // Warn about some deprecated fields + // Warn about some obsolete fields warn_if_field_exists(L, index, "wall_mounted", - "Deprecated; use paramtype2 = 'wallmounted'"); + "Obsolete; use paramtype2 = 'wallmounted'"); warn_if_field_exists(L, index, "light_propagates", - "Deprecated; determined from paramtype"); + "Obsolete; determined from paramtype"); warn_if_field_exists(L, index, "dug_item", - "Deprecated; use 'drop' field"); + "Obsolete; use 'drop' field"); warn_if_field_exists(L, index, "extra_dug_item", - "Deprecated; use 'drop' field"); + "Obsolete; use 'drop' field"); warn_if_field_exists(L, index, "extra_dug_item_rarity", - "Deprecated; use 'drop' field"); + "Obsolete; use 'drop' field"); warn_if_field_exists(L, index, "metadata_name", - "Deprecated; use on_add and metadata callbacks"); + "Obsolete; use on_add and metadata callbacks"); // True for all ground-like things like stone and mud, false for eg. trees getboolfield(L, index, "is_ground_content", f.is_ground_content); @@ -1023,6 +1019,7 @@ void read_server_sound_params(lua_State *L, int index, params.max_hear_distance = BS*getfloatfield_default(L, index, "max_hear_distance", params.max_hear_distance/BS); getboolfield(L, index, "loop", params.loop); + getstringfield(L, index, "exclude_player", params.exclude_player); } } @@ -1851,11 +1848,13 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->size = lua_istable(L, -1) ? read_v2s32(L, -1) : v2s32(); lua_pop(L, 1); - elem->name = getstringfield_default(L, 2, "name", ""); - elem->text = getstringfield_default(L, 2, "text", ""); - elem->number = getintfield_default(L, 2, "number", 0); - elem->item = getintfield_default(L, 2, "item", 0); - elem->dir = getintfield_default(L, 2, "direction", 0); + elem->name = getstringfield_default(L, 2, "name", ""); + elem->text = getstringfield_default(L, 2, "text", ""); + elem->number = getintfield_default(L, 2, "number", 0); + elem->item = getintfield_default(L, 2, "item", 0); + elem->dir = getintfield_default(L, 2, "direction", 0); + elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, + getintfield_default(L, 2, "z_index", 0))); // Deprecated, only for compatibility's sake if (elem->dir == 0) @@ -1921,6 +1920,9 @@ void push_hud_element(lua_State *L, HudElement *elem) push_v3f(L, elem->world_pos); lua_setfield(L, -2, "world_pos"); + + lua_pushnumber(L, elem->z_index); + lua_setfield(L, -2, "z_index"); } HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) @@ -1978,6 +1980,10 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value) elem->size = read_v2s32(L, 4); *value = &elem->size; break; + case HUD_STAT_Z_INDEX: + elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, luaL_checknumber(L, 4))); + *value = &elem->z_index; + break; } return stat; } diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index b9d6f0494..3c2f75641 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -335,6 +335,28 @@ video::SColor read_ARGB8(lua_State *L, int index) return color; } +bool is_color_table(lua_State *L, int index) +{ + // Check whole table in case of missing ColorSpec keys: + // This check does not remove the checked value from the stack. + // Only update the value if we know we have a valid ColorSpec key pair. + if (!lua_istable(L, index)) + return false; + + bool is_color_table = false; + lua_getfield(L, index, "r"); + if (!is_color_table) + is_color_table = lua_isnumber(L, -1); + lua_getfield(L, index, "g"); + if (!is_color_table) + is_color_table = lua_isnumber(L, -1); + lua_getfield(L, index, "b"); + if (!is_color_table) + is_color_table = lua_isnumber(L, -1); + lua_pop(L, 3); // b, g, r values + return is_color_table; +} + aabb3f read_aabb3f(lua_State *L, int index, f32 scale) { aabb3f box; diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index f84494c8d..9620bf75a 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -110,6 +110,7 @@ v2s32 read_v2s32 (lua_State *L, int index); video::SColor read_ARGB8 (lua_State *L, int index); bool read_color (lua_State *L, int index, video::SColor *color); +bool is_color_table (lua_State *L, int index); aabb3f read_aabb3f (lua_State *L, int index, f32 scale); 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 f792b6218..b19af9f82 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -47,7 +47,7 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f) /* * Note that we can't get tracebacks for LUA_ERRMEM or LUA_ERRERR (without * hacking Lua internals). For LUA_ERRMEM, this is because memory errors will - * not execute the the error handler, and by the time lua_pcall returns the + * not execute the error handler, and by the time lua_pcall returns the * execution stack will have already been unwound. For LUA_ERRERR, there was * another error while trying to generate a backtrace from a LUA_ERRRUN. It is * presumed there is an error with the internal Lua state and thus not possible @@ -135,7 +135,27 @@ void script_run_callbacks_f(lua_State *L, int nargs, lua_remove(L, error_handler); } -void log_deprecated(lua_State *L, const std::string &message) +static void script_log(lua_State *L, const std::string &message, + std::ostream &log_to, bool do_error, 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 << ")"; + } else { + log_to << "(at ?:?)"; + } + log_to << std::endl; + + if (do_error) + script_error(L, LUA_ERRRUN, NULL, NULL); + else + infostream << script_get_backtrace(L) << std::endl; +} + +void log_deprecated(lua_State *L, const std::string &message, int stack_depth) { static bool configured = false; static bool do_log = false; @@ -152,24 +172,6 @@ void log_deprecated(lua_State *L, const std::string &message) } } - if (do_log) { - warningstream << message; - if (L) { // L can be NULL if we get called from scripting_game.cpp - lua_Debug ar; - - if (!lua_getstack(L, 2, &ar)) - FATAL_ERROR_IF(!lua_getstack(L, 1, &ar), "lua_getstack() failed"); - FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - warningstream << " (at " << ar.short_src << ":" << ar.currentline << ")"; - } - warningstream << std::endl; - - if (L) { - if (do_error) - script_error(L, LUA_ERRRUN, NULL, NULL); - else - infostream << script_get_backtrace(L) << std::endl; - } - } + if (do_log) + script_log(L, message, warningstream, do_error, stack_depth); } - diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index d2131d1ad..d8cf3fe76 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -103,4 +103,6 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f); void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); void script_run_callbacks_f(lua_State *L, int nargs, RunCallbacksMode mode, const char *fxn); -void log_deprecated(lua_State *L, const std::string &message); + +void log_deprecated(lua_State *L, const std::string &message, + int stack_depth=1); diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index caa335d76..ecb1ba39b 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -197,18 +197,22 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name) { ModNameStorer mod_name_storer(getStack(), mod_name); - const std::string *init_filename = getClient()->getModFile(mod_name + ":init.lua"); - const std::string display_filename = mod_name + ":init.lua"; - if(init_filename == NULL) - throw ModError("Mod:\"" + mod_name + "\" lacks init.lua"); + sanity_check(m_type == ScriptingType::Client); - verbosestream << "Loading and running script " << display_filename << std::endl; + const std::string init_filename = mod_name + ":init.lua"; + const std::string chunk_name = "@" + init_filename; + + const std::string *contents = getClient()->getModFile(init_filename); + if (!contents) + throw ModError("Mod \"" + mod_name + "\" lacks init.lua"); + + verbosestream << "Loading and running script " << chunk_name << std::endl; lua_State *L = getStack(); int error_handler = PUSH_ERROR_HANDLER(L); - bool ok = ScriptApiSecurity::safeLoadFile(L, init_filename->c_str(), display_filename.c_str()); + bool ok = ScriptApiSecurity::safeLoadString(L, *contents, chunk_name.c_str()); if (ok) ok = !lua_pcall(L, 0, 0, error_handler); if (!ok) { @@ -329,6 +333,20 @@ void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) #endif } +/* + * How ObjectRefs are handled in Lua: + * When an active object is created, an ObjectRef is created on the Lua side + * and stored in core.object_refs[id]. + * Methods that require an ObjectRef to a certain object retrieve it from that + * table instead of creating their own.(*) + * When an active object is removed, the existing ObjectRef is invalidated + * using ::set_null() and removed from the core.object_refs table. + * (*) An exception to this are NULL ObjectRefs and anonymous ObjectRefs + * for objects without ID. + * It's unclear what the latter are needed for and their use is problematic + * since we lose control over the ref and the contained pointer. + */ + void ScriptApiBase::addObjectReference(ServerActiveObject *cobj) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index cbdfcf1b1..24955cefc 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -115,7 +115,8 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, return true; } -bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, ServerActiveObject *user) +bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, + ServerActiveObject *user, const PointedThing &pointed) { SCRIPTAPI_PRECHECKHEADER @@ -126,8 +127,6 @@ bool ScriptApiItem::item_OnSecondaryUse(ItemStack &item, ServerActiveObject *use LuaItemStack::create(L, item); objectrefGetOrCreate(L, user); - PointedThing pointed; - pointed.type = POINTEDTHING_NOTHING; pushPointedThing(pointed); PCALL_RES(lua_pcall(L, 3, 1, error_handler)); if (!lua_isnil(L, -1)) { diff --git a/src/script/cpp_api/s_item.h b/src/script/cpp_api/s_item.h index 6c7f286a9..25a3501f9 100644 --- a/src/script/cpp_api/s_item.h +++ b/src/script/cpp_api/s_item.h @@ -42,7 +42,7 @@ public: bool item_OnUse(ItemStack &item, ServerActiveObject *user, const PointedThing &pointed); bool item_OnSecondaryUse(ItemStack &item, - ServerActiveObject *user); + ServerActiveObject *user, const PointedThing &pointed); bool item_OnCraft(ItemStack &item, ServerActiveObject *user, const InventoryList *old_craft_grid, const InventoryLocation &craft_inv); bool item_CraftPredict(ItemStack &item, ServerActiveObject *user, @@ -51,7 +51,6 @@ public: protected: friend class LuaItemStack; friend class ModApiItemMod; - friend class LuaRaycast; bool getItemCallback(const char *name, const char *callbackname, const v3s16 *p = nullptr); /*! diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index b90b3aa2c..b5abcfb5d 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -372,14 +372,16 @@ bool ScriptApiSecurity::isSecure(lua_State *L) return secure; } - -#define CHECK_FILE_ERR(ret, fp) \ - if (ret) { \ - lua_pushfstring(L, "%s: %s", path, strerror(errno)); \ - if (fp) std::fclose(fp); \ - return false; \ +bool ScriptApiSecurity::safeLoadString(lua_State *L, const std::string &code, const char *chunk_name) +{ + if (code.size() > 0 && code[0] == LUA_SIGNATURE[0]) { + lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + return false; } - + if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) + return false; + return true; +} bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char *display_name) { @@ -406,68 +408,49 @@ bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char int c = std::getc(fp); if (c == '#') { // Skip the first line - while ((c = std::getc(fp)) != EOF && c != '\n'); - if (c == '\n') c = std::getc(fp); + while ((c = std::getc(fp)) != EOF && c != '\n') {} + if (c == '\n') + std::getc(fp); start = std::ftell(fp); } - if (c == LUA_SIGNATURE[0]) { - lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); - std::fclose(fp); - if (path) { - delete [] chunk_name; - } - return false; - } - // Read the file int ret = std::fseek(fp, 0, SEEK_END); if (ret) { lua_pushfstring(L, "%s: %s", path, strerror(errno)); - std::fclose(fp); if (path) { + std::fclose(fp); delete [] chunk_name; } return false; } size_t size = std::ftell(fp) - start; - char *code = new char[size]; + std::string code(size, '\0'); ret = std::fseek(fp, start, SEEK_SET); if (ret) { lua_pushfstring(L, "%s: %s", path, strerror(errno)); - std::fclose(fp); - delete [] code; if (path) { + std::fclose(fp); delete [] chunk_name; } return false; } - size_t num_read = std::fread(code, 1, size, fp); - if (path) { + size_t num_read = std::fread(&code[0], 1, size, fp); + if (path) std::fclose(fp); - } if (num_read != size) { lua_pushliteral(L, "Error reading file to load."); - delete [] code; - if (path) { + if (path) delete [] chunk_name; - } return false; } - if (luaL_loadbuffer(L, code, size, chunk_name)) { - delete [] code; - return false; - } - - delete [] code; - - if (path) { + bool result = safeLoadString(L, code, chunk_name); + if (path) delete [] chunk_name; - } - return true; + return result; } @@ -628,14 +611,9 @@ int ScriptApiSecurity::sl_g_load(lua_State *L) code += std::string(buf, len); lua_pop(L, 1); // Pop return value } - if (code[0] == LUA_SIGNATURE[0]) { + if (!safeLoadString(L, code, chunk_name)) { lua_pushnil(L); - lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); - return 2; - } - if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) { - lua_pushnil(L); - lua_insert(L, lua_gettop(L) - 1); + lua_insert(L, -2); return 2; } return 1; @@ -649,16 +627,19 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); lua_pop(L, 1); + // Client implementation if (script->getType() == ScriptingType::Client) { - std::string display_path = readParam<std::string>(L, 1); - const std::string *path = script->getClient()->getModFile(display_path); - if (!path) { - std::string error_msg = "Coudln't find script called:" + display_path; + std::string path = readParam<std::string>(L, 1); + const std::string *contents = script->getClient()->getModFile(path); + if (!contents) { + std::string error_msg = "Coudln't find script called: " + path; lua_pushnil(L); lua_pushstring(L, error_msg.c_str()); return 2; } - if (!safeLoadFile(L, path->c_str(), display_path.c_str())) { + + std::string chunk_name = "@" + path; + if (!safeLoadString(L, *contents, chunk_name.c_str())) { lua_pushnil(L); lua_insert(L, -2); return 2; @@ -666,6 +647,8 @@ int ScriptApiSecurity::sl_g_loadfile(lua_State *L) return 1; } #endif + + // Server implementation const char *path = NULL; if (lua_isstring(L, 1)) { path = lua_tostring(L, 1); @@ -694,15 +677,11 @@ int ScriptApiSecurity::sl_g_loadstring(lua_State *L) size_t size; const char *code = lua_tolstring(L, 1, &size); + std::string code_s(code, size); - if (size > 0 && code[0] == LUA_SIGNATURE[0]) { + if (!safeLoadString(L, code_s, chunk_name)) { lua_pushnil(L); - lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); - return 2; - } - if (luaL_loadbuffer(L, code, size, chunk_name)) { - lua_pushnil(L); - lua_insert(L, lua_gettop(L) - 1); + lua_insert(L, -2); return 2; } return 1; diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index c31aff26b..73e763548 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -50,6 +50,8 @@ public: void initializeSecurityClient(); // Checks if the Lua state has been secured static bool isSecure(lua_State *L); + // Loads a string as Lua code safely (doesn't allow bytecode). + static bool safeLoadString(lua_State *L, const std::string &code, const char *chunk_name); // Loads a file as Lua code safely (doesn't allow bytecode). static bool safeLoadFile(lua_State *L, const char *path, const char *display_name = NULL); // Checks if mods are allowed to read (and optionally write) to the path diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp index 80071b3b8..9c1470284 100644 --- a/src/script/lua_api/l_camera.cpp +++ b/src/script/lua_api/l_camera.cpp @@ -108,11 +108,10 @@ int LuaCamera::l_get_pos(lua_State *L) int LuaCamera::l_get_offset(lua_State *L) { - Camera *camera = getobject(L, 1); - if (!camera) - return 0; + LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer(); + sanity_check(player); - push_v3s16(L, camera->getOffset()); + push_v3f(L, player->getEyeOffset() / BS); return 1; } diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 6345fc75f..fba182492 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -36,12 +36,47 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "nodedef.h" +#define checkCSMRestrictionFlag(flag) \ + ( getClient(L)->checkCSMRestrictionFlag(CSMRestrictionFlags::flag) ) + +// Not the same as FlagDesc, which contains an `u32 flag` +struct CSMFlagDesc { + const char *name; + u64 flag; +}; + +/* + FIXME: This should eventually be moved somewhere else + It also needs to be kept in sync with the definition of CSMRestrictionFlags + in network/networkprotocol.h +*/ +const static CSMFlagDesc flagdesc_csm_restriction[] = { + {"load_client_mods", CSM_RF_LOAD_CLIENT_MODS}, + {"chat_messages", CSM_RF_CHAT_MESSAGES}, + {"read_itemdefs", CSM_RF_READ_ITEMDEFS}, + {"read_nodedefs", CSM_RF_READ_NODEDEFS}, + {"lookup_nodes", CSM_RF_LOOKUP_NODES}, + {"read_playerinfo", CSM_RF_READ_PLAYERINFO}, + {NULL, 0} +}; + +// get_current_modname() int ModApiClient::l_get_current_modname(lua_State *L) { lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); return 1; } +// get_modpath(modname) +int ModApiClient::l_get_modpath(lua_State *L) +{ + std::string modname = readParam<std::string>(L, 1); + // Client mods use a virtual filesystem, see Client::scanModSubfolder() + std::string path = modname + ":"; + lua_pushstring(L, path.c_str()); + return 1; +} + // get_last_run_mod() int ModApiClient::l_get_last_run_mod(lua_State *L) { @@ -95,11 +130,8 @@ int ModApiClient::l_send_chat_message(lua_State *L) // If server disabled this API, discard - // clang-format off - if (getClient(L)->checkCSMRestrictionFlag( - CSMRestrictionFlags::CSM_RF_CHAT_MESSAGES)) + if (checkCSMRestrictionFlag(CSM_RF_CHAT_MESSAGES)) return 0; - // clang-format on std::string message = luaL_checkstring(L, 1); getClient(L)->sendChatMessage(utf8_to_wide(message)); @@ -116,12 +148,8 @@ int ModApiClient::l_clear_out_chat_queue(lua_State *L) // get_player_names() int ModApiClient::l_get_player_names(lua_State *L) { - // clang-format off - if (getClient(L)->checkCSMRestrictionFlag( - CSMRestrictionFlags::CSM_RF_READ_PLAYERINFO)) { + if (checkCSMRestrictionFlag(CSM_RF_READ_PLAYERINFO)) return 0; - } - // clang-format on const std::list<std::string> &plist = getClient(L)->getConnectedPlayerNames(); lua_createtable(L, plist.size(), 0); @@ -190,7 +218,7 @@ int ModApiClient::l_get_node_or_nil(lua_State *L) // Do it bool pos_ok; - MapNode n = getClient(L)->getNode(pos, &pos_ok); + MapNode n = getClient(L)->CSMGetNode(pos, &pos_ok); if (pos_ok) { // Return node pushnode(L, n, getClient(L)->ndef()); @@ -202,9 +230,18 @@ int ModApiClient::l_get_node_or_nil(lua_State *L) int ModApiClient::l_get_language(lua_State *L) { - char *locale = setlocale(LC_ALL, ""); +#ifdef _WIN32 + char *locale = setlocale(LC_ALL, NULL); +#else + char *locale = setlocale(LC_MESSAGES, NULL); +#endif + std::string lang = gettext("LANG_CODE"); + if (lang == "LANG_CODE") + lang = ""; + lua_pushstring(L, locale); - return 1; + lua_pushstring(L, lang.c_str()); + return 2; } int ModApiClient::l_get_wielded_item(lua_State *L) @@ -297,11 +334,8 @@ int ModApiClient::l_get_item_def(lua_State *L) IItemDefManager *idef = gdef->idef(); assert(idef); - // clang-format off - if (getClient(L)->checkCSMRestrictionFlag( - CSMRestrictionFlags::CSM_RF_READ_ITEMDEFS)) + if (checkCSMRestrictionFlag(CSM_RF_READ_ITEMDEFS)) return 0; - // clang-format on if (!lua_isstring(L, 1)) return 0; @@ -328,11 +362,8 @@ int ModApiClient::l_get_node_def(lua_State *L) if (!lua_isstring(L, 1)) return 0; - // clang-format off - if (getClient(L)->checkCSMRestrictionFlag( - CSMRestrictionFlags::CSM_RF_READ_NODEDEFS)) + if (checkCSMRestrictionFlag(CSM_RF_READ_NODEDEFS)) return 0; - // clang-format on std::string name = readParam<std::string>(L, 1); const ContentFeatures &cf = ndef->get(ndef->getId(name)); @@ -362,9 +393,23 @@ int ModApiClient::l_get_builtin_path(lua_State *L) return 1; } +// get_csm_restrictions() +int ModApiClient::l_get_csm_restrictions(lua_State *L) +{ + u64 flags = getClient(L)->getCSMRestrictionFlags(); + const CSMFlagDesc *flagdesc = flagdesc_csm_restriction; + + lua_newtable(L); + for (int i = 0; flagdesc[i].name; i++) { + setboolfield(L, -1, flagdesc[i].name, !!(flags & flagdesc[i].flag)); + } + return 1; +} + void ModApiClient::Initialize(lua_State *L, int top) { API_FCT(get_current_modname); + API_FCT(get_modpath); API_FCT(print); API_FCT(display_chat_message); API_FCT(send_chat_message); @@ -387,4 +432,5 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(get_privilege_list); API_FCT(get_builtin_path); API_FCT(get_language); + API_FCT(get_csm_restrictions); } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 0d3e6b106..6d1f70b1d 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -30,6 +30,9 @@ private: // get_current_modname() static int l_get_current_modname(lua_State *L); + // get_modpath(modname) + static int l_get_modpath(lua_State *L); + // print(text) static int l_print(lua_State *L); @@ -93,6 +96,9 @@ private: // get_builtin_path() static int l_get_builtin_path(lua_State *L); + // get_csm_restrictions() + static int l_get_csm_restrictions(lua_State *L); + public: static void Initialize(lua_State *L, int top); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index a56b1cb0b..3169fa4cf 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -140,17 +140,20 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n) int LuaRaycast::l_next(lua_State *L) { MAP_LOCK_REQUIRED; - - ScriptApiItem *script = getScriptApi<ScriptApiItem>(L); GET_ENV_PTR; + bool csm = false; +#ifndef SERVER + csm = getClient(L) != nullptr; +#endif + LuaRaycast *o = checkobject(L, 1); PointedThing pointed; env->continueRaycast(&o->state, &pointed); if (pointed.type == POINTEDTHING_NOTHING) lua_pushnil(L); else - script->pushPointedThing(pointed, true); + push_pointed_thing(L, pointed, csm, true); return 1; } @@ -637,6 +640,31 @@ int ModApiEnvMod::l_add_item(lua_State *L) return 1; } +// get_connected_players() +int ModApiEnvMod::l_get_connected_players(lua_State *L) +{ + ServerEnvironment *env = (ServerEnvironment *) getEnv(L); + if (!env) { + log_deprecated(L, "Calling get_connected_players() at mod load time" + " is deprecated"); + lua_createtable(L, 0, 0); + return 1; + } + + lua_createtable(L, env->getPlayerCount(), 0); + u32 i = 0; + for (RemotePlayer *player : env->getPlayers()) { + if (player->getPeerId() == PEER_ID_INEXISTENT) + continue; + PlayerSAO *sao = player->getPlayerSAO(); + if (sao && !sao->isGone()) { + getScriptApiBase(L)->objectrefGetOrCreate(L, sao); + lua_rawseti(L, -2, ++i); + } + } + return 1; +} + // get_player_by_name(name) int ModApiEnvMod::l_get_player_by_name(lua_State *L) { @@ -644,16 +672,12 @@ int ModApiEnvMod::l_get_player_by_name(lua_State *L) // Do it const char *name = luaL_checkstring(L, 1); - RemotePlayer *player = dynamic_cast<RemotePlayer *>(env->getPlayer(name)); - if (player == NULL){ - lua_pushnil(L); - return 1; - } + RemotePlayer *player = env->getPlayer(name); + if (!player || player->getPeerId() == PEER_ID_INEXISTENT) + return 0; PlayerSAO *sao = player->getPlayerSAO(); - if(sao == NULL){ - lua_pushnil(L); - return 1; - } + if (!sao || sao->isGone()) + return 0; // Put player on stack getScriptApiBase(L)->objectrefGetOrCreate(L, sao); return 1; @@ -769,11 +793,8 @@ int ModApiEnvMod::l_find_node_near(lua_State *L) #ifndef SERVER // Client API limitations - if (getClient(L) && - getClient(L)->checkCSMRestrictionFlag( - CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) { - radius = std::max<int>(radius, getClient(L)->getCSMNodeRangeLimit()); - } + if (getClient(L)) + radius = getClient(L)->CSMClampRadius(pos, radius); #endif for (int d = start_radius; d <= radius; d++) { @@ -796,11 +817,20 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) { GET_ENV_PTR; - const NodeDefManager *ndef = getServer(L)->ndef(); v3s16 minp = read_v3s16(L, 1); v3s16 maxp = read_v3s16(L, 2); sortBoxVerticies(minp, maxp); +#ifndef SERVER + const NodeDefManager *ndef = getClient(L) ? getClient(L)->ndef() : getServer(L)->ndef(); + if (getClient(L)) { + minp = getClient(L)->CSMClampPos(minp); + maxp = getClient(L)->CSMClampPos(maxp); + } +#else + const NodeDefManager *ndef = getServer(L)->ndef(); +#endif + v3s16 cube = maxp - minp + 1; // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { @@ -864,11 +894,20 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) GET_ENV_PTR; - const NodeDefManager *ndef = getServer(L)->ndef(); v3s16 minp = read_v3s16(L, 1); v3s16 maxp = read_v3s16(L, 2); sortBoxVerticies(minp, maxp); +#ifndef SERVER + const NodeDefManager *ndef = getClient(L) ? getClient(L)->ndef() : getServer(L)->ndef(); + if (getClient(L)) { + minp = getClient(L)->CSMClampPos(minp); + maxp = getClient(L)->CSMClampPos(maxp); + } +#else + const NodeDefManager *ndef = getServer(L)->ndef(); +#endif + v3s16 cube = maxp - minp + 1; // Volume limit equal to 8 default mapchunks, (80 * 2) ^ 3 = 4,096,000 if ((u64)cube.X * (u64)cube.Y * (u64)cube.Z > 4096000) { @@ -1161,7 +1200,7 @@ int ModApiEnvMod::l_find_path(lua_State *L) unsigned int max_jump = luaL_checkint(L, 4); unsigned int max_drop = luaL_checkint(L, 5); PathAlgorithm algo = PA_PLAIN_NP; - if (!lua_isnil(L, 6)) { + if (!lua_isnoneornil(L, 6)) { std::string algorithm = luaL_checkstring(L,6); if (algorithm == "A*") @@ -1301,6 +1340,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(find_nodes_with_meta); API_FCT(get_meta); API_FCT(get_node_timer); + API_FCT(get_connected_players); API_FCT(get_player_by_name); API_FCT(get_objects_inside_radius); API_FCT(set_timeofday); @@ -1329,9 +1369,14 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) void ModApiEnvMod::InitializeClient(lua_State *L, int top) { + API_FCT(get_node_light); API_FCT(get_timeofday); - API_FCT(get_day_count); API_FCT(get_node_max_level); API_FCT(get_node_level); + API_FCT(find_nodes_with_meta); API_FCT(find_node_near); + API_FCT(find_nodes_in_area); + API_FCT(find_nodes_in_area_under_air); + API_FCT(line_of_sight); + API_FCT(raycast); } diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 68a4eae50..ac2f8b588 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -101,6 +101,9 @@ private: // pos = {x=num, y=num, z=num} static int l_add_item(lua_State *L); + // get_connected_players() + static int l_get_connected_players(lua_State *L); + // get_player_by_name(name) static int l_get_player_by_name(lua_State *L); diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index f9708b560..0c174feca 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -414,7 +414,9 @@ ItemStack& LuaItemStack::getItem() int LuaItemStack::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; - ItemStack item = read_item(L, 1, getGameDef(L)->idef()); + ItemStack item; + if (!lua_isnone(L, 1)) + item = read_item(L, 1, getGameDef(L)->idef()); LuaItemStack *o = new LuaItemStack(item); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); @@ -610,9 +612,11 @@ int ModApiItemMod::l_get_content_id(lua_State *L) std::string name = luaL_checkstring(L, 1); const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager(); - content_t c = ndef->getId(name); + content_t content_id; + if (!ndef->getId(name, content_id)) + throw LuaError("Unknown node: " + name); - lua_pushinteger(L, c); + lua_pushinteger(L, content_id); return 1; /* number of results */ } diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index 3e14e48e4..821b1cb66 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -78,7 +78,7 @@ int LuaLocalPlayer::l_is_attached(lua_State *L) { LocalPlayer *player = getobject(L, 1); - lua_pushboolean(L, player->isAttached); + lua_pushboolean(L, player->getParent() != nullptr); return 1; } @@ -157,7 +157,7 @@ int LuaLocalPlayer::l_get_override_pos(lua_State *L) { LocalPlayer *player = getobject(L, 1); - push_v3f(L, player->overridePosition); + push_v3f(L, player->getPosition()); return 1; } diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 2e0cba8dd..cb0d6ac95 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -879,7 +879,7 @@ int ModApiMapgen::l_set_mapgen_params(lua_State *L) settingsmgr->setMapSetting("chunksize", readParam<std::string>(L, -1), true); warn_if_field_exists(L, 1, "flagmask", - "Deprecated: flags field now includes unset flags."); + "Obsolete: flags field now includes unset flags."); lua_getfield(L, 1, "flags"); if (lua_isstring(L, -1)) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index efdb345c9..23ed1ffe0 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -60,6 +60,8 @@ LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref) return NULL; if (obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) return NULL; + if (obj->isGone()) + return NULL; return (LuaEntitySAO*)obj; } @@ -70,6 +72,8 @@ PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) return NULL; if (obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) return NULL; + if (obj->isGone()) + return NULL; return (PlayerSAO*)obj; } @@ -1059,6 +1063,9 @@ int ObjectRef::l_get_luaentity(lua_State *L) int ObjectRef::l_is_player_connected(lua_State *L) { NO_MAP_LOCK_REQUIRED; + // This method was once added for a bugfix, but never documented + log_deprecated(L, "is_player_connected is undocumented and " + "will be removed in a future release"); ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); lua_pushboolean(L, (player != NULL && player->getPeerId() != PEER_ID_INEXISTENT)); @@ -1701,42 +1708,157 @@ int ObjectRef::l_hud_get_hotbar_selected_image(lua_State *L) return 1; } -// set_sky(self, bgcolor, type, list, clouds = true) +// set_sky(self, {base_color=, type=, textures=, clouds=, sky_colors={}}) int ObjectRef::l_set_sky(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + if (!player) return 0; - video::SColor bgcolor(255,255,255,255); - read_color(L, 2, &bgcolor); + bool is_colorspec = is_color_table(L, 2); - std::string type = luaL_checkstring(L, 3); + SkyboxParams skybox_params = player->getSkyParams(); + if (lua_istable(L, 2) && !is_colorspec) { + lua_getfield(L, 2, "base_color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &skybox_params.bgcolor); + lua_pop(L, 1); - std::vector<std::string> params; - if (lua_istable(L, 4)) { - lua_pushnil(L); - while (lua_next(L, 4) != 0) { - // key at index -2 and value at index -1 - if (lua_isstring(L, -1)) - params.emplace_back(readParam<std::string>(L, -1)); - else - params.emplace_back(""); - // removes value, keeps key for next iteration + lua_getfield(L, 2, "type"); + if (!lua_isnil(L, -1)) + skybox_params.type = luaL_checkstring(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 2, "textures"); + skybox_params.textures.clear(); + if (lua_istable(L, -1) && skybox_params.type == "skybox") { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Key is at index -2 and value at index -1 + skybox_params.textures.emplace_back(readParam<std::string>(L, -1)); + // Removes the value, but keeps the key for iteration + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + /* + We want to avoid crashes, so we're checking even if we're not using them. + However, we want to ensure that the skybox can be set to nil when + using "regular" or "plain" skybox modes as textures aren't needed. + */ + + if (skybox_params.textures.size() != 6 && skybox_params.textures.size() > 0) + throw LuaError("Skybox expects 6 textures!"); + + skybox_params.clouds = getboolfield_default(L, 2, + "clouds", skybox_params.clouds); + + lua_getfield(L, 2, "sky_color"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "day_sky"); + read_color(L, -1, &skybox_params.sky_color.day_sky); + lua_pop(L, 1); + + lua_getfield(L, -1, "day_horizon"); + read_color(L, -1, &skybox_params.sky_color.day_horizon); + lua_pop(L, 1); + + lua_getfield(L, -1, "dawn_sky"); + read_color(L, -1, &skybox_params.sky_color.dawn_sky); + lua_pop(L, 1); + + lua_getfield(L, -1, "dawn_horizon"); + read_color(L, -1, &skybox_params.sky_color.dawn_horizon); + lua_pop(L, 1); + + lua_getfield(L, -1, "night_sky"); + read_color(L, -1, &skybox_params.sky_color.night_sky); + lua_pop(L, 1); + + lua_getfield(L, -1, "night_horizon"); + read_color(L, -1, &skybox_params.sky_color.night_horizon); + lua_pop(L, 1); + + lua_getfield(L, -1, "indoors"); + read_color(L, -1, &skybox_params.sky_color.indoors); + lua_pop(L, 1); + + // Prevent flickering clouds at dawn/dusk: + skybox_params.sun_tint = video::SColor(255, 255, 255, 255); + lua_getfield(L, -1, "fog_sun_tint"); + read_color(L, -1, &skybox_params.sun_tint); + lua_pop(L, 1); + + skybox_params.moon_tint = video::SColor(255, 255, 255, 255); + lua_getfield(L, -1, "fog_moon_tint"); + read_color(L, -1, &skybox_params.moon_tint); + lua_pop(L, 1); + + lua_getfield(L, -1, "fog_tint_type"); + if (!lua_isnil(L, -1)) + skybox_params.tint_type = luaL_checkstring(L, -1); + lua_pop(L, 1); + + // Because we need to leave the "sky_color" table. lua_pop(L, 1); } - } + } else { + // Handle old set_sky calls, and log deprecated: + log_deprecated(L, "Deprecated call to set_sky, please check lua_api.txt"); + + // Fix sun, moon and stars showing when classic textured skyboxes are used + SunParams sun_params = player->getSunParams(); + MoonParams moon_params = player->getMoonParams(); + StarParams star_params = player->getStarParams(); + + // Prevent erroneous background colors + skybox_params.bgcolor = video::SColor(255, 255, 255, 255); + read_color(L, 2, &skybox_params.bgcolor); + + skybox_params.type = luaL_checkstring(L, 3); + + // Preserve old behaviour of the sun, moon and stars + // when using the old set_sky call. + if (skybox_params.type == "regular") { + sun_params.visible = true; + sun_params.sunrise_visible = true; + moon_params.visible = true; + star_params.visible = true; + } else { + sun_params.visible = false; + sun_params.sunrise_visible = false; + moon_params.visible = false; + star_params.visible = false; + } - if (type == "skybox" && params.size() != 6) - throw LuaError("skybox expects 6 textures"); + skybox_params.textures.clear(); + if (lua_istable(L, 4)) { + lua_pushnil(L); + while (lua_next(L, 4) != 0) { + // Key at index -2, and value at index -1 + if (lua_isstring(L, -1)) + skybox_params.textures.emplace_back(readParam<std::string>(L, -1)); + else + skybox_params.textures.emplace_back(""); + // Remove the value, keep the key for the next iteration + lua_pop(L, 1); + } + } + if (skybox_params.type == "skybox" && skybox_params.textures.size() != 6) + throw LuaError("Skybox expects 6 textures."); - bool clouds = true; - if (lua_isboolean(L, 5)) - clouds = readParam<bool>(L, 5); + skybox_params.clouds = true; + if (lua_isboolean(L, 5)) + skybox_params.clouds = readParam<bool>(L, 5); - getServer(L)->setSky(player, bgcolor, type, params, clouds); + getServer(L)->setSun(player, sun_params); + getServer(L)->setMoon(player, moon_params); + getServer(L)->setStars(player, star_params); + } + getServer(L)->setSky(player, skybox_params); lua_pushboolean(L, true); return 1; } @@ -1747,28 +1869,226 @@ int ObjectRef::l_get_sky(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); RemotePlayer *player = getplayer(ref); - if (player == NULL) + + if (!player) return 0; - video::SColor bgcolor(255, 255, 255, 255); - std::string type; - std::vector<std::string> params; - bool clouds; + SkyboxParams skybox_params; + skybox_params = player->getSkyParams(); - player->getSky(&bgcolor, &type, ¶ms, &clouds); - type = type.empty() ? "regular" : type; + push_ARGB8(L, skybox_params.bgcolor); + lua_pushlstring(L, skybox_params.type.c_str(), skybox_params.type.size()); - push_ARGB8(L, bgcolor); - lua_pushlstring(L, type.c_str(), type.size()); lua_newtable(L); s16 i = 1; - for (const std::string ¶m : params) { - lua_pushlstring(L, param.c_str(), param.size()); + for (const std::string& texture : skybox_params.textures) { + lua_pushlstring(L, texture.c_str(), texture.size()); lua_rawseti(L, -2, i++); } - lua_pushboolean(L, clouds); + lua_pushboolean(L, skybox_params.clouds); return 4; } +// get_sky_color(self) +int ObjectRef::l_get_sky_color(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + + if (!player) + return 0; + + const SkyboxParams& skybox_params = player->getSkyParams(); + + lua_newtable(L); + if (skybox_params.type == "regular") { + push_ARGB8(L, skybox_params.sky_color.day_sky); + lua_setfield(L, -2, "day_sky"); + push_ARGB8(L, skybox_params.sky_color.day_horizon); + lua_setfield(L, -2, "day_horizon"); + push_ARGB8(L, skybox_params.sky_color.dawn_sky); + lua_setfield(L, -2, "dawn_sky"); + push_ARGB8(L, skybox_params.sky_color.dawn_horizon); + lua_setfield(L, -2, "dawn_horizon"); + push_ARGB8(L, skybox_params.sky_color.night_sky); + lua_setfield(L, -2, "night_sky"); + push_ARGB8(L, skybox_params.sky_color.night_horizon); + lua_setfield(L, -2, "night_horizon"); + push_ARGB8(L, skybox_params.sky_color.indoors); + lua_setfield(L, -2, "indoors"); + } + push_ARGB8(L, skybox_params.sun_tint); + lua_setfield(L, -2, "sun_tint"); + push_ARGB8(L, skybox_params.moon_tint); + lua_setfield(L, -2, "moon_tint"); + lua_pushstring(L, skybox_params.tint_type.c_str()); + lua_setfield(L, -2, "tint_type"); + return 1; +} + +// set_sun(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) +int ObjectRef::l_set_sun(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + + if (!lua_istable(L, 2)) + return 0; + + SunParams sun_params = player->getSunParams(); + + sun_params.visible = getboolfield_default(L, 2, + "visible", sun_params.visible); + sun_params.texture = getstringfield_default(L, 2, + "texture", sun_params.texture); + sun_params.tonemap = getstringfield_default(L, 2, + "tonemap", sun_params.tonemap); + sun_params.sunrise = getstringfield_default(L, 2, + "sunrise", sun_params.sunrise); + sun_params.sunrise_visible = getboolfield_default(L, 2, + "sunrise_visible", sun_params.sunrise_visible); + sun_params.scale = getfloatfield_default(L, 2, + "scale", sun_params.scale); + + getServer(L)->setSun(player, sun_params); + lua_pushboolean(L, true); + return 1; +} + +//get_sun(self) +int ObjectRef::l_get_sun(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + const SunParams &sun_params = player->getSunParams(); + + lua_newtable(L); + lua_pushboolean(L, sun_params.visible); + lua_setfield(L, -2, "visible"); + lua_pushstring(L, sun_params.texture.c_str()); + lua_setfield(L, -2, "texture"); + lua_pushstring(L, sun_params.tonemap.c_str()); + lua_setfield(L, -2, "tonemap"); + lua_pushstring(L, sun_params.sunrise.c_str()); + lua_setfield(L, -2, "sunrise"); + lua_pushboolean(L, sun_params.sunrise_visible); + lua_setfield(L, -2, "sunrise_visible"); + lua_pushnumber(L, sun_params.scale); + lua_setfield(L, -2, "scale"); + + return 1; +} + +// set_moon(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) +int ObjectRef::l_set_moon(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + if (!lua_istable(L, 2)) + return 0; + + MoonParams moon_params = player->getMoonParams(); + + moon_params.visible = getboolfield_default(L, 2, + "visible", moon_params.visible); + moon_params.texture = getstringfield_default(L, 2, + "texture", moon_params.texture); + moon_params.tonemap = getstringfield_default(L, 2, + "tonemap", moon_params.tonemap); + moon_params.scale = getfloatfield_default(L, 2, + "scale", moon_params.scale); + + getServer(L)->setMoon(player, moon_params); + lua_pushboolean(L, true); + return 1; +} + +// get_moon(self) +int ObjectRef::l_get_moon(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + const MoonParams &moon_params = player->getMoonParams(); + + lua_newtable(L); + lua_pushboolean(L, moon_params.visible); + lua_setfield(L, -2, "visible"); + lua_pushstring(L, moon_params.texture.c_str()); + lua_setfield(L, -2, "texture"); + lua_pushstring(L, moon_params.tonemap.c_str()); + lua_setfield(L, -2, "tonemap"); + lua_pushnumber(L, moon_params.scale); + lua_setfield(L, -2, "scale"); + + return 1; +} + +// set_stars(self, {visible, count=, starcolor=, rotation=, scale=}) +int ObjectRef::l_set_stars(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + if (!lua_istable(L, 2)) + return 0; + + StarParams star_params = player->getStarParams(); + + star_params.visible = getboolfield_default(L, 2, + "visible", star_params.visible); + star_params.count = getintfield_default(L, 2, + "count", star_params.count); + + lua_getfield(L, 2, "star_color"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &star_params.starcolor); + lua_pop(L, 1); + + star_params.scale = getfloatfield_default(L, 2, + "scale", star_params.scale); + + getServer(L)->setStars(player, star_params); + lua_pushboolean(L, true); + return 1; +} + +// get_stars(self) +int ObjectRef::l_get_stars(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + RemotePlayer *player = getplayer(ref); + if (!player) + return 0; + const StarParams &star_params = player->getStarParams(); + + lua_newtable(L); + lua_pushboolean(L, star_params.visible); + lua_setfield(L, -2, "visible"); + lua_pushnumber(L, star_params.count); + lua_setfield(L, -2, "count"); + push_ARGB8(L, star_params.starcolor); + lua_setfield(L, -2, "star_color"); + lua_pushnumber(L, star_params.scale); + lua_setfield(L, -2, "scale"); + + return 1; +} + // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) int ObjectRef::l_set_clouds(lua_State *L) { @@ -2025,6 +2345,13 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, hud_get_hotbar_selected_image), luamethod(ObjectRef, set_sky), luamethod(ObjectRef, get_sky), + luamethod(ObjectRef, get_sky_color), + luamethod(ObjectRef, set_sun), + luamethod(ObjectRef, get_sun), + luamethod(ObjectRef, set_moon), + luamethod(ObjectRef, get_moon), + luamethod(ObjectRef, set_stars), + luamethod(ObjectRef, get_stars), luamethod(ObjectRef, set_clouds), luamethod(ObjectRef, get_clouds), luamethod(ObjectRef, override_day_night_ratio), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index e817e1d33..a75c59fd9 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -324,12 +324,33 @@ private: // hud_get_hotbar_selected_image(self) static int l_hud_get_hotbar_selected_image(lua_State *L); - // set_sky(self, bgcolor, type, list, clouds = true) + // set_sky({base_color=, type=, textures=, clouds=, sky_colors={}}) static int l_set_sky(lua_State *L); // get_sky(self) static int l_get_sky(lua_State *L); + // get_sky_color(self) + static int l_get_sky_color(lua_State* L); + + // set_sun(self, {visible, texture=, tonemap=, sunrise=, rotation=, scale=}) + static int l_set_sun(lua_State *L); + + // get_sun(self) + static int l_get_sun(lua_State *L); + + // set_moon(self, {visible, texture=, tonemap=, rotation, scale=}) + static int l_set_moon(lua_State *L); + + // get_moon(self) + static int l_get_moon(lua_State *L); + + // set_stars(self, {visible, count=, starcolor=, rotation, scale=}) + static int l_set_stars(lua_State *L); + + // get_stars(self) + static int l_get_stars(lua_State *L); + // set_clouds(self, {density=, color=, ambient=, height=, thickness=, speed=}) static int l_set_clouds(lua_State *L); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 7c083e652..00e849cdf 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -429,7 +429,7 @@ int ModApiServer::l_get_worldpath(lua_State *L) return 1; } -// sound_play(spec, parameters) +// sound_play(spec, parameters, [ephemeral]) int ModApiServer::l_sound_play(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -437,8 +437,14 @@ int ModApiServer::l_sound_play(lua_State *L) read_soundspec(L, 1, spec); ServerSoundParams params; read_server_sound_params(L, 2, params); - s32 handle = getServer(L)->playSound(spec, params); - lua_pushinteger(L, handle); + bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3); + if (ephemeral) { + getServer(L)->playSound(spec, params, true); + lua_pushnil(L); + } else { + s32 handle = getServer(L)->playSound(spec, params); + lua_pushinteger(L, handle); + } return 1; } @@ -446,7 +452,7 @@ int ModApiServer::l_sound_play(lua_State *L) int ModApiServer::l_sound_stop(lua_State *L) { NO_MAP_LOCK_REQUIRED; - int handle = luaL_checkinteger(L, 1); + s32 handle = luaL_checkinteger(L, 1); getServer(L)->stopSound(handle); return 0; } diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index cc2c73789..33eb02392 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_internal.h" #include "cpp_api/s_security.h" +#include "util/string.h" // FlagDesc #include "settings.h" #include "noise.h" #include "log.h" @@ -128,6 +129,29 @@ int LuaSettings::l_get_np_group(lua_State *L) return 1; } +int LuaSettings::l_get_flags(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaSettings *o = checkobject(L, 1); + std::string key = std::string(luaL_checkstring(L, 2)); + + u32 flags = 0; + auto flagdesc = o->m_settings->getFlagDescFallback(key); + if (o->m_settings->getFlagStrNoEx(key, flags, flagdesc)) { + lua_newtable(L); + int table = lua_gettop(L); + for (size_t i = 0; flagdesc[i].name; ++i) { + lua_pushboolean(L, flags & flagdesc[i].flag); + lua_setfield(L, table, flagdesc[i].name); + } + lua_pushvalue(L, table); + } else { + lua_pushnil(L); + } + + return 1; +} + // set(self, key, value) int LuaSettings::l_set(lua_State* L) { @@ -305,6 +329,7 @@ const luaL_Reg LuaSettings::methods[] = { luamethod(LuaSettings, get), luamethod(LuaSettings, get_bool), luamethod(LuaSettings, get_np_group), + luamethod(LuaSettings, get_flags), luamethod(LuaSettings, set), luamethod(LuaSettings, set_bool), luamethod(LuaSettings, set_np_group), diff --git a/src/script/lua_api/l_settings.h b/src/script/lua_api/l_settings.h index dcf39a89e..67d7b342b 100644 --- a/src/script/lua_api/l_settings.h +++ b/src/script/lua_api/l_settings.h @@ -42,6 +42,9 @@ private: // get_np_group(self, key) -> noiseparam static int l_get_np_group(lua_State *L); + // get_flags(self, key) -> key/value table + static int l_get_flags(lua_State *L); + // set(self, key, value) static int l_set(lua_State *L); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index a58c3a196..ae3e5df3d 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -59,7 +59,7 @@ int ModApiUtil::l_log(lua_State *L) std::string name = luaL_checkstring(L, 1); text = luaL_checkstring(L, 2); if (name == "deprecated") { - log_deprecated(L, text); + log_deprecated(L, text, 2); return 0; } level = Logger::stringToLevel(name); diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index c3e0ca373..1288b1df7 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -69,6 +69,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) { LuaItemStack::Register(L); ItemStackMetaRef::Register(L); + LuaRaycast::Register(L); StorageRef::Register(L); LuaMinimap::Register(L); NodeMetaRef::RegisterClient(L); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 2204c6884..cbf229640 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -121,8 +121,3 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); } - -void log_deprecated(const std::string &message) -{ - log_deprecated(NULL, message); -} diff --git a/src/script/scripting_server.h b/src/script/scripting_server.h index 88cea143c..bf06ab197 100644 --- a/src/script/scripting_server.h +++ b/src/script/scripting_server.h @@ -51,5 +51,3 @@ public: private: void InitializeModApi(lua_State *L, int top); }; - -void log_deprecated(const std::string &message); diff --git a/src/serialization.cpp b/src/serialization.cpp index 36ddb467c..310604f54 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -99,7 +99,7 @@ void compressZlib(const std::string &data, std::ostream &os, int level) compressZlib((u8*)data.c_str(), data.size(), os, level); } -void decompressZlib(std::istream &is, std::ostream &os) +void decompressZlib(std::istream &is, std::ostream &os, size_t limit) { z_stream z; const s32 bufsize = 16384; @@ -108,6 +108,7 @@ void decompressZlib(std::istream &is, std::ostream &os) int status = 0; int ret; int bytes_read = 0; + int bytes_written = 0; int input_buffer_len = 0; z.zalloc = Z_NULL; @@ -124,8 +125,20 @@ void decompressZlib(std::istream &is, std::ostream &os) for(;;) { + int output_size = bufsize; z.next_out = (Bytef*)output_buffer; - z.avail_out = bufsize; + z.avail_out = output_size; + + if (limit) { + int limit_remaining = limit - bytes_written; + if (limit_remaining <= 0) { + // we're aborting ahead of time - throw an error? + break; + } + if (limit_remaining < output_size) { + z.avail_out = output_size = limit_remaining; + } + } if(z.avail_in == 0) { @@ -153,10 +166,11 @@ void decompressZlib(std::istream &is, std::ostream &os) zerr(status); throw SerializationError("decompressZlib: inflate failed"); } - int count = bufsize - z.avail_out; + int count = output_size - z.avail_out; //dstream<<"count="<<count<<std::endl; if(count) os.write(output_buffer, count); + bytes_written += count; if(status == Z_STREAM_END) { //dstream<<"Z_STREAM_END"<<std::endl; diff --git a/src/serialization.h b/src/serialization.h index 7f8b83382..f399983c4 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -87,7 +87,7 @@ inline bool ser_ver_supported(s32 v) { void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level = -1); void compressZlib(const std::string &data, std::ostream &os, int level = -1); -void decompressZlib(std::istream &is, std::ostream &os); +void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); // These choose between zlib and a self-made one according to version void compress(const SharedBuffer<u8> &data, std::ostream &os, u8 version); diff --git a/src/server.cpp b/src/server.cpp index d914beead..239ddd92e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1018,16 +1018,15 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) // Send Breath SendPlayerBreath(playersao); - Address addr = getPeerAddress(player->getPeerId()); - std::string ip_str = addr.serializeString(); - actionstream<<player->getName() <<" [" << ip_str << "] joins game. " << std::endl; /* Print out action */ { + Address addr = getPeerAddress(player->getPeerId()); + std::string ip_str = addr.serializeString(); const std::vector<std::string> &names = m_clients.getPlayerNames(); - actionstream << player->getName() << " joins game. List of players: "; + actionstream << player->getName() << " [" << ip_str << "] joins game. List of players: "; for (const std::string &name : names) { actionstream << name << " "; @@ -1283,7 +1282,7 @@ bool Server::getClientInfo( *major = client->getMajor(); *minor = client->getMinor(); *patch = client->getPatch(); - *vers_string = client->getPatch(); + *vers_string = client->getFull(); m_clients.unlock(); @@ -1654,7 +1653,8 @@ 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->align << form->offset << form->world_pos << form->size + << form->z_index; Send(&pkt); } @@ -1717,17 +1717,62 @@ void Server::SendHUDSetParam(session_t peer_id, u16 param, const std::string &va Send(&pkt); } -void Server::SendSetSky(session_t peer_id, const video::SColor &bgcolor, - const std::string &type, const std::vector<std::string> ¶ms, - bool &clouds) +void Server::SendSetSky(session_t peer_id, const SkyboxParams ¶ms) { NetworkPacket pkt(TOCLIENT_SET_SKY, 0, peer_id); - pkt << bgcolor << type << (u16) params.size(); - for (const std::string ¶m : params) - pkt << param; + // Handle prior clients here + if (m_clients.getProtocolVersion(peer_id) < 39) { + pkt << params.bgcolor << params.type << (u16) params.textures.size(); + + for (const std::string& texture : params.textures) + pkt << texture; + + pkt << params.clouds; + } else { // Handle current clients and future clients + pkt << params.bgcolor << params.type + << params.clouds << params.sun_tint + << params.moon_tint << params.tint_type; + + if (params.type == "skybox") { + pkt << (u16) params.textures.size(); + for (const std::string &texture : params.textures) + pkt << texture; + } else if (params.type == "regular") { + pkt << params.sky_color.day_sky << params.sky_color.day_horizon + << params.sky_color.dawn_sky << params.sky_color.dawn_horizon + << params.sky_color.night_sky << params.sky_color.night_horizon + << params.sky_color.indoors; + } + } + + Send(&pkt); +} + +void Server::SendSetSun(session_t peer_id, const SunParams ¶ms) +{ + NetworkPacket pkt(TOCLIENT_SET_SUN, 0, peer_id); + pkt << params.visible << params.texture + << params.tonemap << params.sunrise + << params.sunrise_visible << params.scale; + + Send(&pkt); +} +void Server::SendSetMoon(session_t peer_id, const MoonParams ¶ms) +{ + NetworkPacket pkt(TOCLIENT_SET_MOON, 0, peer_id); - pkt << clouds; + pkt << params.visible << params.texture + << params.tonemap << params.scale; + + Send(&pkt); +} +void Server::SendSetStars(session_t peer_id, const StarParams ¶ms) +{ + NetworkPacket pkt(TOCLIENT_SET_STARS, 0, peer_id); + + pkt << params.visible << params.count + << params.starcolor << params.scale; Send(&pkt); } @@ -1767,10 +1812,7 @@ void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) void Server::SendPlayerHP(session_t peer_id) { PlayerSAO *playersao = getPlayerSAO(peer_id); - // In some rare case if the player is disconnected - // while Lua call l_punch, for example, this can be NULL - if (!playersao) - return; + assert(playersao); SendHP(peer_id, playersao->getHP()); m_script->player_event(playersao,"health_changed"); @@ -2012,8 +2054,18 @@ void Server::SendPlayerSpeed(session_t peer_id, const v3f &added_vel) Send(&pkt); } +inline s32 Server::nextSoundId() +{ + s32 ret = m_next_sound_id; + if (m_next_sound_id == INT32_MAX) + m_next_sound_id = 0; // signed overflow is undefined + else + m_next_sound_id++; + return ret; +} + s32 Server::playSound(const SimpleSoundSpec &spec, - const ServerSoundParams ¶ms) + const ServerSoundParams ¶ms, bool ephemeral) { // Find out initial position of sound bool pos_exists = false; @@ -2024,7 +2076,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, // Filter destination clients std::vector<session_t> dst_clients; - if(!params.to_player.empty()) { + if (!params.to_player.empty()) { RemotePlayer *player = m_env->getPlayer(params.to_player.c_str()); if(!player){ infostream<<"Server::playSound: Player \""<<params.to_player @@ -2044,6 +2096,9 @@ s32 Server::playSound(const SimpleSoundSpec &spec, RemotePlayer *player = m_env->getPlayer(client_id); if (!player) continue; + if (!params.exclude_player.empty() && + params.exclude_player == player->getName()) + continue; PlayerSAO *sao = player->getPlayerSAO(); if (!sao) @@ -2062,27 +2117,32 @@ s32 Server::playSound(const SimpleSoundSpec &spec, return -1; // Create the sound - s32 id = m_next_sound_id++; - // The sound will exist as a reference in m_playing_sounds - m_playing_sounds[id] = ServerPlayingSound(); - ServerPlayingSound &psound = m_playing_sounds[id]; - psound.params = params; - psound.spec = spec; + s32 id; + ServerPlayingSound *psound = nullptr; + if (ephemeral) { + id = -1; // old clients will still use this, so pick a reserved ID + } else { + id = nextSoundId(); + // The sound will exist as a reference in m_playing_sounds + m_playing_sounds[id] = ServerPlayingSound(); + psound = &m_playing_sounds[id]; + psound->params = params; + psound->spec = spec; + } float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); pkt << id << spec.name << gain << (u8) params.type << pos << params.object - << params.loop << params.fade << params.pitch; + << params.loop << params.fade << params.pitch + << ephemeral; - // Backwards compability - bool play_sound = gain > 0; + bool as_reliable = !ephemeral; for (const u16 dst_client : dst_clients) { - if (play_sound || m_clients.getProtocolVersion(dst_client) >= 32) { - psound.clients.insert(dst_client); - m_clients.send(dst_client, 0, &pkt, true); - } + if (psound) + psound->clients.insert(dst_client); + m_clients.send(dst_client, 0, &pkt, as_reliable); } return id; } @@ -2682,10 +2742,7 @@ void Server::sendDetachedInventories(session_t peer_id, bool incremental) void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) { PlayerSAO *playersao = getPlayerSAO(peer_id); - // In some rare cases this can be NULL -- if the player is disconnected - // when a Lua function modifies l_punch, for example - if (!playersao) - return; + assert(playersao); infostream << "Server::DiePlayer(): Player " << playersao->getPlayer()->getName() @@ -3307,13 +3364,32 @@ void Server::setPlayerEyeOffset(RemotePlayer *player, const v3f &first, const v3 SendEyeOffset(player->getPeerId(), first, third); } -void Server::setSky(RemotePlayer *player, const video::SColor &bgcolor, - const std::string &type, const std::vector<std::string> ¶ms, - bool &clouds) +void Server::setSky(RemotePlayer *player, const SkyboxParams ¶ms) +{ + sanity_check(player); + player->setSky(params); + SendSetSky(player->getPeerId(), params); +} + +void Server::setSun(RemotePlayer *player, const SunParams ¶ms) +{ + sanity_check(player); + player->setSun(params); + SendSetSun(player->getPeerId(), params); +} + +void Server::setMoon(RemotePlayer *player, const MoonParams ¶ms) +{ + sanity_check(player); + player->setMoon(params); + SendSetMoon(player->getPeerId(), params); +} + +void Server::setStars(RemotePlayer *player, const StarParams ¶ms) { sanity_check(player); - player->setSky(bgcolor, type, params, clouds); - SendSetSky(player->getPeerId(), bgcolor, type, params, clouds); + player->setStars(params); + SendSetStars(player->getPeerId(), params); } void Server::setClouds(RemotePlayer *player, const CloudParams ¶ms) diff --git a/src/server.h b/src/server.h index d61840871..680de57b5 100644 --- a/src/server.h +++ b/src/server.h @@ -61,6 +61,10 @@ class ServerScripting; class ServerEnvironment; struct SimpleSoundSpec; struct CloudParams; +struct SkyboxParams; +struct SunParams; +struct MoonParams; +struct StarParams; class ServerThread; class ServerModManager; @@ -98,6 +102,7 @@ struct ServerSoundParams v3f pos; u16 object = 0; std::string to_player = ""; + std::string exclude_player = ""; v3f getPos(ServerEnvironment *env, bool *pos_exists) const; }; @@ -209,7 +214,8 @@ public: // Returns -1 if failed, sound handle on success // Envlock - s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms); + s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms, + bool ephemeral=false); void stopSound(s32 handle); void fadeSound(s32 handle, float step, float gain); @@ -305,9 +311,11 @@ public: f32 frame_speed); void setPlayerEyeOffset(RemotePlayer *player, const v3f &first, const v3f &third); - void setSky(RemotePlayer *player, const video::SColor &bgcolor, - const std::string &type, const std::vector<std::string> ¶ms, - bool &clouds); + void setSky(RemotePlayer *player, const SkyboxParams ¶ms); + void setSun(RemotePlayer *player, const SunParams ¶ms); + void setMoon(RemotePlayer *player, const MoonParams ¶ms); + void setStars(RemotePlayer *player, const StarParams ¶ms); + void setClouds(RemotePlayer *player, const CloudParams ¶ms); bool overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); @@ -411,9 +419,10 @@ private: void SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value); void SendHUDSetFlags(session_t peer_id, u32 flags, u32 mask); void SendHUDSetParam(session_t peer_id, u16 param, const std::string &value); - void SendSetSky(session_t peer_id, const video::SColor &bgcolor, - const std::string &type, const std::vector<std::string> ¶ms, - bool &clouds); + void SendSetSky(session_t peer_id, const SkyboxParams ¶ms); + void SendSetSun(session_t peer_id, const SunParams ¶ms); + void SendSetMoon(session_t peer_id, const MoonParams ¶ms); + void SendSetStars(session_t peer_id, const StarParams ¶ms); void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); void broadcastModChannelMessage(const std::string &channel, @@ -646,7 +655,8 @@ private: Sounds */ std::unordered_map<s32, ServerPlayingSound> m_playing_sounds; - s32 m_next_sound_id = 0; + s32 m_next_sound_id = 0; // positive values only + s32 nextSoundId(); /* Detached inventories (behind m_env_mutex) diff --git a/src/serverenvironment.h b/src/serverenvironment.h index cc4ecd797..3c7b7d059 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -358,6 +358,7 @@ public: RemotePlayer *getPlayer(const session_t peer_id); RemotePlayer *getPlayer(const char* name); + const std::vector<RemotePlayer *> getPlayers() const { return m_players; } u32 getPlayerCount() const { return m_players.size(); } static bool migratePlayersDatabase(const GameParams &game_params, diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 7d3ab4bbb..18264e933 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -155,6 +155,16 @@ std::vector<ServerListSpec> deSerialize(const std::string &liststring) server["address"] = tmp; std::getline(stream, tmp); server["port"] = tmp; + bool unique = true; + for (const ServerListSpec &added : serverlist) { + if (server["name"] == added["name"] + && server["port"] == added["port"]) { + unique = false; + break; + } + } + if (!unique) + continue; std::getline(stream, tmp); server["description"] = tmp; serverlist.push_back(server); diff --git a/src/settings.cpp b/src/settings.cpp index c1fe41fa3..55404319e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -484,12 +484,33 @@ v3f Settings::getV3F(const std::string &name) const u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc, u32 *flagmask) const { - std::string val = get(name); - return std::isdigit(val[0]) - ? stoi(val) - : readFlagString(val, flagdesc, flagmask); -} + u32 flags = 0; + u32 mask_default = 0; + + std::string value; + // Read default value (if there is any) + if (getDefaultNoEx(name, value)) { + flags = std::isdigit(value[0]) + ? stoi(value) + : readFlagString(value, flagdesc, &mask_default); + } + + // Apply custom flags "on top" + value = get(name); + u32 flags_user; + u32 mask_user = U32_MAX; + flags_user = std::isdigit(value[0]) + ? stoi(value) // Override default + : readFlagString(value, flagdesc, &mask_user); + + flags &= ~mask_user; + flags |= flags_user; + + if (flagmask) + *flagmask = mask_default | mask_user; + return flags; +} // N.B. if getStruct() is used to read a non-POD aggregate type, // the behavior is undefined. @@ -736,19 +757,16 @@ bool Settings::getV3FNoEx(const std::string &name, v3f &val) const } -// N.B. getFlagStrNoEx() does not set val, but merely modifies it. Thus, -// val must be initialized before using getFlagStrNoEx(). The intention of -// this is to simplify modifying a flags field from a default value. bool Settings::getFlagStrNoEx(const std::string &name, u32 &val, - FlagDesc *flagdesc) const + const FlagDesc *flagdesc) const { - try { - u32 flags, flagmask; - - flags = getFlagStr(name, flagdesc, &flagmask); + if (!flagdesc) { + if (!(flagdesc = getFlagDescFallback(name))) + return false; // Not found + } - val &= ~flagmask; - val |= flags; + try { + val = getFlagStr(name, flagdesc, nullptr); return true; } catch (SettingNotFoundException &e) { @@ -873,6 +891,11 @@ bool Settings::setV3F(const std::string &name, v3f value) bool Settings::setFlagStr(const std::string &name, u32 flags, const FlagDesc *flagdesc, u32 flagmask) { + if (!flagdesc) { + if (!(flagdesc = getFlagDescFallback(name))) + return false; // Not found + } + return set(name, writeFlagString(flags, flagdesc, flagmask)); } @@ -1018,6 +1041,42 @@ void Settings::clearDefaultsNoLock() m_defaults.clear(); } +void Settings::setDefault(const std::string &name, const FlagDesc *flagdesc, + u32 flags) +{ + m_flags[name] = flagdesc; + setDefault(name, writeFlagString(flags, flagdesc, U32_MAX)); +} + +void Settings::overrideDefaults(Settings *other) +{ + for (const auto &setting : other->m_settings) { + if (setting.second.is_group) { + setGroupDefault(setting.first, setting.second.group); + continue; + } + const FlagDesc *flagdesc = getFlagDescFallback(setting.first); + if (flagdesc) { + // Flags cannot be copied directly. + // 1) Get the current set flags + u32 flags = getFlagStr(setting.first, flagdesc, nullptr); + // 2) Set the flags as defaults + other->setDefault(setting.first, flagdesc, flags); + // 3) Get the newly set flags and override the default setting value + setDefault(setting.first, flagdesc, + other->getFlagStr(setting.first, flagdesc, nullptr)); + continue; + } + // Also covers FlagDesc settings + setDefault(setting.first, setting.second.value); + } +} + +const FlagDesc *Settings::getFlagDescFallback(const std::string &name) const +{ + auto it = m_flags.find(name); + return it == m_flags.end() ? nullptr : it->second; +} void Settings::registerChangedCallback(const std::string &name, SettingsChangedCallback cbf, void *userdata) diff --git a/src/settings.h b/src/settings.h index 329a61140..0c9a155db 100644 --- a/src/settings.h +++ b/src/settings.h @@ -174,10 +174,12 @@ public: bool getFloatNoEx(const std::string &name, float &val) const; bool getV2FNoEx(const std::string &name, v2f &val) const; bool getV3FNoEx(const std::string &name, v3f &val) const; - // N.B. getFlagStrNoEx() does not set val, but merely modifies it. Thus, - // val must be initialized before using getFlagStrNoEx(). The intention of - // this is to simplify modifying a flags field from a default value. - bool getFlagStrNoEx(const std::string &name, u32 &val, FlagDesc *flagdesc) const; + + // Like other getters, but handling each flag individualy: + // 1) Read default flags (or 0) + // 2) Override using user-defined flags + bool getFlagStrNoEx(const std::string &name, u32 &val, + const FlagDesc *flagdesc) const; /*********** @@ -201,7 +203,7 @@ public: bool setV2F(const std::string &name, v2f value); bool setV3F(const std::string &name, v3f value); bool setFlagStr(const std::string &name, u32 flags, - const FlagDesc *flagdesc, u32 flagmask); + const FlagDesc *flagdesc = nullptr, u32 flagmask = U32_MAX); bool setNoiseParams(const std::string &name, const NoiseParams &np, bool set_default=false); // N.B. if setStruct() is used to write a non-POD aggregate type, @@ -215,6 +217,15 @@ public: void updateValue(const Settings &other, const std::string &name); void update(const Settings &other); + /************** + * Miscellany * + **************/ + + void setDefault(const std::string &name, const FlagDesc *flagdesc, u32 flags); + // Takes the provided setting values and uses them as new defaults + void overrideDefaults(Settings *other); + const FlagDesc *getFlagDescFallback(const std::string &name) const; + void registerChangedCallback(const std::string &name, SettingsChangedCallback cbf, void *userdata = NULL); void deregisterChangedCallback(const std::string &name, @@ -229,6 +240,7 @@ private: SettingEntries m_settings; SettingEntries m_defaults; + std::unordered_map<std::string, const FlagDesc *> m_flags; SettingsCallbackMap m_callbacks; diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index eea045ae3..9156580c0 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -198,7 +198,7 @@ fake_function() { gettext("In-Game"); gettext("Basic"); gettext("VBO"); - gettext("Enable VBO"); + gettext("Enable vertex buffer objects.\nThis should greatly improve graphics performance."); gettext("Fog"); gettext("Whether to fog out the end of the visible area."); gettext("Leaves style"); @@ -239,7 +239,7 @@ fake_function() { gettext("Path to shader directory. If no path is defined, default location will be used."); gettext("Tone Mapping"); gettext("Filmic tone mapping"); - gettext("Enables filmic tone mapping"); + gettext("Enables Hable's 'Uncharted 2' filmic tone mapping.\nSimulates the tone curve of photographic film and how this approximates the\nappearance of high dynamic range images. Mid-range contrast is slightly\nenhanced, highlights and shadows are gradually compressed."); gettext("Bumpmapping"); gettext("Bumpmapping"); gettext("Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack\nor need to be auto-generated.\nRequires shaders to be enabled."); @@ -263,15 +263,18 @@ fake_function() { gettext("Parallax occlusion bias"); gettext("Overall bias of parallax occlusion effect, usually scale/2."); gettext("Waving Nodes"); - gettext("Waving water"); - gettext("Set to true enables waving water.\nRequires shaders to be enabled."); - gettext("Waving water wave height"); - gettext("Waving water wavelength"); - gettext("Waving water wave speed"); + gettext("Waving liquids"); + gettext("Set to true to enable waving liquids (like water).\nRequires shaders to be enabled."); + gettext("Waving liquids wave height"); + gettext("The maximum height of the surface of waving liquids.\n4.0 = Wave height is two nodes.\n0.0 = Wave doesn't move at all.\nDefault is 1.0 (1/2 node).\nRequires waving liquids to be enabled."); + gettext("Waving liquids wavelength"); + gettext("Length of liquid waves.\nRequires waving liquids to be enabled."); + gettext("Waving liquids wave speed"); + gettext("How fast liquid waves will move. Higher = faster.\nIf negative, liquid waves will move backwards.\nRequires waving liquids to be enabled."); gettext("Waving leaves"); - gettext("Set to true enables waving leaves.\nRequires shaders to be enabled."); + gettext("Set to true to enable waving leaves.\nRequires shaders to be enabled."); gettext("Waving plants"); - gettext("Set to true enables waving plants.\nRequires shaders to be enabled."); + gettext("Set to true to enable waving plants.\nRequires shaders to be enabled."); gettext("Advanced"); gettext("Arm inertia"); gettext("Arm inertia, gives a more realistic movement of\nthe arm when the camera moves."); @@ -283,8 +286,8 @@ fake_function() { gettext("Open the pause menu when the window's focus is lost. Does not pause if a formspec is\nopen."); gettext("Viewing range"); gettext("View distance in nodes."); - gettext("Near clipping plane"); - gettext("Camera 'near clipping plane' distance in nodes, between 0 and 0.5.\nMost users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets."); + gettext("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("Screen height"); @@ -299,18 +302,18 @@ fake_function() { gettext("Vertical screen synchronization."); gettext("Field of view"); gettext("Field of view in degrees."); - gettext("Gamma"); - gettext("Adjust the gamma encoding for the light tables. Higher numbers are brighter.\nThis setting is for the client only and is ignored by the server."); - gettext("Darkness sharpness"); - gettext("Gradient of light curve at minimum light level."); - gettext("Lightness sharpness"); - gettext("Gradient of light curve at maximum light level."); - gettext("Light curve mid boost"); - gettext("Strength of light curve mid-boost."); - gettext("Light curve mid boost center"); - gettext("Center of light curve mid-boost."); - gettext("Light curve mid boost spread"); - gettext("Spread of light curve mid-boost.\nStandard deviation of the mid-boost gaussian."); + gettext("Light curve gamma"); + gettext("Alters the light curve by applying 'gamma correction' to it.\nHigher values make middle and lower light levels brighter.\nValue '1.0' leaves the light curve unaltered.\nThis only has significant effect on daylight and artificial\nlight, it has very little effect on natural night light."); + gettext("Light curve low gradient"); + gettext("Gradient of light curve at minimum light level.\nControls the contrast of the lowest light levels."); + gettext("Light curve high gradient"); + gettext("Gradient of light curve at maximum light level.\nControls the contrast of the highest light levels."); + gettext("Light curve boost"); + gettext("Strength of light curve boost.\nThe 3 'boost' parameters define a range of the light\ncurve that is boosted in brightness."); + gettext("Light curve boost center"); + gettext("Center of light curve boost range.\nWhere 0.0 is minimum light level, 1.0 is maximum light level."); + gettext("Light curve boost spread"); + gettext("Spread of light curve boost range.\nControls the width of the range to be boosted.\nStandard deviation of the light curve boost Gaussian."); gettext("Texture path"); gettext("Path to texture directory. All textures are first searched from here."); gettext("Video driver"); @@ -364,7 +367,7 @@ fake_function() { gettext("Round minimap"); gettext("Shape of the minimap. Enabled = round, disabled = square."); gettext("Minimap scan height"); - gettext("True = 256\nFalse = 128\nUseable to make minimap smoother on slower machines."); + gettext("True = 256\nFalse = 128\nUsable to make minimap smoother on slower machines."); gettext("Colored fog"); gettext("Make fog and sky colors depend on daytime (dawn/sunset) and view direction."); gettext("Ambient occlusion gamma"); @@ -395,21 +398,35 @@ fake_function() { gettext("Append item name"); gettext("Append item name to tooltip."); gettext("FreeType fonts"); - gettext("Whether FreeType fonts are used, requires FreeType support to be compiled in."); - gettext("Font path"); - gettext("Path to TrueTypeFont or bitmap."); - gettext("Font size"); + gettext("Whether FreeType fonts are used, requires FreeType support to be compiled in.\nIf disabled, bitmap and XML vectors fonts are used instead."); + gettext("Font bold by default"); + gettext("Font italic by default"); gettext("Font shadow"); - gettext("Font shadow offset, if 0 then shadow will not be drawn."); + gettext("Shadow offset (in pixels) of the default font. If 0, then shadow will not be drawn."); gettext("Font shadow alpha"); - gettext("Font shadow alpha (opaqueness, between 0 and 255)."); - gettext("Monospace font path"); + gettext("Opaqueness (alpha) of the shadow behind the default font, between 0 and 255."); + gettext("Font size"); + gettext("Font size of the default font in point (pt)."); + gettext("Regular font path"); + gettext("Path to the default font.\nIf “freetype” setting is enabled: Must be a TrueType font.\nIf “freetype” setting is disabled: Must be a bitmap or XML vectors font.\nThe fallback font will be used if the font cannot be loaded."); + gettext("Bold font path"); + gettext("Italic font path"); + gettext("Bold and italic font path"); gettext("Monospace font size"); - gettext("Fallback font"); - gettext("This font will be used for certain languages."); + gettext("Font size of the monospace font in point (pt)."); + gettext("Monospace font path"); + gettext("Path to the monospace 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 is used for e.g. the console and profiler screen."); + gettext("Bold monospace font path"); + gettext("Italic monospace font path"); + gettext("Bold and italic monospace font path"); gettext("Fallback font 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("Screenshot folder"); gettext("Path to save screenshots at."); gettext("Screenshot format"); @@ -423,8 +440,11 @@ fake_function() { gettext("Windows systems only: Start Minetest with the command line window in the background.\nContains the same information as the file debug.txt (default name)."); gettext("Sound"); gettext("Sound"); + gettext("Enables the sound system.\nIf disabled, this completely disables all sounds everywhere and the in-game\nsound controls will be non-functional.\nChanging this setting requires a restart."); gettext("Volume"); + gettext("Volume of all sounds.\nRequires the sound system to be enabled."); gettext("Mute sound"); + gettext("Whether to mute sounds. You can unmute sounds at any time, unless the\nsound system is disabled (enable_sound=false).\nIn-game, you can toggle the mute state with the mute key or by using the\npause menu."); gettext("Client"); gettext("Network"); gettext("Server address"); @@ -477,7 +497,7 @@ fake_function() { gettext("Remote media"); gettext("Specifies URL from which client fetches media instead of using UDP.\n$filename should be accessible from $remote_media$filename via cURL\n(obviously, remote_media should end with a slash).\nFiles that are not present will be fetched the usual way."); gettext("IPv6 server"); - gettext("Enable/disable running an IPv6 server.\nIgnored if bind_address is set."); + gettext("Enable/disable running an IPv6 server.\nIgnored if bind_address is set.\nNeeds enable_ipv6 to be enabled."); gettext("Advanced"); gettext("Maximum simultaneous block sends per client"); gettext("Maximum number of blocks that are simultaneously sent per client.\nThe maximum total count is calculated dynamically:\nmax_total = ceil((#clients + max_users) * per_client / 4)"); @@ -572,16 +592,16 @@ fake_function() { gettext("Jumping speed"); gettext("Initial vertical speed when jumping, in nodes per second."); gettext("Liquid fluidity"); - gettext("Decrease this to increase liquid resistence to movement."); + gettext("Decrease this to increase liquid resistance to movement."); gettext("Liquid fluidity smoothing"); - gettext("Maximum liquid resistence. Controls deceleration when entering liquid at\nhigh speed."); + gettext("Maximum liquid resistance. Controls deceleration when entering liquid at\nhigh speed."); gettext("Liquid sinking"); gettext("Controls sinking speed in liquid."); gettext("Gravity"); gettext("Acceleration of gravity, in nodes per second per second."); gettext("Advanced"); gettext("Deprecated Lua API handling"); - gettext("Handling for deprecated lua api calls:\n- legacy: (try to) mimic old behaviour (default for release).\n- log: mimic and log backtrace of deprecated call (default for debug).\n- error: abort on usage of deprecated call (suggested for mod developers)."); + gettext("Handling for deprecated Lua API calls:\n- legacy: (try to) mimic old behaviour (default for release).\n- log: mimic and log backtrace of deprecated call (default for debug).\n- error: abort on usage of deprecated call (suggested for mod developers)."); gettext("Max. clearobjects extra blocks"); gettext("Number of extra blocks that can be loaded by /clearobjects at once.\nThis is a trade-off between sqlite transaction overhead and\nmemory consumption (4096=100MB, as a rule of thumb)."); gettext("Unload unused server data"); @@ -655,7 +675,7 @@ fake_function() { gettext("Debug log file size threshold"); gettext("If the file size of debug.txt exceeds the number of megabytes specified in\nthis setting when it is opened, the file is moved to debug.txt.1,\ndeleting an older debug.txt.1 if it exists.\ndebug.txt is only moved if this setting is positive."); gettext("IPv6"); - gettext("IPv6 support."); + gettext("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."); @@ -695,11 +715,19 @@ fake_function() { gettext("Mapgen V5 specific flags"); gettext("Map generation attributes specific to Mapgen v5."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Large cave depth"); gettext("Y of upper limit of large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Cavern limit"); gettext("Y-level of cavern upper limit."); gettext("Cavern taper"); @@ -767,21 +795,19 @@ fake_function() { gettext("Mountain zero level"); gettext("Y of mountain density gradient zero level. Used to shift mountains vertically."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Large cave depth"); gettext("Y of upper limit of large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); - gettext("Floatland mountain density"); - gettext("Controls the density of mountain-type floatlands.\nIs a noise offset added to the 'mgv7_np_mountain' noise value."); - gettext("Floatland mountain height"); - gettext("Typical maximum height, above and below midpoint, of floatland mountains."); - gettext("Floatland mountain exponent"); - gettext("Alters how mountain-type floatlands taper above and below midpoint."); - gettext("Floatland level"); - gettext("Y-level of floatland midpoint and lake surface."); - gettext("Shadow limit"); - gettext("Y-level to which floatland shadows extend."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Cavern limit"); gettext("Y-level of cavern upper limit."); gettext("Cavern taper"); @@ -807,10 +833,6 @@ fake_function() { gettext("Variation of maximum mountain height (in nodes)."); gettext("Ridge underwater noise"); gettext("Defines large-scale river channel structure."); - gettext("Floatland base noise"); - gettext("Defines areas of floatland smooth terrain.\nSmooth floatlands occur when noise > 0."); - gettext("Floatland base height noise"); - gettext("Variation of hill height and lake depth on floatland smooth terrain."); gettext("Mountain noise"); gettext("3D noise defining mountain structure and height.\nAlso defines structure of floatland mountain terrain."); gettext("Ridge noise"); @@ -835,11 +857,19 @@ fake_function() { gettext("River valley width"); gettext("Defines the width of the river valley."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Large cave depth"); gettext("Y of upper limit of large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Cavern limit"); gettext("Y-level of cavern upper limit."); gettext("Cavern taper"); @@ -887,15 +917,23 @@ fake_function() { gettext("3D noise that determines number of dungeons per mapchunk."); gettext("Mapgen Flat"); gettext("Mapgen Flat specific flags"); - gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills can be added to the flat world."); + gettext("Map generation attributes specific to Mapgen Flat.\nOccasional lakes and hills can be added to the flat world."); gettext("Ground level"); gettext("Y of flat ground."); gettext("Large cave depth"); gettext("Y of upper limit of large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Lake threshold"); gettext("Terrain noise threshold for lakes.\nControls proportion of world area covered by lakes.\nAdjust towards 0.0 for a larger proportion."); gettext("Lake steepness"); @@ -921,25 +959,33 @@ fake_function() { gettext("3D noise that determines number of dungeons per mapchunk."); gettext("Mapgen Fractal"); gettext("Mapgen Fractal specific flags"); - gettext("Map generation attributes specific to Mapgen flat.\n'terrain' enables the generation of non-fractal terrain:\nocean, islands and underground."); + gettext("Map generation attributes specific to Mapgen Fractal.\n'terrain' enables the generation of non-fractal terrain:\nocean, islands and underground."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Large cave depth"); gettext("Y of upper limit of large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Dungeon minimum Y"); gettext("Lower Y limit of dungeons."); gettext("Dungeon maximum Y"); gettext("Upper Y limit of dungeons."); gettext("Fractal type"); - gettext("Selects one of 18 fractal types.\n1 = 4D \"Roundy\" mandelbrot set.\n2 = 4D \"Roundy\" julia set.\n3 = 4D \"Squarry\" mandelbrot set.\n4 = 4D \"Squarry\" julia set.\n5 = 4D \"Mandy Cousin\" mandelbrot set.\n6 = 4D \"Mandy Cousin\" julia set.\n7 = 4D \"Variation\" mandelbrot set.\n8 = 4D \"Variation\" julia set.\n9 = 3D \"Mandelbrot/Mandelbar\" mandelbrot set.\n10 = 3D \"Mandelbrot/Mandelbar\" julia set.\n11 = 3D \"Christmas Tree\" mandelbrot set.\n12 = 3D \"Christmas Tree\" julia set.\n13 = 3D \"Mandelbulb\" mandelbrot set.\n14 = 3D \"Mandelbulb\" julia set.\n15 = 3D \"Cosine Mandelbulb\" mandelbrot set.\n16 = 3D \"Cosine Mandelbulb\" julia set.\n17 = 4D \"Mandelbulb\" mandelbrot set.\n18 = 4D \"Mandelbulb\" julia set."); + gettext("Selects one of 18 fractal types.\n1 = 4D \"Roundy\" Mandelbrot set.\n2 = 4D \"Roundy\" Julia set.\n3 = 4D \"Squarry\" Mandelbrot set.\n4 = 4D \"Squarry\" Julia set.\n5 = 4D \"Mandy Cousin\" Mandelbrot set.\n6 = 4D \"Mandy Cousin\" Julia set.\n7 = 4D \"Variation\" Mandelbrot set.\n8 = 4D \"Variation\" Julia set.\n9 = 3D \"Mandelbrot/Mandelbar\" Mandelbrot set.\n10 = 3D \"Mandelbrot/Mandelbar\" Julia set.\n11 = 3D \"Christmas Tree\" Mandelbrot set.\n12 = 3D \"Christmas Tree\" Julia set.\n13 = 3D \"Mandelbulb\" Mandelbrot set.\n14 = 3D \"Mandelbulb\" Julia set.\n15 = 3D \"Cosine Mandelbulb\" Mandelbrot set.\n16 = 3D \"Cosine Mandelbulb\" Julia set.\n17 = 4D \"Mandelbulb\" Mandelbrot set.\n18 = 4D \"Mandelbulb\" Julia set."); gettext("Iterations"); gettext("Iterations of the recursive function.\nIncreasing this increases the amount of fine detail, but also\nincreases processing load.\nAt iterations = 20 this mapgen has a similar load to mapgen V7."); gettext("Scale"); gettext("(X,Y,Z) scale of fractal in nodes.\nActual fractal size will be 2 to 3 times larger.\nThese numbers can be made very large, the fractal does\nnot have to fit inside the world.\nIncrease these to 'zoom' into the detail of the fractal.\nDefault is for a vertically-squashed shape suitable for\nan island, set all 3 numbers equal for the raw shape."); gettext("Offset"); - gettext("(X,Y,Z) offset of fractal from world center in units of 'scale'.\nCan be used to move a desired point to (0, 0) to create a\nsuitable spawn point, or to allow 'zooming in' on a desired\npoint by increasing 'scale'.\nThe default is tuned for a suitable spawn point for mandelbrot\nsets with default parameters, it may need altering in other\nsituations.\nRange roughly -2 to 2. Multiply by 'scale' for offset in nodes."); + gettext("(X,Y,Z) offset of fractal from world center in units of 'scale'.\nCan be used to move a desired point to (0, 0) to create a\nsuitable spawn point, or to allow 'zooming in' on a desired\npoint by increasing 'scale'.\nThe default is tuned for a suitable spawn point for Mandelbrot\nsets with default parameters, it may need altering in other\nsituations.\nRange roughly -2 to 2. Multiply by 'scale' for offset in nodes."); gettext("Slice w"); gettext("W coordinate of the generated 3D slice of a 4D fractal.\nDetermines which 3D slice of the 4D shape is generated.\nAlters the shape of the fractal.\nHas no effect on 3D fractals.\nRange roughly -2 to 2."); gettext("Julia x"); @@ -968,8 +1014,16 @@ fake_function() { gettext("The vertical distance over which heat drops by 20 if 'altitude_chill' is\nenabled. Also the vertical distance over which humidity drops by 10 if\n'altitude_dry' is enabled."); gettext("Large cave depth"); gettext("Depth below which you'll find large caves."); - gettext("Lava depth"); - gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves."); + gettext("Small cave minimum number"); + gettext("Minimum limit of random number of small caves per mapchunk."); + gettext("Small cave maximum number"); + gettext("Maximum limit of random number of small caves per mapchunk."); + gettext("Large cave minimum number"); + gettext("Minimum limit of random number of large caves per mapchunk."); + gettext("Large cave maximum number"); + gettext("Maximum limit of random number of large caves per mapchunk."); + gettext("Large cave proportion flooded"); + gettext("Proportion of large caves that contain liquid."); gettext("Cavern upper limit"); gettext("Depth below which you'll find giant caverns."); gettext("Cavern taper"); @@ -981,7 +1035,7 @@ fake_function() { gettext("River size"); gettext("How wide to make rivers."); gettext("Cave width"); - gettext("Controls width of tunnels, a smaller value creates wider tunnels."); + gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations."); gettext("Dungeon minimum Y"); gettext("Lower Y limit of dungeons."); gettext("Dungeon maximum Y"); diff --git a/src/skyparams.h b/src/skyparams.h new file mode 100644 index 000000000..9fdfd89da --- /dev/null +++ b/src/skyparams.h @@ -0,0 +1,121 @@ +/* +Minetest +Copyright (C) 2019 Jordach, Jordan Snelling <jordach.snelling@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 + +struct SkyColor +{ + video::SColor day_sky; + video::SColor day_horizon; + video::SColor dawn_sky; + video::SColor dawn_horizon; + video::SColor night_sky; + video::SColor night_horizon; + video::SColor indoors; +}; + +struct SkyboxParams +{ + video::SColor bgcolor; + std::string type; + std::vector<std::string> textures; + bool clouds; + SkyColor sky_color; + video::SColor sun_tint; + video::SColor moon_tint; + std::string tint_type; +}; + +struct SunParams +{ + bool visible; + std::string texture; + std::string tonemap; + std::string sunrise; + bool sunrise_visible; + f32 scale; +}; + +struct MoonParams +{ + bool visible; + std::string texture; + std::string tonemap; + f32 scale; +}; + +struct StarParams +{ + bool visible; + u32 count; + video::SColor starcolor; + f32 scale; +}; + +// Utility class for setting default sky, sun, moon, stars values: +class SkyboxDefaults +{ +public: + const SkyColor getSkyColorDefaults() + { + SkyColor sky; + // Horizon colors + sky.day_horizon = video::SColor(255, 155, 193, 240); + sky.indoors = video::SColor(255, 100, 100, 100); + sky.dawn_horizon = video::SColor(255, 186, 193, 240); + sky.night_horizon = video::SColor(255, 64, 144, 255); + // Sky colors + sky.day_sky = video::SColor(255, 140, 186, 250); + sky.dawn_sky = video::SColor(255, 180, 186, 250); + sky.night_sky = video::SColor(255, 0, 107, 255); + return sky; + } + + const SunParams getSunDefaults() + { + SunParams sun; + sun.visible = true; + sun.sunrise_visible = true; + sun.texture = "sun.png"; + sun.tonemap = "sun_tonemap.png"; + sun.sunrise = "sunrisebg.png"; + sun.scale = 1; + return sun; + } + + const MoonParams getMoonDefaults() + { + MoonParams moon; + moon.visible = true; + moon.texture = "moon.png"; + moon.tonemap = "moon_tonemap.png"; + moon.scale = 1; + return moon; + } + + const StarParams getStarDefaults() + { + StarParams stars; + stars.visible = true; + stars.count = 1000; + stars.starcolor = video::SColor(105, 235, 235, 255); + stars.scale = 1; + return stars; + } +}; diff --git a/src/threading/semaphore.cpp b/src/threading/semaphore.cpp index 77ceff509..ce22dcd05 100644 --- a/src/threading/semaphore.cpp +++ b/src/threading/semaphore.cpp @@ -140,22 +140,27 @@ bool Semaphore::wait(unsigned int time_ms) errno = EINVAL; } # else - struct timespec wait_time; - struct timeval now; + int ret; + if (time_ms > 0) { + struct timespec wait_time; + struct timeval now; - if (gettimeofday(&now, NULL) == -1) { - std::cerr << "Semaphore::wait(ms): Unable to get time with gettimeofday!" << std::endl; - abort(); - } + if (gettimeofday(&now, NULL) == -1) { + std::cerr << "Semaphore::wait(ms): Unable to get time with gettimeofday!" << std::endl; + abort(); + } - wait_time.tv_nsec = ((time_ms % 1000) * 1000 * 1000) + (now.tv_usec * 1000); - wait_time.tv_sec = (time_ms / 1000) + (wait_time.tv_nsec / (1000 * 1000 * 1000)) + now.tv_sec; - wait_time.tv_nsec %= 1000 * 1000 * 1000; + wait_time.tv_nsec = ((time_ms % 1000) * 1000 * 1000) + (now.tv_usec * 1000); + wait_time.tv_sec = (time_ms / 1000) + (wait_time.tv_nsec / (1000 * 1000 * 1000)) + now.tv_sec; + wait_time.tv_nsec %= 1000 * 1000 * 1000; - int ret = sem_timedwait(&semaphore, &wait_time); + ret = sem_timedwait(&semaphore, &wait_time); + } else { + ret = sem_trywait(&semaphore); + } # endif - assert(!ret || (errno == ETIMEDOUT || errno == EINTR)); + assert(!ret || (errno == ETIMEDOUT || errno == EINTR || errno == EAGAIN)); return !ret; #endif } diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp index 7d0378131..dfcadd4b2 100644 --- a/src/unittest/test_compression.cpp +++ b/src/unittest/test_compression.cpp @@ -37,6 +37,8 @@ public: void testRLECompression(); void testZlibCompression(); void testZlibLargeData(); + void testZlibLimit(); + void _testZlibLimit(u32 size, u32 limit); }; static TestCompression g_test_instance; @@ -46,6 +48,7 @@ void TestCompression::runTests(IGameDef *gamedef) TEST(testRLECompression); TEST(testZlibCompression); TEST(testZlibLargeData); + TEST(testZlibLimit); } //////////////////////////////////////////////////////////////////////////////// @@ -170,3 +173,63 @@ void TestCompression::testZlibLargeData() i, str_decompressed[i], i, data_in[i]); } } + +void TestCompression::testZlibLimit() +{ + // edge cases + _testZlibLimit(1024, 1023); + _testZlibLimit(1024, 1024); + _testZlibLimit(1024, 1025); + + // test around buffer borders + u32 bufsize = 16384; // as in implementation + for (int s = -1; s <= 1; s++) + { + for (int l = -1; l <= 1; l++) + { + _testZlibLimit(bufsize + s, bufsize + l); + } + } + // span multiple buffers + _testZlibLimit(35000, 22000); + _testZlibLimit(22000, 35000); +} + +void TestCompression::_testZlibLimit(u32 size, u32 limit) +{ + infostream << "Test: Testing zlib wrappers with a decompression " + "memory limit of " << limit << std::endl; + + infostream << "Test: Input size of compressZlib for limit is " + << size << std::endl; + + // how much data we expect to get + u32 expected = size < limit ? size : limit; + + // create recognizable data + std::string data_in; + data_in.resize(size); + for (u32 i = 0; i < size; i++) + data_in[i] = (u8)(i % 256); + + std::ostringstream os_compressed(std::ios::binary); + compressZlib(data_in, os_compressed); + infostream << "Test: Output size of compressZlib for limit is " + << os_compressed.str().size()<<std::endl; + + std::istringstream is_compressed(os_compressed.str(), std::ios::binary); + std::ostringstream os_decompressed(std::ios::binary); + decompressZlib(is_compressed, os_decompressed, limit); + infostream << "Test: Output size of decompressZlib with limit is " + << os_decompressed.str().size() << std::endl; + + std::string str_decompressed = os_decompressed.str(); + UASSERTEQ(size_t, str_decompressed.size(), expected); + + for (u32 i = 0; i < size && i < str_decompressed.size(); i++) { + UTEST(str_decompressed[i] == data_in[i], + "index out[%i]=%i differs from in[%i]=%i", + i, str_decompressed[i], i, data_in[i]); + } +} + diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp index b2666559e..aa56f3e06 100644 --- a/src/unittest/test_settings.cpp +++ b/src/unittest/test_settings.cpp @@ -31,6 +31,7 @@ public: void runTests(IGameDef *gamedef); void testAllSettings(); + void testFlagDesc(); static const char *config_text_before; static const std::string config_text_after; @@ -41,6 +42,7 @@ static TestSettings g_test_instance; void TestSettings::runTests(IGameDef *gamedef) { TEST(testAllSettings); + TEST(testFlagDesc); } //////////////////////////////////////////////////////////////////////////////// @@ -206,3 +208,38 @@ void TestSettings::testAllSettings() UASSERT(!"Setting not found!"); } } + +void TestSettings::testFlagDesc() +{ + Settings s; + FlagDesc flagdesc[] = { + { "biomes", 0x01 }, + { "trees", 0x02 }, + { "jungles", 0x04 }, + { "oranges", 0x08 }, + { "tables", 0x10 }, + { nullptr, 0 } + }; + + // Enabled: biomes, jungles, oranges (default) + s.setDefault("test_desc", flagdesc, readFlagString( + "biomes,notrees,jungles,oranges", flagdesc, nullptr)); + UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == (0x01 | 0x04 | 0x08)); + + // Enabled: jungles, oranges, tables + s.set("test_desc", "nobiomes,tables"); + UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == (0x04 | 0x08 | 0x10)); + + // Enabled: (nothing) + s.set("test_desc", "nobiomes,nojungles,nooranges,notables"); + UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == 0x00); + + // Numeric flag tests (override) + // Enabled: trees, tables + s.setDefault("test_flags", flagdesc, 0x02 | 0x10); + UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == (0x02 | 0x10)); + + // Enabled: tables + s.set("test_flags", "16"); + UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == 0x10); +} diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 8e8958d18..447b591e1 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "test.h" #include <cmath> +#include "util/enriched_string.h" #include "util/numeric.h" #include "util/string.h" @@ -49,6 +50,7 @@ public: void testUTF8(); void testRemoveEscapes(); void testWrapRows(); + void testEnrichedString(); void testIsNumber(); void testIsPowerOfTwo(); void testMyround(); @@ -79,6 +81,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testUTF8); TEST(testRemoveEscapes); TEST(testWrapRows); + TEST(testEnrichedString); TEST(testIsNumber); TEST(testIsPowerOfTwo); TEST(testMyround); @@ -344,6 +347,23 @@ void TestUtilities::testWrapRows() } } +void TestUtilities::testEnrichedString() +{ + EnrichedString str(L"Test bar"); + irr::video::SColor color(0xFF, 0, 0, 0xFF); + + UASSERT(str.substr(1, 3).getString() == L"est"); + str += L" BUZZ"; + UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ"); + str.setDefaultColor(color); // Blue foreground + UASSERT(str.getColors()[5] == color); + // Green background, then white and yellow text + str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow"; + UASSERT(str.getColors()[2] == 0xFFFFFFFF); + str.setDefaultColor(color); // Blue foreground + UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text + UASSERT(str.getBackground() == 0xFF00FF00); // Green background +} void TestUtilities::testIsNumber() { diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp index 642188a52..762d094eb 100644 --- a/src/util/enriched_string.cpp +++ b/src/util/enriched_string.cpp @@ -19,7 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "enriched_string.h" #include "util/string.h" +#include "debug.h" #include "log.h" + using namespace irr::video; EnrichedString::EnrichedString() @@ -28,10 +30,12 @@ EnrichedString::EnrichedString() } EnrichedString::EnrichedString(const std::wstring &string, - const std::vector<SColor> &colors): - m_string(string), - m_colors(colors) -{} + const std::vector<SColor> &colors) +{ + clear(); + m_string = string; + m_colors = colors; +} EnrichedString::EnrichedString(const std::wstring &s, const SColor &color) { @@ -45,15 +49,28 @@ EnrichedString::EnrichedString(const wchar_t *str, const SColor &color) addAtEnd(translate_string(std::wstring(str)), color); } +void EnrichedString::clear() +{ + m_string.clear(); + m_colors.clear(); + m_has_background = false; + m_default_length = 0; + m_default_color = irr::video::SColor(255, 255, 255, 255); + m_background = irr::video::SColor(0, 0, 0, 0); +} + void EnrichedString::operator=(const wchar_t *str) { clear(); - addAtEnd(translate_string(std::wstring(str)), SColor(255, 255, 255, 255)); + addAtEnd(translate_string(std::wstring(str)), m_default_color); } void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color) { SColor color(initial_color); + bool use_default = (m_default_length == m_string.size() && + color == m_default_color); + size_t i = 0; while (i < s.length()) { if (s[i] != L'\x1b') { @@ -90,6 +107,12 @@ void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color continue; } parseColorString(wide_to_utf8(parts[1]), color, true); + + // No longer use default color after first escape + if (use_default) { + m_default_length = m_string.size(); + use_default = false; + } } else if (parts[0] == L"b") { if (parts.size() < 2) { continue; @@ -98,6 +121,10 @@ void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color m_has_background = true; } } + + // Update if no escape character was found + if (use_default) + m_default_length = m_string.size(); } void EnrichedString::addChar(const EnrichedString &source, size_t i) @@ -110,7 +137,7 @@ void EnrichedString::addCharNoColor(wchar_t c) { m_string += c; if (m_colors.empty()) { - m_colors.emplace_back(255, 255, 255, 255); + m_colors.emplace_back(m_default_color); } else { m_colors.push_back(m_colors[m_colors.size() - 1]); } @@ -118,35 +145,44 @@ void EnrichedString::addCharNoColor(wchar_t c) EnrichedString EnrichedString::operator+(const EnrichedString &other) const { - std::vector<SColor> result; - result.insert(result.end(), m_colors.begin(), m_colors.end()); - result.insert(result.end(), other.m_colors.begin(), other.m_colors.end()); - return EnrichedString(m_string + other.m_string, result); + EnrichedString result = *this; + result += other; + return result; } void EnrichedString::operator+=(const EnrichedString &other) { + bool update_default_color = m_default_length == m_string.size(); + m_string += other.m_string; m_colors.insert(m_colors.end(), other.m_colors.begin(), other.m_colors.end()); + + if (update_default_color) { + m_default_length += other.m_default_length; + updateDefaultColor(); + } } EnrichedString EnrichedString::substr(size_t pos, size_t len) const { - if (pos == m_string.length()) { + if (pos >= m_string.length()) return EnrichedString(); - } - if (len == std::string::npos || pos + len > m_string.length()) { - return EnrichedString( - m_string.substr(pos, std::string::npos), - std::vector<SColor>(m_colors.begin() + pos, m_colors.end()) - ); - } - return EnrichedString( + if (len == std::string::npos || pos + len > m_string.length()) + len = m_string.length() - pos; + + EnrichedString str( m_string.substr(pos, len), std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len) ); + str.m_has_background = m_has_background; + str.m_background = m_background; + + if (pos < m_default_length) + str.m_default_length = std::min(m_default_length - pos, str.size()); + str.setDefaultColor(m_default_color); + return str; } const wchar_t *EnrichedString::c_str() const @@ -163,3 +199,17 @@ const std::wstring &EnrichedString::getString() const { return m_string; } + +void EnrichedString::setDefaultColor(const irr::video::SColor &color) +{ + m_default_color = color; + updateDefaultColor(); +} + +void EnrichedString::updateDefaultColor() +{ + sanity_check(m_default_length <= m_colors.size()); + + for (size_t i = 0; i < m_default_length; ++i) + m_colors[i] = m_default_color; +} diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h index 202d84cb0..c8a095887 100644 --- a/src/util/enriched_string.h +++ b/src/util/enriched_string.h @@ -32,6 +32,7 @@ public: const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255)); EnrichedString(const std::wstring &string, const std::vector<irr::video::SColor> &colors); + void clear(); void operator=(const wchar_t *str); void addAtEnd(const std::wstring &s, const irr::video::SColor &color); @@ -50,6 +51,14 @@ public: const wchar_t *c_str() const; const std::vector<irr::video::SColor> &getColors() const; const std::wstring &getString() const; + + void setDefaultColor(const irr::video::SColor &color); + void updateDefaultColor(); + inline const irr::video::SColor &getDefaultColor() const + { + return m_default_color; + } + inline bool operator==(const EnrichedString &other) const { return (m_string == other.m_string && m_colors == other.m_colors); @@ -58,12 +67,6 @@ public: { return !(*this == other); } - inline void clear() - { - m_string.clear(); - m_colors.clear(); - m_has_background = false; - } inline bool empty() const { return m_string.empty(); @@ -72,6 +75,7 @@ public: { return m_string.size(); } + inline bool hasBackground() const { return m_has_background; @@ -80,9 +84,19 @@ public: { return m_background; } + inline void setBackground(const irr::video::SColor &color) + { + m_background = color; + m_has_background = true; + } + private: std::wstring m_string; std::vector<irr::video::SColor> m_colors; - bool m_has_background = false; + bool m_has_background; + irr::video::SColor m_default_color; irr::video::SColor m_background; + // This variable defines the length of the default-colored text. + // Change this to a std::vector if an "end coloring" tag is wanted. + size_t m_default_length = 0; }; diff --git a/src/util/string.cpp b/src/util/string.cpp index 388e8d293..e6c52585d 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -79,6 +79,13 @@ bool convert(const char *to, const char *from, char *outbuf, return true; } +#ifdef __ANDROID__ +// Android need manual caring to support the full character set possible with wchar_t +const char *DEFAULT_ENCODING = "UTF-32LE"; +#else +const char *DEFAULT_ENCODING = "WCHAR_T"; +#endif + std::wstring utf8_to_wide(const std::string &input) { size_t inbuf_size = input.length() + 1; @@ -90,7 +97,12 @@ std::wstring utf8_to_wide(const std::string &input) char *outbuf = new char[outbuf_size]; memset(outbuf, 0, outbuf_size); - if (!convert("WCHAR_T", "UTF-8", outbuf, outbuf_size, inbuf, inbuf_size)) { +#ifdef __ANDROID__ + // Android need manual caring to support the full character set possible with wchar_t + SANITY_CHECK(sizeof(wchar_t) == 4); +#endif + + if (!convert(DEFAULT_ENCODING, "UTF-8", outbuf, outbuf_size, inbuf, inbuf_size)) { infostream << "Couldn't convert UTF-8 string 0x" << hex_encode(input) << " into wstring" << std::endl; delete[] inbuf; @@ -105,13 +117,6 @@ std::wstring utf8_to_wide(const std::string &input) return out; } -#ifdef __ANDROID__ -// TODO: this is an ugly fix for wide_to_utf8 somehow not working on android -std::string wide_to_utf8(const std::wstring &input) -{ - return wide_to_narrow(input); -} -#else std::string wide_to_utf8(const std::wstring &input) { size_t inbuf_size = (input.length() + 1) * sizeof(wchar_t); @@ -123,7 +128,7 @@ std::string wide_to_utf8(const std::wstring &input) char *outbuf = new char[outbuf_size]; memset(outbuf, 0, outbuf_size); - if (!convert("UTF-8", "WCHAR_T", outbuf, outbuf_size, inbuf, inbuf_size)) { + if (!convert("UTF-8", DEFAULT_ENCODING, outbuf, outbuf_size, inbuf, inbuf_size)) { infostream << "Couldn't convert wstring 0x" << hex_encode(inbuf, inbuf_size) << " into UTF-8 string" << std::endl; delete[] inbuf; @@ -138,7 +143,6 @@ std::string wide_to_utf8(const std::wstring &input) return out; } -#endif #else // _WIN32 std::wstring utf8_to_wide(const std::string &input) @@ -183,7 +187,7 @@ wchar_t *utf8_to_wide_c(const char *str) // The returned string is allocated using new wchar_t *narrow_to_wide_c(const char *str) { - wchar_t *nstr = NULL; + wchar_t *nstr = nullptr; #if defined(_WIN32) int nResult = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) str, -1, 0, 0); if (nResult == 0) { @@ -204,67 +208,8 @@ wchar_t *narrow_to_wide_c(const char *str) return nstr; } - -#ifdef __ANDROID__ - -const wchar_t* wide_chars = - L" !\"#$%&'()*+,-./0123456789:;<=>?@" - L"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" - L"abcdefghijklmnopqrstuvwxyz{|}~"; - -int wctomb(char *s, wchar_t wc) -{ - for (unsigned int j = 0; j < (sizeof(wide_chars)/sizeof(wchar_t));j++) { - if (wc == wide_chars[j]) { - *s = (char) (j+32); - return 1; - } - else if (wc == L'\n') { - *s = '\n'; - return 1; - } - } - return -1; -} - -int mbtowc(wchar_t *pwc, const char *s, size_t n) -{ - std::wstring intermediate = narrow_to_wide(s); - - if (intermediate.length() > 0) { - *pwc = intermediate[0]; - return 1; - } - else { - return -1; - } -} - std::wstring narrow_to_wide(const std::string &mbs) { size_t wcl = mbs.size(); - - std::wstring retval = L""; - - for (unsigned int i = 0; i < wcl; i++) { - if (((unsigned char) mbs[i] >31) && - ((unsigned char) mbs[i] < 127)) { - - retval += wide_chars[(unsigned char) mbs[i] -32]; - } - //handle newline - else if (mbs[i] == '\n') { - retval += L'\n'; - } - } - - return retval; -} - -#else // not Android - -std::wstring narrow_to_wide(const std::string &mbs) -{ - size_t wcl = mbs.size(); Buffer<wchar_t> wcs(wcl + 1); size_t len = mbstowcs(*wcs, mbs.c_str(), wcl); if (len == (size_t)(-1)) @@ -273,37 +218,6 @@ std::wstring narrow_to_wide(const std::string &mbs) return *wcs; } -#endif - -#ifdef __ANDROID__ - -std::string wide_to_narrow(const std::wstring &wcs) { - size_t mbl = wcs.size()*4; - - std::string retval = ""; - for (unsigned int i = 0; i < wcs.size(); i++) { - wchar_t char1 = (wchar_t) wcs[i]; - - if (char1 == L'\n') { - retval += '\n'; - continue; - } - - for (unsigned int j = 0; j < wcslen(wide_chars);j++) { - wchar_t char2 = (wchar_t) wide_chars[j]; - - if (char1 == char2) { - char toadd = (j+32); - retval += toadd; - break; - } - } - } - - return retval; -} - -#else // not Android std::string wide_to_narrow(const std::wstring &wcs) { @@ -317,7 +231,6 @@ std::string wide_to_narrow(const std::wstring &wcs) return *mbs; } -#endif std::string urlencode(const std::string &str) { @@ -361,10 +274,10 @@ u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask) u32 mask = 0; char *s = &str[0]; char *flagstr; - char *strpos = NULL; + char *strpos = nullptr; while ((flagstr = strtok_r(s, ",", &strpos))) { - s = NULL; + s = nullptr; while (*flagstr == ' ' || *flagstr == '\t') flagstr++; @@ -436,7 +349,7 @@ char *mystrtok_r(char *s, const char *sep, char **lasts) s++; if (!*s) - return NULL; + return nullptr; t = s; while (*t) { diff --git a/src/util/string.h b/src/util/string.h index ab9a4a6c8..0d2a6bdb2 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_bloated.h" +#include "irrString.h" #include <cstdlib> #include <string> #include <cstring> @@ -723,3 +724,21 @@ inline std::string str_join(const std::vector<std::string> &list, } return oss.str(); } + +/** + * Create a UTF8 std::string from a irr::core::stringw. + */ +inline std::string stringw_to_utf8(const irr::core::stringw &input) +{ + std::wstring str(input.c_str()); + return wide_to_utf8(str); +} + + /** + * Create a irr::core:stringw from a UTF8 std::string. + */ +inline irr::core::stringw utf8_to_stringw(const std::string &input) +{ + std::wstring str = utf8_to_wide(input); + return irr::core::stringw(str.c_str()); +} |