From 997be666e37e9494bb92a50521b4b749156a609c Mon Sep 17 00:00:00 2001 From: paramat Date: Wed, 17 Feb 2016 02:39:21 +0000 Subject: [PATCH 01/17] Clientmap: Define p_nodes_min/max as v3s32 instead of v3s16 'cam_pos_nodes -/+ box_nodes_d' can exceed the range of v3s16 when a player is near the world edge using a large view range This previously caused world to disappear Create new function getBlocksInViewRange() called from updateDrawList() and renderMap() Correct code style throughout updateDrawList() and renderMap() --- src/clientmap.cpp | 253 +++++++++++++++++++++------------------------- src/clientmap.h | 2 + 2 files changed, 116 insertions(+), 139 deletions(-) diff --git a/src/clientmap.cpp b/src/clientmap.cpp index e67b6cbf7..ea2b8e4e3 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -141,6 +141,33 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, return false; } +void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, + v3s16 *p_blocks_min, v3s16 *p_blocks_max) +{ + v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1); + // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d' + // can exceed the range of v3s16 when a large view range is used near the + // world edges. + v3s32 p_nodes_min( + cam_pos_nodes.X - box_nodes_d.X, + cam_pos_nodes.Y - box_nodes_d.Y, + cam_pos_nodes.Z - box_nodes_d.Z); + v3s32 p_nodes_max( + cam_pos_nodes.X + box_nodes_d.X, + cam_pos_nodes.Y + box_nodes_d.Y, + cam_pos_nodes.Z + box_nodes_d.Z); + // Take a fair amount as we will be dropping more out later + // Umm... these additions are a bit strange but they are needed. + *p_blocks_min = v3s16( + p_nodes_min.X / MAP_BLOCKSIZE - 3, + p_nodes_min.Y / MAP_BLOCKSIZE - 3, + p_nodes_min.Z / MAP_BLOCKSIZE - 3); + *p_blocks_max = v3s16( + p_nodes_max.X / MAP_BLOCKSIZE + 1, + p_nodes_max.Y / MAP_BLOCKSIZE + 1, + p_nodes_max.Z / MAP_BLOCKSIZE + 1); +} + void ClientMap::updateDrawList(video::IVideoDriver* driver) { ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); @@ -148,10 +175,8 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) INodeDefManager *nodemgr = m_gamedef->ndef(); - for(std::map::iterator - i = m_drawlist.begin(); - i != m_drawlist.end(); ++i) - { + for (std::map::iterator i = m_drawlist.begin(); + i != m_drawlist.end(); ++i) { MapBlock *block = i->second; block->refDrop(); } @@ -167,19 +192,9 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) camera_fov *= 1.2; v3s16 cam_pos_nodes = floatToInt(camera_position, BS); - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); - v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; - v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; - // Take a fair amount as we will be dropping more out later - // Umm... these additions are a bit strange but they are needed. - v3s16 p_blocks_min( - p_nodes_min.X / MAP_BLOCKSIZE - 3, - p_nodes_min.Y / MAP_BLOCKSIZE - 3, - p_nodes_min.Z / MAP_BLOCKSIZE - 3); - v3s16 p_blocks_max( - p_nodes_max.X / MAP_BLOCKSIZE + 1, - p_nodes_max.Y / MAP_BLOCKSIZE + 1, - p_nodes_max.Z / MAP_BLOCKSIZE + 1); + v3s16 p_blocks_min; + v3s16 p_blocks_max; + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); // Number of blocks in rendering range u32 blocks_in_range = 0; @@ -199,19 +214,14 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) // Distance to farthest drawn block float farthest_drawn = 0; - for(std::map::iterator - si = m_sectors.begin(); - si != m_sectors.end(); ++si) - { + for (std::map::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { MapSector *sector = si->second; v2s16 sp = sector->getPos(); - if(m_control.range_all == false) - { - if(sp.X < p_blocks_min.X - || sp.X > p_blocks_max.X - || sp.Y < p_blocks_min.Z - || sp.Y > p_blocks_max.Z) + if (m_control.range_all == false) { + if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X || + sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z) continue; } @@ -224,9 +234,8 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) u32 sector_blocks_drawn = 0; - for(MapBlockVect::iterator i = sectorblocks.begin(); - i != sectorblocks.end(); ++i) - { + for (MapBlockVect::iterator i = sectorblocks.begin(); + i != sectorblocks.end(); ++i) { MapBlock *block = *i; /* @@ -238,16 +247,13 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) block->mesh->updateCameraOffset(m_camera_offset); float range = 100000 * BS; - if(m_control.range_all == false) + if (m_control.range_all == false) range = m_control.wanted_range * BS; float d = 0.0; - if(isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, - range, &d) == false) - { + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, range, &d)) continue; - } // This is ugly (spherical distance limit?) /*if(m_control.range_all == false && @@ -262,7 +268,7 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) { //MutexAutoLock lock(block->mesh_mutex); - if(block->mesh == NULL){ + if (block->mesh == NULL) { blocks_in_range_without_mesh++; continue; } @@ -275,44 +281,41 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) // No occlusion culling when free_move is on and camera is // inside ground bool occlusion_culling_enabled = true; - if(g_settings->getBool("free_move")){ + if (g_settings->getBool("free_move")) { MapNode n = getNodeNoEx(cam_pos_nodes); - if(n.getContent() == CONTENT_IGNORE || + if (n.getContent() == CONTENT_IGNORE || nodemgr->get(n).solidness == 2) occlusion_culling_enabled = false; } v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; - cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); - float step = BS*1; + cpn += v3s16(MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2); + float step = BS * 1; float stepfac = 1.1; - float startoff = BS*1; - float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; - v3s16 spn = cam_pos_nodes + v3s16(0,0,0); - s16 bs2 = MAP_BLOCKSIZE/2 + 1; + float startoff = BS * 1; + float endoff = -BS*MAP_BLOCKSIZE * 1.42 * 1.42; + v3s16 spn = cam_pos_nodes + v3s16(0, 0, 0); + s16 bs2 = MAP_BLOCKSIZE / 2 + 1; u32 needed_count = 1; - if( - occlusion_culling_enabled && - isOccluded(this, spn, cpn + v3s16(0,0,0), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) && - isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), - step, stepfac, startoff, endoff, needed_count, nodemgr) - ) - { + if (occlusion_culling_enabled && + isOccluded(this, spn, cpn + v3s16(0, 0, 0), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr)) { blocks_occlusion_culled++; continue; } @@ -322,9 +325,9 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) // Limit block count in case of a sudden increase blocks_would_have_drawn++; - if(blocks_drawn >= m_control.wanted_max_blocks - && m_control.range_all == false - && d > m_control.wanted_min_range * BS) + if (blocks_drawn >= m_control.wanted_max_blocks && + !m_control.range_all && + d > m_control.wanted_min_range * BS) continue; // Add to set @@ -333,12 +336,12 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) sector_blocks_drawn++; blocks_drawn++; - if(d/BS > farthest_drawn) - farthest_drawn = d/BS; + if (d / BS > farthest_drawn) + farthest_drawn = d / BS; } // foreach sectorblocks - if(sector_blocks_drawn != 0) + if (sector_blocks_drawn != 0) m_last_drawn_sectors.insert(sp); } @@ -348,9 +351,9 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) g_profiler->avg("CM: blocks in range", blocks_in_range); g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); - if(blocks_in_range != 0) + if (blocks_in_range != 0) g_profiler->avg("CM: blocks in range without mesh (frac)", - (float)blocks_in_range_without_mesh/blocks_in_range); + (float)blocks_in_range_without_mesh / blocks_in_range); g_profiler->avg("CM: blocks drawn", blocks_drawn); g_profiler->avg("CM: farthest drawn", farthest_drawn); g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks); @@ -402,7 +405,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; std::string prefix; - if(pass == scene::ESNRP_SOLID) + if (pass == scene::ESNRP_SOLID) prefix = "CM: solid: "; else prefix = "CM: transparent: "; @@ -410,10 +413,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) /* This is called two times per frame, reset on the non-transparent one */ - if(pass == scene::ESNRP_SOLID) - { + if (pass == scene::ESNRP_SOLID) m_last_drawn_sectors.clear(); - } /* Get time for measuring timeout. @@ -439,22 +440,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) */ v3s16 cam_pos_nodes = floatToInt(camera_position, BS); - - v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); - - v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; - v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; - - // Take a fair amount as we will be dropping more out later - // Umm... these additions are a bit strange but they are needed. - v3s16 p_blocks_min( - p_nodes_min.X / MAP_BLOCKSIZE - 3, - p_nodes_min.Y / MAP_BLOCKSIZE - 3, - p_nodes_min.Z / MAP_BLOCKSIZE - 3); - v3s16 p_blocks_max( - p_nodes_max.X / MAP_BLOCKSIZE + 1, - p_nodes_max.Y / MAP_BLOCKSIZE + 1, - p_nodes_max.Z / MAP_BLOCKSIZE + 1); + v3s16 p_blocks_min; + v3s16 p_blocks_max; + getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max); u32 vertex_count = 0; u32 meshbuffer_count = 0; @@ -475,27 +463,22 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) */ { - ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); + ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG); MeshBufListList drawbufs; - for(std::map::iterator - i = m_drawlist.begin(); - i != m_drawlist.end(); ++i) - { + for (std::map::iterator i = m_drawlist.begin(); + i != m_drawlist.end(); ++i) { MapBlock *block = i->second; // If the mesh of the block happened to get deleted, ignore it - if(block->mesh == NULL) + if (block->mesh == NULL) continue; float d = 0.0; - if(isBlockInSight(block->getPos(), camera_position, - camera_direction, camera_fov, - 100000*BS, &d) == false) - { + if (!isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, 100000 * BS, &d)) continue; - } // Mesh animation { @@ -503,24 +486,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); // Pretty random but this should work somewhat nicely - bool faraway = d >= BS*50; + bool faraway = d >= BS * 50; //bool faraway = d >= m_control.wanted_range * BS; - if(mapBlockMesh->isAnimationForced() || - !faraway || - mesh_animate_count_far < (m_control.range_all ? 200 : 50)) - { - bool animated = mapBlockMesh->animate( - faraway, - animation_time, - crack, - daynight_ratio); - if(animated) + if (mapBlockMesh->isAnimationForced() || !faraway || + mesh_animate_count_far < (m_control.range_all ? 200 : 50)) { + bool animated = mapBlockMesh->animate(faraway, animation_time, + crack, daynight_ratio); + if (animated) mesh_animate_count++; - if(animated && faraway) + if (animated && faraway) mesh_animate_count_far++; - } - else - { + } else { mapBlockMesh->decreaseAnimationForceTimer(); } } @@ -538,7 +514,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) assert(mesh); u32 c = mesh->getMeshBufferCount(); - for(u32 i=0; igetMeshBuffer(i); @@ -550,11 +526,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) video::IMaterialRenderer* rnd = driver->getMaterialRenderer(material.MaterialType); bool transparent = (rnd && rnd->isTransparent()); - if(transparent == is_transparent_pass) - { - if(buf->getVertexCount() == 0) - errorstream<<"Block ["<getVertexCount() == 0) + errorstream << "Block [" << analyze_block(block) + << "] contains an empty meshbuf" << std::endl; drawbufs.add(buf); } } @@ -564,13 +539,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) std::vector &lists = drawbufs.lists; int timecheck_counter = 0; - for(std::vector::iterator i = lists.begin(); + for (std::vector::iterator i = lists.begin(); i != lists.end(); ++i) { timecheck_counter++; - if(timecheck_counter > 50) { + if (timecheck_counter > 50) { timecheck_counter = 0; int time2 = time(0); - if(time2 > time1 + 4) { + if (time2 > time1 + 4) { infostream << "ClientMap::renderMap(): " "Rendering takes ages, returning." << std::endl; @@ -582,7 +557,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) driver->setMaterial(list.m); - for(std::vector::iterator j = list.bufs.begin(); + for (std::vector::iterator j = list.bufs.begin(); j != list.bufs.end(); ++j) { scene::IMeshBuffer *buf = *j; driver->drawMeshBuffer(buf); @@ -594,18 +569,18 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) } // ScopeProfiler // Log only on solid pass because values are the same - if(pass == scene::ESNRP_SOLID){ + if (pass == scene::ESNRP_SOLID) { g_profiler->avg("CM: animated meshes", mesh_animate_count); g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); } - g_profiler->avg(prefix+"vertices drawn", vertex_count); - if(blocks_had_pass_meshbuf != 0) - g_profiler->avg(prefix+"meshbuffers per block", - (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); - if(blocks_drawn != 0) - g_profiler->avg(prefix+"empty blocks (frac)", - (float)blocks_without_stuff / blocks_drawn); + g_profiler->avg(prefix + "vertices drawn", vertex_count); + if (blocks_had_pass_meshbuf != 0) + g_profiler->avg(prefix + "meshbuffers per block", + (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); + if (blocks_drawn != 0) + g_profiler->avg(prefix + "empty blocks (frac)", + (float)blocks_without_stuff / blocks_drawn); /*infostream<<"renderMap(): is_transparent_pass="< Date: Thu, 18 Feb 2016 16:06:07 -0500 Subject: [PATCH 02/17] Require request_insecure_environment to be called from the mod's main scope Previously you could steal a secure environment from a trusted mod by wrapping request_insecure_environment with some code like this: local rie_cp = minetest.request_insecure_environment local stolen_ie function minetest.request_insecure_environment() local ie = rie_cp() stolen_ie = stolen_ie or ie return ie end --- doc/lua_api.txt | 2 +- src/script/lua_api/l_util.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index d9a8bea97..2df0cac7c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2437,7 +2437,7 @@ These functions return the leftover itemstack. * `minetest.request_insecure_environment()`: returns an environment containing insecure functions if the calling mod has been listed as trusted in the `secure.trusted_mods` setting or security is disabled, otherwise returns `nil`. - * Only works at init time. + * Only works at init time and must be called from the mod's main scope (not from a function). * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED ENVIRONMENT, STORE IT IN A LOCAL VARIABLE!** diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 39863b987..c1e883a98 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -357,22 +357,44 @@ int ModApiUtil::l_get_dir_list(lua_State *L) int ModApiUtil::l_request_insecure_environment(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + // Just return _G if security is disabled if (!ScriptApiSecurity::isSecure(L)) { lua_getglobal(L, "_G"); return 1; } + + // We have to make sure that this function is being called directly by + // a mod, otherwise a malicious mod could override this function and + // steal its return value. + lua_Debug info; + // Make sure there's only one item below this function on the stack... + if (lua_getstack(L, 2, &info)) { + return 0; + } + assert(lua_getstack(L, 1, &info)); + assert(lua_getinfo(L, "S", &info)); + // ...and that that item is the main file scope. + if (strcmp(info.what, "main") != 0) { + return 0; + } + + // Get mod name lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); if (!lua_isstring(L, -1)) { - lua_pushnil(L); - return 1; + return 0; } + + // Check secure.trusted_mods const char *mod_name = lua_tostring(L, -1); std::string trusted_mods = g_settings->get("secure.trusted_mods"); std::vector mod_list = str_split(trusted_mods, ','); - if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) { - lua_pushnil(L); - return 1; + if (std::find(mod_list.begin(), mod_list.end(), mod_name) == + mod_list.end()) { + return 0; } + + // Push insecure environment lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); return 1; } From 5dbaa689677e89ee10e2c6e3a4e8eb57b79f3140 Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Fri, 19 Feb 2016 14:13:21 +0100 Subject: [PATCH 03/17] Camera: Don't count camera offset twice for Nametagged CAOs --- src/camera.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/camera.cpp b/src/camera.cpp index 8aac32123..0ce6bb345 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -675,8 +675,7 @@ void Camera::drawNametags() i = m_nametags.begin(); i != m_nametags.end(); ++i) { Nametag *nametag = *i; - v3f pos = nametag->parent_node->getPosition() - - intToFloat(m_camera_offset, BS) + v3f(0.0, 1.1 * BS, 0.0); + v3f pos = nametag->parent_node->getPosition() + v3f(0.0, 1.1 * BS, 0.0); f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f }; trans.multiplyWith1x4Matrix(transformed_pos); if (transformed_pos[3] > 0) { From 5f4ed94d90668af58a3e677e7401f0028871acce Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Thu, 18 Feb 2016 17:17:17 +0100 Subject: [PATCH 04/17] Minimap: show player markers --- src/camera.h | 3 + src/minimap.cpp | 69 +++++++++++++++++++++++ src/minimap.h | 6 ++ textures/base/pack/object_marker_red.png | Bin 0 -> 663 bytes 4 files changed, 78 insertions(+) create mode 100644 textures/base/pack/object_marker_red.png diff --git a/src/camera.h b/src/camera.h index 2df7da2b8..36fc72f1a 100644 --- a/src/camera.h +++ b/src/camera.h @@ -172,6 +172,9 @@ public: void removeNametag(Nametag *nametag); + std::list *getNametags() + { return &m_nametags; } + void drawNametags(); private: diff --git a/src/minimap.cpp b/src/minimap.cpp index ded8470c5..8cd0a7beb 100644 --- a/src/minimap.cpp +++ b/src/minimap.cpp @@ -211,6 +211,7 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar) Mapper::Mapper(IrrlichtDevice *device, Client *client) { + this->client = client; this->driver = device->getVideoDriver(); this->m_tsrc = client->getTextureSource(); this->m_shdrsrc = client->getShaderSource(); @@ -250,6 +251,8 @@ Mapper::Mapper(IrrlichtDevice *device, Client *client) // Create player marker texture data->player_marker = m_tsrc->getTexture("player_marker.png"); + // Create object marker texture + data->object_marker_red = m_tsrc->getTexture("object_marker_red.png"); // Create mesh buffer for minimap m_meshbuffer = getMinimapMeshBuffer(); @@ -274,6 +277,7 @@ Mapper::~Mapper() driver->removeTexture(data->heightmap_texture); driver->removeTexture(data->minimap_overlay_round); driver->removeTexture(data->minimap_overlay_square); + driver->removeTexture(data->object_marker_red); delete data; delete m_minimap_update_thread; @@ -468,6 +472,7 @@ void Mapper::drawMinimap() if (!minimap_texture) return; + updateActiveMarkers(); v2u32 screensize = porting::getWindowSize(); const u32 size = 0.25 * screensize.Y; @@ -527,6 +532,70 @@ void Mapper::drawMinimap() driver->setTransform(video::ETS_VIEW, oldViewMat); driver->setTransform(video::ETS_PROJECTION, oldProjMat); driver->setViewPort(oldViewPort); + + // Draw player markers + v2s32 s_pos(screensize.X - size - 10, 10); + core::dimension2di imgsize(data->object_marker_red->getOriginalSize()); + core::rect img_rect(0, 0, imgsize.Width, imgsize.Height); + static const video::SColor col(255, 255, 255, 255); + static const video::SColor c[4] = {col, col, col, col}; + f32 sin_angle = sin(m_angle * core::DEGTORAD); + f32 cos_angle = cos(m_angle * core::DEGTORAD); + s32 marker_size2 = 0.025 * (float)size; + for (std::list::const_iterator + i = m_active_markers.begin(); + i != m_active_markers.end(); ++i) { + v2f posf = *i; + if (data->minimap_shape_round) { + f32 t1 = posf.X * cos_angle - posf.Y * sin_angle; + f32 t2 = posf.X * sin_angle + posf.Y * cos_angle; + posf.X = t1; + posf.Y = t2; + } + posf.X = (posf.X + 0.5) * (float)size; + posf.Y = (posf.Y + 0.5) * (float)size; + core::rect dest_rect( + s_pos.X + posf.X - marker_size2, + s_pos.Y + posf.Y - marker_size2, + s_pos.X + posf.X + marker_size2, + s_pos.Y + posf.Y + marker_size2); + driver->draw2DImage(data->object_marker_red, dest_rect, + img_rect, &dest_rect, &c[0], true); + } +} + +void Mapper::updateActiveMarkers () +{ + video::IImage *minimap_mask = data->minimap_shape_round ? + data->minimap_mask_round : data->minimap_mask_square; + + std::list *nametags = client->getCamera()->getNametags(); + + m_active_markers.clear(); + + for (std::list::const_iterator + i = nametags->begin(); + i != nametags->end(); ++i) { + Nametag *nametag = *i; + v3s16 pos = floatToInt(nametag->parent_node->getPosition() + + intToFloat(client->getCamera()->getOffset(), BS), BS); + pos -= data->pos - v3s16(data->map_size / 2, + data->scan_height / 2, + data->map_size / 2); + if (pos.X < 0 || pos.X > data->map_size || + pos.Y < 0 || pos.Y > data->scan_height || + pos.Z < 0 || pos.Z > data->map_size) { + continue; + } + pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX; + pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY; + video::SColor mask_col = minimap_mask->getPixel(pos.X, pos.Z); + if (!mask_col.getAlpha()) { + continue; + } + m_active_markers.push_back(v2f(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5, + (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5)); + } } //// diff --git a/src/minimap.h b/src/minimap.h index dd1397d54..743b2bff2 100644 --- a/src/minimap.h +++ b/src/minimap.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "camera.h" #define MINIMAP_MAX_SX 512 #define MINIMAP_MAX_SY 512 @@ -82,6 +83,7 @@ struct MinimapData { video::ITexture *minimap_overlay_round; video::ITexture *minimap_overlay_square; video::ITexture *player_marker; + video::ITexture *object_marker_red; }; struct QueuedMinimapUpdate { @@ -138,9 +140,12 @@ public: video::IImage *heightmap_image); scene::SMeshBuffer *getMinimapMeshBuffer(); + + void updateActiveMarkers(); void drawMinimap(); video::IVideoDriver *driver; + Client* client; MinimapData *data; private: @@ -153,6 +158,7 @@ private: u16 m_surface_mode_scan_height; f32 m_angle; Mutex m_mutex; + std::list m_active_markers; }; #endif diff --git a/textures/base/pack/object_marker_red.png b/textures/base/pack/object_marker_red.png new file mode 100644 index 0000000000000000000000000000000000000000..552a546e3e225942b9c8c47ca84adb6573ea369d GIT binary patch literal 663 zcmV;I0%-k-P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00I9=L_t(2&wbOqi(W+($MMgZ zx%YXNeQ`Arvg?WgH-#>QEiE*XLa>QwX<=ny>7O8irC=#o*hfgLg=}FF5UmUtkf{}8GUs#@(d+%86JgAKa4(H4azxkc{!tmc%6|P9Wh~ywB5soESg*UMRLY$U=;>F0` zKa*VEW@CHMjC)JdY<4%B{W5s;+hDI0pDKRPh=urtruFf~@am<`%7sDRtTrb&!kwht z%ua4`qo4Z_w=z~D`(QVDXmy5JRoWnYv#xpoqSSv<tx|r-Ssfy<4tGFOzj2Z4F?S5Epf=klCB+2n4o1avN^gmfD z{Ylmh$-`9m`ffHmtnX>7!=f(w(J6r=F8L-IT>W|Q_)g-85*5rsFsf@fCT<7! zizA&cv+!WTg;}=q1f%7`(!ZSJbfU6Pe3#hEW Date: Fri, 19 Feb 2016 21:14:38 +0100 Subject: [PATCH 05/17] Ignore spaces in secure.trusted_mods setting --- src/script/lua_api/l_util.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index c1e883a98..c04f09f90 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -388,6 +388,8 @@ int ModApiUtil::l_request_insecure_environment(lua_State *L) // Check secure.trusted_mods const char *mod_name = lua_tostring(L, -1); std::string trusted_mods = g_settings->get("secure.trusted_mods"); + trusted_mods.erase(std::remove(trusted_mods.begin(), + trusted_mods.end(), ' '), trusted_mods.end()); std::vector mod_list = str_split(trusted_mods, ','); if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) { From a44393e43a39d2b78ff6673fc646be44698c9d37 Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Fri, 19 Feb 2016 19:55:31 -0500 Subject: [PATCH 06/17] Don't print locale directory error message when GetText is disabled Also, downgrade the error to a warning. --- src/porting.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/porting.cpp b/src/porting.cpp index fd1915b0d..98b85b7d0 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -558,8 +558,9 @@ void initializePaths() infostream << "Detected user path: " << path_user << std::endl; infostream << "Detected cache path: " << path_cache << std::endl; +#ifdef USE_GETTEXT bool found_localedir = false; -#ifdef STATIC_LOCALEDIR +# ifdef STATIC_LOCALEDIR if (STATIC_LOCALEDIR[0] && fs::PathExists(STATIC_LOCALEDIR)) { found_localedir = true; path_locale = STATIC_LOCALEDIR; @@ -573,15 +574,16 @@ void initializePaths() << "(RUN_IN_PLACE or CUSTOM_LOCALEDIR)." << std::endl; } } -#else +# else path_locale = getDataPath("locale"); if (fs::PathExists(path_locale)) { found_localedir = true; } -#endif +# endif if (!found_localedir) { - errorstream << "Couldn't find a locale directory!" << std::endl; + warningstream << "Couldn't find a locale directory!" << std::endl; } +#endif // USE_GETTEXT } From 60dc01dc258db842e229351b871d0989e3e7d62c Mon Sep 17 00:00:00 2001 From: gregorycu Date: Tue, 27 Jan 2015 23:33:54 +1100 Subject: [PATCH 07/17] Fix jumping at node edge --- src/collision.cpp | 66 ++++++++++++----------------------------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 2ae59b38f..a2d17d51a 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -248,8 +248,9 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, bool any_position_valid = false; + // The order is important here, must be y first + for(s16 y = max_y; y >= min_y; y--) for(s16 x = min_x; x <= max_x; x++) - for(s16 y = min_y; y <= max_y; y++) for(s16 z = min_z; z <= max_z; z++) { v3s16 p(x,y,z); @@ -404,15 +405,17 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, Go through every nodebox, find nearest collision */ for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { - // Ignore if already stepped up this nodebox. - if(is_step_up[boxindex]) - continue; - // Find nearest collision of the two boxes (raytracing-like) f32 dtime_tmp; int collided = axisAlignedCollision( cboxes[boxindex], movingbox, *speed_f, d, &dtime_tmp); + // Ignore if already stepped up this nodebox. + if (is_step_up[boxindex]) { + pos_f->Y += (cboxes[boxindex].MaxEdge.Y - movingbox.MinEdge.Y); + continue; + } + if (collided == -1 || dtime_tmp >= nearest_dtime) continue; @@ -462,10 +465,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, is_collision = false; CollisionInfo info; - if (is_object[nearest_boxindex]) + if (is_object[nearest_boxindex]) { info.type = COLLISION_OBJECT; - else + result.standing_on_object = true; + } else { info.type = COLLISION_NODE; + } info.node_p = node_positions[nearest_boxindex]; info.bouncy = bouncy; @@ -483,12 +488,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, speed_f->X = 0; result.collides = true; result.collides_xz = true; - } - else if(nearest_collided == 1) { // Y - if (fabs(speed_f->Y) > BS * 3) + } else if(nearest_collided == 1) { // Y + if (fabs(speed_f->Y) > BS * 3) { speed_f->Y *= bounce; - else + } else { speed_f->Y = 0; + result.touching_ground = true; + } result.collides = true; } else if(nearest_collided == 2) { // Z if (fabs(speed_f->Z) > BS * 3) @@ -509,43 +515,5 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } } - /* - Final touches: Check if standing on ground, step up stairs. - */ - aabb3f box = box_0; - box.MinEdge += *pos_f; - box.MaxEdge += *pos_f; - for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { - const aabb3f& cbox = cboxes[boxindex]; - - /* - See if the object is touching ground. - - Object touches ground if object's minimum Y is near node's - maximum Y and object's X-Z-area overlaps with the node's - X-Z-area. - - Use 0.15*BS so that it is easier to get on a node. - */ - if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X && - cbox.MaxEdge.Z - d > box.MinEdge.Z && - cbox.MinEdge.Z + d < box.MaxEdge.Z) { - if (is_step_up[boxindex]) { - pos_f->Y += (cbox.MaxEdge.Y - box.MinEdge.Y); - box = box_0; - box.MinEdge += *pos_f; - box.MaxEdge += *pos_f; - } - if (fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15 * BS) { - result.touching_ground = true; - - if (is_object[boxindex]) - result.standing_on_object = true; - if (is_unloaded[boxindex]) - result.standing_on_unloaded = true; - } - } - } - return result; } From c6d7d2097c3736b3de82f074c63eda959c07290f Mon Sep 17 00:00:00 2001 From: Loic Blot Date: Sat, 20 Feb 2016 09:44:22 +0100 Subject: [PATCH 08/17] Little collision.cpp cleanups --- src/collision.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index a2d17d51a..4680e0a5e 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -235,7 +235,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, std::vector node_positions; { //TimeTaker tt2("collisionMoveSimple collect boxes"); - ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); + ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); v3s16 oldpos_i = floatToInt(*pos_f, BS); v3s16 newpos_i = floatToInt(*pos_f + *speed_f * dtime, BS); @@ -383,13 +383,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, while(dtime > BS * 1e-10) { //TimeTaker tt3("collisionMoveSimple dtime loop"); - ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG); + ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG); // Avoid infinite loop loopcount++; if (loopcount >= 100) { warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl; - dtime = 0; break; } @@ -399,7 +398,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, int nearest_collided = -1; f32 nearest_dtime = dtime; - u32 nearest_boxindex = -1; + int nearest_boxindex = -1; /* Go through every nodebox, find nearest collision From ecc8b70182b9ad3aaec910d7c8b32fc54cac824a Mon Sep 17 00:00:00 2001 From: BlockMen Date: Fri, 19 Feb 2016 10:03:02 +0100 Subject: [PATCH 09/17] Restore simple settings tab and add advanced settings as dialog --- builtin/mainmenu/dlg_settings_advanced.lua | 756 ++++++++++++++ builtin/mainmenu/init.lua | 1 + builtin/mainmenu/tab_settings.lua | 1028 +++++++------------- 3 files changed, 1106 insertions(+), 679 deletions(-) create mode 100644 builtin/mainmenu/dlg_settings_advanced.lua diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua new file mode 100644 index 000000000..35883f05f --- /dev/null +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -0,0 +1,756 @@ +--Minetest +--Copyright (C) 2015 PilzAdam +-- +--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. + +local FILENAME = "settingtypes.txt" + +local CHAR_CLASSES = { + SPACE = "[%s]", + VARIABLE = "[%w_%-%.]", + INTEGER = "[+-]?[%d]", + FLOAT = "[+-]?[%d%.]", + FLAGS = "[%w_%-%.,]", +} + +-- returns error message, or nil +local function parse_setting_line(settings, line, read_all, base_level, allow_secure) + -- comment + local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") + if comment then + if settings.current_comment == "" then + settings.current_comment = comment + else + settings.current_comment = settings.current_comment .. "\n" .. comment + end + return + end + + -- clear current_comment so only comments directly above a setting are bound to it + -- but keep a local reference to it for variables in the current line + local current_comment = settings.current_comment + settings.current_comment = "" + + -- empty lines + if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then + return + end + + -- category + local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") + if category then + table.insert(settings, { + name = category, + level = stars:len() + base_level, + type = "category", + }) + return + end + + -- settings + local first_part, name, readable_name, setting_type = line:match("^" + -- this first capture group matches the whole first part, + -- so we can later strip it from the rest of the line + .. "(" + .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name + .. CHAR_CLASSES.SPACE .. "*" + .. "%(([^%)]*)%)" -- readable name + .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type + .. CHAR_CLASSES.SPACE .. "*" + .. ")") + + if not first_part then + return "Invalid line" + end + + if name:match("secure%.[.]*") and not allow_secure then + return "Tried to add \"secure.\" setting" + end + + if readable_name == "" then + readable_name = nil + end + local remaining_line = line:sub(first_part:len() + 1) + + if setting_type == "int" then + local default, min, max = remaining_line:match("^" + -- first int is required, the last 2 are optional + .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.INTEGER .. "*)" + .. "$") + + if not default or not tonumber(default) then + return "Invalid integer setting" + end + + min = tonumber(min) + max = tonumber(max) + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "int", + default = default, + min = min, + max = max, + comment = current_comment, + }) + return + end + + if setting_type == "string" or setting_type == "noise_params" + or setting_type == "key" or setting_type == "v3f" then + local default = remaining_line:match("^(.*)$") + + if not default then + return "Invalid string setting" + end + if setting_type == "key" and not read_all then + -- ignore key type if read_all is false + return + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = setting_type, + default = default, + comment = current_comment, + }) + return + end + + if setting_type == "bool" then + if remaining_line ~= "false" and remaining_line ~= "true" then + return "Invalid boolean setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "bool", + default = remaining_line, + comment = current_comment, + }) + return + end + + if setting_type == "float" then + local default, min, max = remaining_line:match("^" + -- first float is required, the last 2 are optional + .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "?" + .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "?" + .. "(" .. CHAR_CLASSES.FLOAT .. "*)" + .."$") + + if not default or not tonumber(default) then + return "Invalid float setting" + end + + min = tonumber(min) + max = tonumber(max) + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "float", + default = default, + min = min, + max = max, + comment = current_comment, + }) + return + end + + if setting_type == "enum" then + local default, values = remaining_line:match("^" + -- first value (default) may be empty (i.e. is optional) + .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLAGS .. "+)" + .. "$") + + if not default or values == "" then + return "Invalid enum setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "enum", + default = default, + values = values:split(",", true), + comment = current_comment, + }) + return + end + + if setting_type == "path" then + local default = remaining_line:match("^(.*)$") + + if not default then + return "Invalid path setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "path", + default = default, + comment = current_comment, + }) + return + end + + if setting_type == "flags" then + local default, possible = remaining_line:match("^" + -- first value (default) may be empty (i.e. is optional) + -- this is implemented by making the last value optional, and + -- swapping them around if it turns out empty. + .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLAGS .. "*)" + .. "$") + + if not default or not possible then + return "Invalid flags setting" + end + + if possible == "" then + possible = default + default = "" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "flags", + default = default, + possible = possible, + comment = current_comment, + }) + return + end + + return "Invalid setting type \"" .. setting_type .. "\"" +end + +local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) + -- store this helper variable in the table so it's easier to pass to parse_setting_line() + result.current_comment = "" + + local line = file:read("*line") + while line do + local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) + if error_msg then + core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") + end + line = file:read("*line") + end + + result.current_comment = nil +end + +-- read_all: whether to ignore certain setting types for GUI or not +-- parse_mods: whether to parse settingtypes.txt in mods and games +local function parse_config_file(read_all, parse_mods) + local builtin_path = core.get_builtin_path() .. DIR_DELIM .. FILENAME + local file = io.open(builtin_path, "r") + local settings = {} + if not file then + core.log("error", "Can't load " .. FILENAME) + return settings + end + + parse_single_file(file, builtin_path, read_all, settings, 0, true) + + file:close() + + if parse_mods then + -- Parse games + local games_category_initialized = false + local index = 1 + local game = gamemgr.get_game(index) + while game do + local path = game.path .. DIR_DELIM .. FILENAME + local file = io.open(path, "r") + if file then + if not games_category_initialized then + local translation = fgettext_ne("Games"), -- not used, but needed for xgettext + table.insert(settings, { + name = "Games", + level = 0, + type = "category", + }) + games_category_initialized = true + end + + table.insert(settings, { + name = game.name, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + + index = index + 1 + game = gamemgr.get_game(index) + end + + -- Parse mods + local mods_category_initialized = false + local mods = {} + get_mods(core.get_modpath(), mods) + for _, mod in ipairs(mods) do + local path = mod.path .. DIR_DELIM .. FILENAME + local file = io.open(path, "r") + if file then + if not mods_category_initialized then + local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext + table.insert(settings, { + name = "Mods", + level = 0, + type = "category", + }) + mods_category_initialized = true + end + + table.insert(settings, { + name = mod.name, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + end + end + + return settings +end + +local settings = parse_config_file(false, true) +local selected_setting = 1 + +local function get_current_value(setting) + local value = core.setting_get(setting.name) + if value == nil then + value = setting.default + end + return value +end + +local function create_change_setting_formspec(dialogdata) + local setting = settings[selected_setting] + local formspec = "size[10,5.2,true]" .. + "button[5,4.5;2,1;btn_done;" .. fgettext("Save") .. "]" .. + "button[3,4.5;2,1;btn_cancel;" .. fgettext("Cancel") .. "]" .. + "tablecolumns[color;text]" .. + "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. + "table[0,0;10,3;info;" + + if setting.readable_name then + formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name) + .. " (" .. core.formspec_escape(setting.name) .. ")," + else + formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. "," + end + + formspec = formspec .. ",," + + local comment_text = "" + + if setting.comment == "" then + comment_text = fgettext_ne("(No description of setting given)") + else + comment_text = fgettext_ne(setting.comment) + end + for _, comment_line in ipairs(comment_text:split("\n", true)) do + formspec = formspec .. "," .. core.formspec_escape(comment_line) .. "," + end + + if setting.type == "flags" then + formspec = formspec .. ",," + .. "," .. fgettext("Please enter a comma seperated list of flags.") .. "," + .. "," .. fgettext("Possible values are: ") + .. core.formspec_escape(setting.possible:gsub(",", ", ")) .. "," + elseif setting.type == "noise_params" then + formspec = formspec .. ",," + .. "," .. fgettext("Format: , , (, , ), , , ") .. "," + .. "," .. fgettext("Optionally the lacunarity can be appended with a leading comma.") .. "," + elseif setting.type == "v3f" then + formspec = formspec .. ",," + .. "," .. fgettext_ne("Format is 3 numbers separated by commas and inside brackets.") .. "," + end + + formspec = formspec:sub(1, -2) -- remove trailing comma + + formspec = formspec .. ";1]" + + if setting.type == "bool" then + local selected_index + if core.is_yes(get_current_value(setting)) then + selected_index = 2 + else + selected_index = 1 + end + formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;" + .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";" + .. selected_index .. "]" + + elseif setting.type == "enum" then + local selected_index = 0 + formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;" + for index, value in ipairs(setting.values) do + -- translating value is not possible, since it's the value + -- that we set the setting to + formspec = formspec .. core.formspec_escape(value) .. "," + if get_current_value(setting) == value then + selected_index = index + end + end + if #setting.values > 0 then + formspec = formspec:sub(1, -2) -- remove trailing comma + end + formspec = formspec .. ";" .. selected_index .. "]" + + elseif setting.type == "path" then + local current_value = dialogdata.selected_path + if not current_value then + current_value = get_current_value(setting) + end + formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;" + .. core.formspec_escape(current_value) .. "]" + .. "button[8,3.75;2,1;btn_browser_path;" .. fgettext("Browse") .. "]" + + else + -- TODO: fancy input for float, int, flags, noise_params, v3f + local width = 10 + local text = get_current_value(setting) + if dialogdata.error_message then + formspec = formspec .. "tablecolumns[color;text]" .. + "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. + "table[5,3.9;5,0.6;error_message;#FF0000," + .. core.formspec_escape(dialogdata.error_message) .. ";0]" + width = 5 + if dialogdata.entered_text then + text = dialogdata.entered_text + end + end + formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;" + .. core.formspec_escape(text) .. "]" + end + return formspec +end + +local function handle_change_setting_buttons(this, fields) + if fields["btn_done"] or fields["key_enter"] then + local setting = settings[selected_setting] + if setting.type == "bool" then + local new_value = fields["dd_setting_value"] + -- Note: new_value is the actual (translated) value shown in the dropdown + core.setting_setbool(setting.name, new_value == fgettext("Enabled")) + + elseif setting.type == "enum" then + local new_value = fields["dd_setting_value"] + core.setting_set(setting.name, new_value) + + elseif setting.type == "int" then + local new_value = tonumber(fields["te_setting_value"]) + if not new_value or math.floor(new_value) ~= new_value then + this.data.error_message = fgettext_ne("Please enter a valid integer.") + this.data.entered_text = fields["te_setting_value"] + core.update_formspec(this:get_formspec()) + return true + end + if setting.min and new_value < setting.min then + this.data.error_message = fgettext_ne("The value must be greater than $1.", setting.min) + this.data.entered_text = fields["te_setting_value"] + core.update_formspec(this:get_formspec()) + return true + end + if setting.max and new_value > setting.max then + this.data.error_message = fgettext_ne("The value must be lower than $1.", setting.max) + this.data.entered_text = fields["te_setting_value"] + core.update_formspec(this:get_formspec()) + return true + end + core.setting_set(setting.name, new_value) + + elseif setting.type == "float" then + local new_value = tonumber(fields["te_setting_value"]) + if not new_value then + this.data.error_message = fgettext_ne("Please enter a valid number.") + this.data.entered_text = fields["te_setting_value"] + core.update_formspec(this:get_formspec()) + return true + end + core.setting_set(setting.name, new_value) + + elseif setting.type == "flags" then + local new_value = fields["te_setting_value"] + for _,value in ipairs(new_value:split(",", true)) do + value = value:trim() + local possible = "," .. setting.possible .. "," + if not possible:find("," .. value .. ",", 0, true) then + this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value) + this.data.entered_text = fields["te_setting_value"] + core.update_formspec(this:get_formspec()) + return true + end + end + core.setting_set(setting.name, new_value) + + else + local new_value = fields["te_setting_value"] + core.setting_set(setting.name, new_value) + end + core.setting_save() + this:delete() + return true + end + + if fields["btn_cancel"] then + this:delete() + return true + end + + if fields["btn_browser_path"] then + core.show_file_open_dialog("dlg_browse_path", fgettext_ne("Select path")) + end + + if fields["dlg_browse_path_accepted"] then + this.data.selected_path = fields["dlg_browse_path_accepted"] + core.update_formspec(this:get_formspec()) + end + + return false +end + +local function create_settings_formspec(tabview, name, tabdata) + local formspec = "size[12,6.5;true]" .. + "tablecolumns[color;tree;text;text]" .. + "tableoptions[background=#00000000;border=false]" .. + "table[0,0;12,5.5;list_settings;" + + local current_level = 0 + for _, entry in ipairs(settings) do + local name + if not core.setting_getbool("main_menu_technical_settings") and entry.readable_name then + name = fgettext_ne(entry.readable_name) + else + name = entry.name + end + + if entry.type == "category" then + current_level = entry.level + formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",," + + elseif entry.type == "bool" then + local value = get_current_value(entry) + if core.is_yes(value) then + value = fgettext("Enabled") + else + value = fgettext("Disabled") + end + formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," + .. value .. "," + + elseif entry.type == "key" then + -- ignore key settings, since we have a special dialog for them + + else + formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," + .. core.formspec_escape(get_current_value(entry)) .. "," + end + end + + if #settings > 0 then + formspec = formspec:sub(1, -2) -- remove trailing comma + end + formspec = formspec .. ";" .. selected_setting .. "]" .. + "button[0,6;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" .. + "button[10,6;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. + "button[7,6;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. + "checkbox[0,5.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";" + .. dump(core.setting_getbool("main_menu_technical_settings")) .. "]" + + return formspec +end + +local function handle_settings_buttons(this, fields, tabname, tabdata) + local list_enter = false + if fields["list_settings"] then + selected_setting = core.get_table_index("list_settings") + if core.explode_table_event(fields["list_settings"]).type == "DCL" then + -- Directly toggle booleans + local setting = settings[selected_setting] + if setting.type == "bool" then + local current_value = get_current_value(setting) + core.setting_setbool(setting.name, not core.is_yes(current_value)) + core.setting_save() + return true + else + list_enter = true + end + else + return true + end + end + + if fields["btn_edit"] or list_enter then + local setting = settings[selected_setting] + if setting.type ~= "category" then + local edit_dialog = dialog_create("change_setting", create_change_setting_formspec, + handle_change_setting_buttons) + edit_dialog:set_parent(this) + this:hide() + edit_dialog:show() + end + return true + end + + if fields["btn_restore"] then + local setting = settings[selected_setting] + if setting.type ~= "category" then + core.setting_set(setting.name, setting.default) + core.setting_save() + core.update_formspec(this:get_formspec()) + end + return true + end + + if fields["btn_back"] then + this:delete() + return true + end + + if fields["cb_tech_settings"] then + core.setting_set("main_menu_technical_settings", fields["cb_tech_settings"]) + core.setting_save() + core.update_formspec(this:get_formspec()) + return true + end + + return false +end + +function create_adv_settings_dlg() + local dlg = dialog_create("settings_advanced", + create_settings_formspec, + handle_settings_buttons, + nil) + + return dlg +end + +local function create_minetest_conf_example() + local result = "# This file contains a list of all available settings and their default value for minetest.conf\n" .. + "\n" .. + "# By default, all the settings are commented and not functional.\n" .. + "# Uncomment settings by removing the preceding #.\n" .. + "\n" .. + "# minetest.conf is read by default from:\n" .. + "# ../minetest.conf\n" .. + "# ../../minetest.conf\n" .. + "# Any other path can be chosen by passing the path as a parameter\n" .. + "# to the program, eg. \"minetest.exe --config ../minetest.conf.example\".\n" .. + "\n" .. + "# Further documentation:\n" .. + "# http://wiki.minetest.net/\n" .. + "\n" + + local settings = parse_config_file(true, false) + for _, entry in ipairs(settings) do + if entry.type == "category" then + if entry.level == 0 then + result = result .. "#\n# " .. entry.name .. "\n#\n\n" + else + for i = 1, entry.level do + result = result .. "#" + end + result = result .. "# " .. entry.name .. "\n\n" + end + else + if entry.comment ~= "" then + for _, comment_line in ipairs(entry.comment:split("\n", true)) do + result = result .."# " .. comment_line .. "\n" + end + end + result = result .. "# type: " .. entry.type + if entry.min then + result = result .. " min: " .. entry.min + end + if entry.max then + result = result .. " max: " .. entry.max + end + if entry.values then + result = result .. " values: " .. table.concat(entry.values, ", ") + end + if entry.possible then + result = result .. " possible values: " .. entry.possible:gsub(",", ", ") + end + result = result .. "\n" + result = result .. "# " .. entry.name .. " = ".. entry.default .. "\n\n" + end + end + return result +end + +local function create_translation_file() + local result = "// This file is automatically generated\n" .. + "// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files\n" .. + "// To update it, refer to the bottom of builtin/mainmenu/tab_settings.lua\n\n" .. + "fake_function() {\n" + + local settings = parse_config_file(true, false) + for _, entry in ipairs(settings) do + if entry.type == "category" then + local name_escaped = entry.name:gsub("\"", "\\\"") + result = result .. "\tgettext(\"" .. name_escaped .. "\");\n" + else + if entry.readable_name then + local readable_name_escaped = entry.readable_name:gsub("\"", "\\\"") + result = result .. "\tgettext(\"" .. readable_name_escaped .. "\");\n" + end + if entry.comment ~= "" then + local comment_escaped = entry.comment:gsub("\n", "\\n") + comment_escaped = comment_escaped:gsub("\"", "\\\"") + result = result .. "\tgettext(\"" .. comment_escaped .. "\");\n" + end + end + end + result = result .. "}\n" + return result +end + +if false then + local file = io.open("minetest.conf.example", "w") + if file then + file:write(create_minetest_conf_example()) + file:close() + end +end + +if false then + local file = io.open("src/settings_translation_file.cpp", "w") + if file then + file:write(create_translation_file()) + file:close() + end +end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 176796bef..69536630d 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -41,6 +41,7 @@ dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") dofile(menupath .. DIR_DELIM .. "tab_credits.lua") dofile(menupath .. DIR_DELIM .. "tab_mods.lua") dofile(menupath .. DIR_DELIM .. "tab_settings.lua") +dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") if PLATFORM ~= "Android" then dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_mod.lua") diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 99138a091..dc2bcff14 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -1,5 +1,5 @@ --Minetest ---Copyright (C) 2015 PilzAdam +--Copyright (C) 2013 sapier -- --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 @@ -15,739 +15,409 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -local FILENAME = "settingtypes.txt" +-------------------------------------------------------------------------------- -local CHAR_CLASSES = { - SPACE = "[%s]", - VARIABLE = "[%w_%-%.]", - INTEGER = "[+-]?[%d]", - FLOAT = "[+-]?[%d%.]", - FLAGS = "[%w_%-%.,]", +local leaves_style_labels = { + fgettext("Opaque Leaves"), + fgettext("Simple Leaves"), + fgettext("Fancy Leaves") } --- returns error message, or nil -local function parse_setting_line(settings, line, read_all, base_level, allow_secure) - -- comment - local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") - if comment then - if settings.current_comment == "" then - settings.current_comment = comment - else - settings.current_comment = settings.current_comment .. "\n" .. comment - end - return +local leaves_style = { + {leaves_style_labels[1] .. "," .. leaves_style_labels[2] .. "," .. leaves_style_labels[3]}, + {"opaque", "simple", "fancy"}, +} + +local dd_filter_labels = { + fgettext("No Filter"), + fgettext("Bilinear Filter"), + fgettext("Trilinear Filter") +} + +local filters = { + {dd_filter_labels[1] .. "," .. dd_filter_labels[2] .. "," .. dd_filter_labels[3]}, + {"", "bilinear_filter", "trilinear_filter"}, +} + +local dd_mipmap_labels = { + fgettext("No Mipmap"), + fgettext("Mipmap"), + fgettext("Mipmap + Aniso. Filter") +} + +local mipmap = { + {dd_mipmap_labels[1] .. "," .. dd_mipmap_labels[2] .. "," .. dd_mipmap_labels[3]}, + {"", "mip_map", "anisotropic_filter"}, +} + +local function getLeavesStyleSettingIndex() + local style = core.setting_get("leaves_style") + if (style == leaves_style[2][3]) then + return 3 + elseif (style == leaves_style[2][2]) then + return 2 end - - -- clear current_comment so only comments directly above a setting are bound to it - -- but keep a local reference to it for variables in the current line - local current_comment = settings.current_comment - settings.current_comment = "" - - -- empty lines - if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then - return - end - - -- category - local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") - if category then - table.insert(settings, { - name = category, - level = stars:len() + base_level, - type = "category", - }) - return - end - - -- settings - local first_part, name, readable_name, setting_type = line:match("^" - -- this first capture group matches the whole first part, - -- so we can later strip it from the rest of the line - .. "(" - .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name - .. CHAR_CLASSES.SPACE .. "*" - .. "%(([^%)]*)%)" -- readable name - .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type - .. CHAR_CLASSES.SPACE .. "*" - .. ")") - - if not first_part then - return "Invalid line" - end - - if name:match("secure%.[.]*") and not allow_secure then - return "Tried to add \"secure.\" setting" - end - - if readable_name == "" then - readable_name = nil - end - local remaining_line = line:sub(first_part:len() + 1) - - if setting_type == "int" then - local default, min, max = remaining_line:match("^" - -- first int is required, the last 2 are optional - .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.INTEGER .. "*)" - .. "$") - - if not default or not tonumber(default) then - return "Invalid integer setting" - end - - min = tonumber(min) - max = tonumber(max) - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "int", - default = default, - min = min, - max = max, - comment = current_comment, - }) - return - end - - if setting_type == "string" or setting_type == "noise_params" - or setting_type == "key" or setting_type == "v3f" then - local default = remaining_line:match("^(.*)$") - - if not default then - return "Invalid string setting" - end - if setting_type == "key" and not read_all then - -- ignore key type if read_all is false - return - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = setting_type, - default = default, - comment = current_comment, - }) - return - end - - if setting_type == "bool" then - if remaining_line ~= "false" and remaining_line ~= "true" then - return "Invalid boolean setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "bool", - default = remaining_line, - comment = current_comment, - }) - return - end - - if setting_type == "float" then - local default, min, max = remaining_line:match("^" - -- first float is required, the last 2 are optional - .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLOAT .. "*)" - .."$") - - if not default or not tonumber(default) then - return "Invalid float setting" - end - - min = tonumber(min) - max = tonumber(max) - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "float", - default = default, - min = min, - max = max, - comment = current_comment, - }) - return - end - - if setting_type == "enum" then - local default, values = remaining_line:match("^" - -- first value (default) may be empty (i.e. is optional) - .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLAGS .. "+)" - .. "$") - - if not default or values == "" then - return "Invalid enum setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "enum", - default = default, - values = values:split(",", true), - comment = current_comment, - }) - return - end - - if setting_type == "path" then - local default = remaining_line:match("^(.*)$") - - if not default then - return "Invalid path setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "path", - default = default, - comment = current_comment, - }) - return - end - - if setting_type == "flags" then - local default, possible = remaining_line:match("^" - -- first value (default) may be empty (i.e. is optional) - -- this is implemented by making the last value optional, and - -- swapping them around if it turns out empty. - .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLAGS .. "*)" - .. "$") - - if not default or not possible then - return "Invalid flags setting" - end - - if possible == "" then - possible = default - default = "" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "flags", - default = default, - possible = possible, - comment = current_comment, - }) - return - end - - return "Invalid setting type \"" .. setting_type .. "\"" + return 1 end -local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) - -- store this helper variable in the table so it's easier to pass to parse_setting_line() - result.current_comment = "" +local dd_antialiasing_labels = { + fgettext("None"), + fgettext("2x"), + fgettext("4x"), + fgettext("8x"), +} - local line = file:read("*line") - while line do - local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) - if error_msg then - core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") - end - line = file:read("*line") +local antialiasing = { + {dd_antialiasing_labels[1] .. "," .. dd_antialiasing_labels[2] .. "," .. + dd_antialiasing_labels[3] .. "," .. dd_antialiasing_labels[4]}, + {"0", "2", "4", "8"} +} + +local function getFilterSettingIndex() + if (core.setting_get(filters[2][3]) == "true") then + return 3 end - - result.current_comment = nil + if (core.setting_get(filters[2][3]) == "false" and core.setting_get(filters[2][2]) == "true") then + return 2 + end + return 1 end --- read_all: whether to ignore certain setting types for GUI or not --- parse_mods: whether to parse settingtypes.txt in mods and games -local function parse_config_file(read_all, parse_mods) - local builtin_path = core.get_builtin_path() .. DIR_DELIM .. FILENAME - local file = io.open(builtin_path, "r") - local settings = {} - if not file then - core.log("error", "Can't load " .. FILENAME) - return settings +local function getMipmapSettingIndex() + if (core.setting_get(mipmap[2][3]) == "true") then + return 3 end + if (core.setting_get(mipmap[2][3]) == "false" and core.setting_get(mipmap[2][2]) == "true") then + return 2 + end + return 1 +end - parse_single_file(file, builtin_path, read_all, settings, 0, true) +local function getAntialiasingSettingIndex() + local antialiasing_setting = core.setting_get("fsaa") + for i = 1, #(antialiasing[2]) do + if antialiasing_setting == antialiasing[2][i] then + return i + end + end + return 1 +end - file:close() +local function antialiasing_fname_to_name(fname) + for i = 1, #(dd_antialiasing_labels) do + if fname == dd_antialiasing_labels[i] then + return antialiasing[2][i] + end + end + return 0 +end - if parse_mods then - -- Parse games - local games_category_initialized = false - local index = 1 - local game = gamemgr.get_game(index) - while game do - local path = game.path .. DIR_DELIM .. FILENAME - local file = io.open(path, "r") - if file then - if not games_category_initialized then - local translation = fgettext_ne("Games"), -- not used, but needed for xgettext - table.insert(settings, { - name = "Games", - level = 0, - type = "category", - }) - games_category_initialized = true - end +local function dlg_confirm_reset_formspec(data) + local retval = + "size[8,3]" .. + "label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" .. + "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. + fgettext("Yes") .. "]" .. + "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. + fgettext("No!!!") .. "]" + return retval +end - table.insert(settings, { - name = game.name, - level = 1, - type = "category", - }) +local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) - parse_single_file(file, path, read_all, settings, 2, false) + if fields["dlg_reset_singleplayer_confirm"] ~= nil then + local worldlist = core.get_worlds() + local found_singleplayerworld = false - file:close() + for i = 1, #worldlist, 1 do + if worldlist[i].name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i end - - index = index + 1 - game = gamemgr.get_game(index) end - -- Parse mods - local mods_category_initialized = false - local mods = {} - get_mods(core.get_modpath(), mods) - for _, mod in ipairs(mods) do - local path = mod.path .. DIR_DELIM .. FILENAME - local file = io.open(path, "r") - if file then - if not mods_category_initialized then - local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext - table.insert(settings, { - name = "Mods", - level = 0, - type = "category", - }) - mods_category_initialized = true - end + if found_singleplayerworld then + core.delete_world(gamedata.worldindex) + end - table.insert(settings, { - name = mod.name, - level = 1, - type = "category", - }) + core.create_world("singleplayerworld", 1) - parse_single_file(file, path, read_all, settings, 2, false) + worldlist = core.get_worlds() - file:close() + found_singleplayerworld = false + + for i = 1, #worldlist, 1 do + if worldlist[i].name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i end end end - return settings + this.parent:show() + this:hide() + this:delete() + return true end -local settings = parse_config_file(false, true) -local selected_setting = 1 +local function showconfirm_reset(tabview) + local new_dlg = dialog_create("reset_spworld", + dlg_confirm_reset_formspec, + dlg_confirm_reset_btnhandler, + nil) + new_dlg:set_parent(tabview) + tabview:hide() + new_dlg:show() +end -local function get_current_value(setting) - local value = core.setting_get(setting.name) - if value == nil then - value = setting.default +local function gui_scale_to_scrollbar() + local current_value = tonumber(core.setting_get("gui_scaling")) + + if (current_value == nil) or current_value < 0.25 then + return 0 end - return value + if current_value <= 1.25 then + return ((current_value - 0.25)/ 1.0) * 700 + end + if current_value <= 6 then + return ((current_value -1.25) * 100) + 700 + end + + return 1000 end -local function create_change_setting_formspec(dialogdata) - local setting = settings[selected_setting] - local formspec = "size[10,5.2,true]" .. - "button[5,4.5;2,1;btn_done;" .. fgettext("Save") .. "]" .. - "button[3,4.5;2,1;btn_cancel;" .. fgettext("Cancel") .. "]" .. - "tablecolumns[color;text]" .. - "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. - "table[0,0;10,3;info;" +local function scrollbar_to_gui_scale(value) + value = tonumber(value) - if setting.readable_name then - formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name) - .. " (" .. core.formspec_escape(setting.name) .. ")," + if (value <= 700) then + return ((value / 700) * 1.0) + 0.25 + end + if (value <= 1000) then + return ((value - 700) / 100) + 1.25 + end + + return 1 +end + +local function formspec(tabview, name, tabdata) + local tab_string = + "box[0,0;3.5,4.3;#999999]" .. + "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") + .. ";" .. dump(core.setting_getbool("smooth_lighting")) .. "]" .. + "checkbox[0.25,0.5;cb_particles;" .. fgettext("Enable Particles") .. ";" + .. dump(core.setting_getbool("enable_particles")) .. "]" .. + "checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";" + .. dump(core.setting_getbool("enable_3d_clouds")) .. "]" .. + "checkbox[0.25,1.5;cb_opaque_water;" .. fgettext("Opaque Water") .. ";" + .. dump(core.setting_getbool("opaque_water")) .. "]" .. + "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";" + .. dump(core.setting_getbool("connected_glass")) .. "]" .. + "checkbox[0.25,2.5;cb_node_highlighting;" .. fgettext("Node Highlighting") .. ";" + .. dump(core.setting_getbool("enable_node_highlighting")) .. "]" .. + "dropdown[0.25,3.4;3.3;dd_leaves_style;" .. leaves_style[1][1] .. ";" + .. getLeavesStyleSettingIndex() .. "]" .. + "box[3.75,0;3.75,3.45;#999999]" .. + "label[3.85,0.1;" .. fgettext("Texturing:") .. "]" .. + "dropdown[3.85,0.55;3.85;dd_filters;" .. filters[1][1] .. ";" + .. getFilterSettingIndex() .. "]" .. + "dropdown[3.85,1.35;3.85;dd_mipmap;" .. mipmap[1][1] .. ";" + .. getMipmapSettingIndex() .. "]" .. + "label[3.85,2.15;" .. fgettext("Antialiasing:") .. "]" .. + "dropdown[3.85,2.6;3.85;dd_antialiasing;" .. antialiasing[1][1] .. ";" + .. getAntialiasingSettingIndex() .. "]" .. + "box[7.75,0;4,4;#999999]" .. + "checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";" + .. dump(core.setting_getbool("enable_shaders")) .. "]" + + tab_string = tab_string .. + "button[8,4.75;3.75,0.5;btn_change_keys;" .. fgettext("Change keys") .. "]" .. + "button[0,4.75;3.75,0.5;btn_advanced_settings;" .. fgettext("Advanced Settings") .. "]" + + + if core.setting_get("touchscreen_threshold") ~= nil then + tab_string = tab_string .. + "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" .. + "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" .. + ((tonumber(core.setting_get("touchscreen_threshold"))/10)+1) .. "]" + end + + if core.setting_getbool("enable_shaders") then + tab_string = tab_string .. + "checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bumpmapping") .. ";" + .. dump(core.setting_getbool("enable_bumpmapping")) .. "]" .. + "checkbox[8,1.0;cb_generate_normalmaps;" .. fgettext("Generate Normalmaps") .. ";" + .. dump(core.setting_getbool("generate_normalmaps")) .. "]" .. + "checkbox[8,1.5;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" + .. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]" .. + "checkbox[8,2.0;cb_waving_water;" .. fgettext("Waving Water") .. ";" + .. dump(core.setting_getbool("enable_waving_water")) .. "]" .. + "checkbox[8,2.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" + .. dump(core.setting_getbool("enable_waving_leaves")) .. "]" .. + "checkbox[8,3.0;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" + .. dump(core.setting_getbool("enable_waving_plants")) .. "]" else - formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. "," + tab_string = tab_string .. + "tablecolumns[color;text]" .. + "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. + "table[8.33,0.7;3.5,4;shaders;" .. + "#888888," .. fgettext("Bumpmapping") .. "," .. + "#888888," .. fgettext("Generate Normalmaps") .. "," .. + "#888888," .. fgettext("Parallax Occlusion") .. "," .. + "#888888," .. fgettext("Waving Water") .. "," .. + "#888888," .. fgettext("Waving Leaves") .. "," .. + "#888888," .. fgettext("Waving Plants") .. "," .. + ";1]" end - formspec = formspec .. ",," - - local comment_text = "" - - if setting.comment == "" then - comment_text = fgettext_ne("(No description of setting given)") - else - comment_text = fgettext_ne(setting.comment) - end - for _, comment_line in ipairs(comment_text:split("\n", true)) do - formspec = formspec .. "," .. core.formspec_escape(comment_line) .. "," - end - - if setting.type == "flags" then - formspec = formspec .. ",," - .. "," .. fgettext("Please enter a comma seperated list of flags.") .. "," - .. "," .. fgettext("Possible values are: ") - .. core.formspec_escape(setting.possible:gsub(",", ", ")) .. "," - elseif setting.type == "noise_params" then - formspec = formspec .. ",," - .. "," .. fgettext("Format: , , (, , ), , , ") .. "," - .. "," .. fgettext("Optionally the lacunarity can be appended with a leading comma.") .. "," - elseif setting.type == "v3f" then - formspec = formspec .. ",," - .. "," .. fgettext_ne("Format is 3 numbers separated by commas and inside brackets.") .. "," - end - - formspec = formspec:sub(1, -2) -- remove trailing comma - - formspec = formspec .. ";1]" - - if setting.type == "bool" then - local selected_index - if core.is_yes(get_current_value(setting)) then - selected_index = 2 - else - selected_index = 1 - end - formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;" - .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";" - .. selected_index .. "]" - - elseif setting.type == "enum" then - local selected_index = 0 - formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;" - for index, value in ipairs(setting.values) do - -- translating value is not possible, since it's the value - -- that we set the setting to - formspec = formspec .. core.formspec_escape(value) .. "," - if get_current_value(setting) == value then - selected_index = index - end - end - if #setting.values > 0 then - formspec = formspec:sub(1, -2) -- remove trailing comma - end - formspec = formspec .. ";" .. selected_index .. "]" - - elseif setting.type == "path" then - local current_value = dialogdata.selected_path - if not current_value then - current_value = get_current_value(setting) - end - formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;" - .. core.formspec_escape(current_value) .. "]" - .. "button[8,3.75;2,1;btn_browser_path;" .. fgettext("Browse") .. "]" - - else - -- TODO: fancy input for float, int, flags, noise_params, v3f - local width = 10 - local text = get_current_value(setting) - if dialogdata.error_message then - formspec = formspec .. "tablecolumns[color;text]" .. - "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. - "table[5,3.9;5,0.6;error_message;#FF0000," - .. core.formspec_escape(dialogdata.error_message) .. ";0]" - width = 5 - if dialogdata.entered_text then - text = dialogdata.entered_text - end - end - formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;" - .. core.formspec_escape(text) .. "]" - end - return formspec -end - -local function handle_change_setting_buttons(this, fields) - if fields["btn_done"] or fields["key_enter"] then - local setting = settings[selected_setting] - if setting.type == "bool" then - local new_value = fields["dd_setting_value"] - -- Note: new_value is the actual (translated) value shown in the dropdown - core.setting_setbool(setting.name, new_value == fgettext("Enabled")) - - elseif setting.type == "enum" then - local new_value = fields["dd_setting_value"] - core.setting_set(setting.name, new_value) - - elseif setting.type == "int" then - local new_value = tonumber(fields["te_setting_value"]) - if not new_value or math.floor(new_value) ~= new_value then - this.data.error_message = fgettext_ne("Please enter a valid integer.") - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.min and new_value < setting.min then - this.data.error_message = fgettext_ne("The value must be greater than $1.", setting.min) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.max and new_value > setting.max then - this.data.error_message = fgettext_ne("The value must be lower than $1.", setting.max) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - core.setting_set(setting.name, new_value) - - elseif setting.type == "float" then - local new_value = tonumber(fields["te_setting_value"]) - if not new_value then - this.data.error_message = fgettext_ne("Please enter a valid number.") - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - core.setting_set(setting.name, new_value) - - elseif setting.type == "flags" then - local new_value = fields["te_setting_value"] - for _,value in ipairs(new_value:split(",", true)) do - value = value:trim() - local possible = "," .. setting.possible .. "," - if not possible:find("," .. value .. ",", 0, true) then - this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - end - core.setting_set(setting.name, new_value) - - else - local new_value = fields["te_setting_value"] - core.setting_set(setting.name, new_value) - end - core.setting_save() - this:delete() - return true - end - - if fields["btn_cancel"] then - this:delete() - return true - end - - if fields["btn_browser_path"] then - core.show_file_open_dialog("dlg_browse_path", fgettext_ne("Select path")) - end - - if fields["dlg_browse_path_accepted"] then - this.data.selected_path = fields["dlg_browse_path_accepted"] - core.update_formspec(this:get_formspec()) - end - - return false -end - -local function create_settings_formspec(tabview, name, tabdata) - local formspec = "tablecolumns[color;tree;text;text]" .. - "tableoptions[background=#00000000;border=false]" .. - "table[0,0;12,4.5;list_settings;" - - local current_level = 0 - for _, entry in ipairs(settings) do - local name - if not core.setting_getbool("main_menu_technical_settings") and entry.readable_name then - name = fgettext_ne(entry.readable_name) - else - name = entry.name - end - - if entry.type == "category" then - current_level = entry.level - formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",," - - elseif entry.type == "bool" then - local value = get_current_value(entry) - if core.is_yes(value) then - value = fgettext("Enabled") - else - value = fgettext("Disabled") - end - formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," - .. value .. "," - - elseif entry.type == "key" then - -- ignore key settings, since we have a special dialog for them - - else - formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," - .. core.formspec_escape(get_current_value(entry)) .. "," - end - end - - if #settings > 0 then - formspec = formspec:sub(1, -2) -- remove trailing comma - end - formspec = formspec .. ";" .. selected_setting .. "]" .. - "button[4,4.5;3,1;btn_change_keys;".. fgettext("Change keys") .. "]" .. - "button[10,4.5;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. - "button[7,4.5;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. - "checkbox[0,4.5;cb_tech_settings;" .. fgettext("Show technical names") .. ";" - .. dump(core.setting_getbool("main_menu_technical_settings")) .. "]" - - return formspec + return tab_string end +-------------------------------------------------------------------------------- local function handle_settings_buttons(this, fields, tabname, tabdata) - local list_enter = false - if fields["list_settings"] then - selected_setting = core.get_table_index("list_settings") - if core.explode_table_event(fields["list_settings"]).type == "DCL" then - -- Directly toggle booleans - local setting = settings[selected_setting] - if setting.type == "bool" then - local current_value = get_current_value(setting) - core.setting_setbool(setting.name, not core.is_yes(current_value)) - core.setting_save() - return true - else - list_enter = true - end + + if fields["btn_advanced_settings"] ~= nil then + local adv_settings_dlg = create_adv_settings_dlg() + adv_settings_dlg:set_parent(this) + this:hide() + adv_settings_dlg:show() + --mm_texture.update("singleplayer", current_game()) + return true + end + + if fields["cb_smooth_lighting"] then + core.setting_set("smooth_lighting", fields["cb_smooth_lighting"]) + return true + end + if fields["cb_particles"] then + core.setting_set("enable_particles", fields["cb_particles"]) + return true + end + if fields["cb_3d_clouds"] then + core.setting_set("enable_3d_clouds", fields["cb_3d_clouds"]) + return true + end + if fields["cb_opaque_water"] then + core.setting_set("opaque_water", fields["cb_opaque_water"]) + return true + end + if fields["cb_connected_glass"] then + core.setting_set("connected_glass", fields["cb_connected_glass"]) + return true + end + if fields["cb_node_highlighting"] then + core.setting_set("enable_node_highlighting", fields["cb_node_highlighting"]) + return true + end + if fields["cb_shaders"] then + if (core.setting_get("video_driver") == "direct3d8" + or core.setting_get("video_driver") == "direct3d9") then + core.setting_set("enable_shaders", "false") + gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") else + core.setting_set("enable_shaders", fields["cb_shaders"]) + end + return true + end + if fields["cb_bumpmapping"] then + core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) + end + if fields["cb_generate_normalmaps"] then + core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"]) + end + if fields["cb_parallax"] then + core.setting_set("enable_parallax_occlusion", fields["cb_parallax"]) + return true + end + if fields["cb_waving_water"] then + core.setting_set("enable_waving_water", fields["cb_waving_water"]) + return true + end + if fields["cb_waving_leaves"] then + core.setting_set("enable_waving_leaves", fields["cb_waving_leaves"]) + end + if fields["cb_waving_plants"] then + core.setting_set("enable_waving_plants", fields["cb_waving_plants"]) + return true + end + + if fields["sb_gui_scaling"] then + local event = core.explode_scrollbar_event(fields["sb_gui_scaling"]) + + if event.type == "CHG" then + local tosave = string.format("%.2f",scrollbar_to_gui_scale(event.value)) + core.setting_set("gui_scaling", tosave) return true end end - if fields["btn_edit"] or list_enter then - local setting = settings[selected_setting] - if setting.type ~= "category" then - local edit_dialog = dialog_create("change_setting", create_change_setting_formspec, - handle_change_setting_buttons) - edit_dialog:set_parent(this) - this:hide() - edit_dialog:show() - end - return true - end - - if fields["btn_restore"] then - local setting = settings[selected_setting] - if setting.type ~= "category" then - core.setting_set(setting.name, setting.default) - core.setting_save() - core.update_formspec(this:get_formspec()) - end - return true - end - if fields["btn_change_keys"] then core.show_keys_menu() return true end - - if fields["cb_tech_settings"] then - core.setting_set("main_menu_technical_settings", fields["cb_tech_settings"]) - core.setting_save() - core.update_formspec(this:get_formspec()) + if fields["cb_touchscreen_target"] then + core.setting_set("touchtarget", fields["cb_touchscreen_target"]) + return true + end + if fields["btn_reset_singleplayer"] then + showconfirm_reset(this) return true end - return false + --Note dropdowns have to be handled LAST! + local ddhandled = false + + if fields["dd_leaves_style"] == leaves_style_labels[1] then + core.setting_set("leaves_style", leaves_style[2][1]) + ddhandled = true + elseif fields["dd_leaves_style"] == leaves_style_labels[2] then + core.setting_set("leaves_style", leaves_style[2][2]) + ddhandled = true + elseif fields["dd_leaves_style"] == leaves_style_labels[3] then + core.setting_set("leaves_style", leaves_style[2][3]) + ddhandled = true + end + if fields["dd_filters"] == dd_filter_labels[1] then + core.setting_set("bilinear_filter", "false") + core.setting_set("trilinear_filter", "false") + ddhandled = true + elseif fields["dd_filters"] == dd_filter_labels[2] then + core.setting_set("bilinear_filter", "true") + core.setting_set("trilinear_filter", "false") + ddhandled = true + elseif fields["dd_filters"] == dd_filter_labels[3] then + core.setting_set("bilinear_filter", "false") + core.setting_set("trilinear_filter", "true") + ddhandled = true + end + if fields["dd_mipmap"] == dd_mipmap_labels[1] then + core.setting_set("mip_map", "false") + core.setting_set("anisotropic_filter", "false") + ddhandled = true + elseif fields["dd_mipmap"] == dd_mipmap_labels[2] then + core.setting_set("mip_map", "true") + core.setting_set("anisotropic_filter", "false") + ddhandled = true + elseif fields["dd_mipmap"] == dd_mipmap_labels[3] then + core.setting_set("mip_map", "true") + core.setting_set("anisotropic_filter", "true") + ddhandled = true + end + if fields["dd_antialiasing"] then + core.setting_set("fsaa", + antialiasing_fname_to_name(fields["dd_antialiasing"])) + ddhandled = true + end + if fields["dd_touchthreshold"] then + core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"]) + ddhandled = true + end + + return ddhandled end tab_settings = { name = "settings", caption = fgettext("Settings"), - cbf_formspec = create_settings_formspec, - cbf_button_handler = handle_settings_buttons, + cbf_formspec = formspec, + cbf_button_handler = handle_settings_buttons } - -local function create_minetest_conf_example() - local result = "# This file contains a list of all available settings and their default value for minetest.conf\n" .. - "\n" .. - "# By default, all the settings are commented and not functional.\n" .. - "# Uncomment settings by removing the preceding #.\n" .. - "\n" .. - "# minetest.conf is read by default from:\n" .. - "# ../minetest.conf\n" .. - "# ../../minetest.conf\n" .. - "# Any other path can be chosen by passing the path as a parameter\n" .. - "# to the program, eg. \"minetest.exe --config ../minetest.conf.example\".\n" .. - "\n" .. - "# Further documentation:\n" .. - "# http://wiki.minetest.net/\n" .. - "\n" - - local settings = parse_config_file(true, false) - for _, entry in ipairs(settings) do - if entry.type == "category" then - if entry.level == 0 then - result = result .. "#\n# " .. entry.name .. "\n#\n\n" - else - for i = 1, entry.level do - result = result .. "#" - end - result = result .. "# " .. entry.name .. "\n\n" - end - else - if entry.comment ~= "" then - for _, comment_line in ipairs(entry.comment:split("\n", true)) do - result = result .."# " .. comment_line .. "\n" - end - end - result = result .. "# type: " .. entry.type - if entry.min then - result = result .. " min: " .. entry.min - end - if entry.max then - result = result .. " max: " .. entry.max - end - if entry.values then - result = result .. " values: " .. table.concat(entry.values, ", ") - end - if entry.possible then - result = result .. " possible values: " .. entry.possible:gsub(",", ", ") - end - result = result .. "\n" - result = result .. "# " .. entry.name .. " = ".. entry.default .. "\n\n" - end - end - return result -end - -local function create_translation_file() - local result = "// This file is automatically generated\n" .. - "// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files\n" .. - "// To update it, refer to the bottom of builtin/mainmenu/tab_settings.lua\n\n" .. - "fake_function() {\n" - - local settings = parse_config_file(true, false) - for _, entry in ipairs(settings) do - if entry.type == "category" then - local name_escaped = entry.name:gsub("\"", "\\\"") - result = result .. "\tgettext(\"" .. name_escaped .. "\");\n" - else - if entry.readable_name then - local readable_name_escaped = entry.readable_name:gsub("\"", "\\\"") - result = result .. "\tgettext(\"" .. readable_name_escaped .. "\");\n" - end - if entry.comment ~= "" then - local comment_escaped = entry.comment:gsub("\n", "\\n") - comment_escaped = comment_escaped:gsub("\"", "\\\"") - result = result .. "\tgettext(\"" .. comment_escaped .. "\");\n" - end - end - end - result = result .. "}\n" - return result -end - -if false then - local file = io.open("minetest.conf.example", "w") - if file then - file:write(create_minetest_conf_example()) - file:close() - end -end - -if false then - local file = io.open("src/settings_translation_file.cpp", "w") - if file then - file:write(create_translation_file()) - file:close() - end -end From 354635f9fbe67cebbcd147db94b48983aa3799eb Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Sat, 20 Feb 2016 06:53:56 +0100 Subject: [PATCH 10/17] Dont make fastface if tile is not seamless Fixes #3378 Closes #3751 --- src/mapblock_mesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 42e84b75e..e1ec50ab0 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -885,8 +885,9 @@ static void updateFastFaceRow( && next_lights[3] == lights[3] && next_tile == tile && tile.rotation == 0 - && next_light_source == light_source) - { + && next_light_source == light_source + && (tile.material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL) + && (tile.material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) { next_is_different = false; } else{ From 2f4cf0bcca1ce5941dd3d2625fe4636e86e73e75 Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Sat, 20 Feb 2016 03:37:26 +0100 Subject: [PATCH 11/17] Remove preload_item_visuals code Closes #3748 --- builtin/settingtypes.txt | 5 ----- minetest.conf.example | 6 ------ src/client.cpp | 23 ----------------------- src/defaultsettings.cpp | 2 -- src/settings_translation_file.cpp | 2 -- 5 files changed, 38 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2bb1c93b4..df9158254 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -311,11 +311,6 @@ texture_clean_transparent (Clean transparent textures) bool false # enabled. texture_min_size (Minimum texture size for filters) int 64 -# Pre-generate all item visuals used in the inventory. -# This increases startup time, but runs smoother in-game. -# The generated textures can easily exceed your VRAM, causing artifacts in the inventory. -preload_item_visuals (Preload inventory textures) bool false - # Experimental option, might cause visible spaces between blocks # when set to higher number than 0. fsaa (FSAA) enum 0 0,1,2,4,8,16 diff --git a/minetest.conf.example b/minetest.conf.example index 4cfb8dd28..4813adadc 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -335,12 +335,6 @@ # type: int # texture_min_size = 64 -# Pre-generate all item visuals used in the inventory. -# This increases startup time, but runs smoother in-game. -# The generated textures can easily exceed your VRAM, causing artifacts in the inventory. -# type: bool -# preload_item_visuals = false - # Experimental option, might cause visible spaces between blocks # when set to higher number than 0. # type: enum values: 0, 1, 2, 4, 8, 16 diff --git a/src/client.cpp b/src/client.cpp index 3fe67d645..f27f031c5 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1766,29 +1766,6 @@ void Client::afterContentReceived(IrrlichtDevice *device) m_nodedef->updateTextures(this, texture_update_progress, &tu_args); delete[] tu_args.text_base; - // Preload item textures and meshes if configured to - if(g_settings->getBool("preload_item_visuals")) - { - verbosestream<<"Updating item textures and meshes"< names = m_itemdef->getAll(); - size_t size = names.size(); - size_t count = 0; - int percent = 0; - for(std::set::const_iterator - i = names.begin(); i != names.end(); ++i) - { - // Asking for these caches the result - m_itemdef->getInventoryTexture(*i, this); - m_itemdef->getWieldMesh(*i, this); - count++; - percent = (count * 100 / size * 0.2) + 80; - draw_load_screen(text, device, guienv, 0, percent); - } - delete[] text; - } - // Start mesh update thread after setting up content definitions infostream<<"- Starting mesh update thread"<setDefault("trilinear_filter", "false"); settings->setDefault("texture_clean_transparent", "false"); settings->setDefault("texture_min_size", "64"); - settings->setDefault("preload_item_visuals", "false"); settings->setDefault("tone_mapping", "false"); settings->setDefault("enable_bumpmapping", "false"); settings->setDefault("enable_parallax_occlusion", "false"); @@ -345,7 +344,6 @@ void set_default_settings(Settings *settings) settings->setDefault("max_simultaneous_block_sends_per_client", "3"); settings->setDefault("emergequeue_limit_diskonly", "8"); settings->setDefault("emergequeue_limit_generate", "8"); - settings->setDefault("preload_item_visuals", "false"); settings->setDefault("viewing_range_nodes_max", "50"); settings->setDefault("viewing_range_nodes_min", "20"); diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index b078e4d12..d3bf5a1f8 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -136,8 +136,6 @@ fake_function() { gettext("Filtered textures can blend RGB values with fully-transparent neighbors,\nwhich PNG optimizers usually discard, sometimes resulting in a dark or\nlight edge to transparent textures. Apply this filter to clean that up\nat texture load time."); gettext("Minimum texture size for filters"); gettext("When using bilinear/trilinear/anisotropic filters, low-resolution textures\ncan be blurred, so automatically upscale them with nearest-neighbor\ninterpolation to preserve crisp pixels. This sets the minimum texture size\nfor the upscaled textures; higher values look sharper, but require more\nmemory. Powers of 2 are recommended. Setting this higher than 1 may not\nhave a visible effect unless bilinear/trilinear/anisotropic filtering is\nenabled."); - gettext("Preload inventory textures"); - gettext("Pre-generate all item visuals used in the inventory.\nThis increases startup time, but runs smoother in-game.\nThe generated textures can easily exceed your VRAM, causing artifacts in the inventory."); gettext("FSAA"); gettext("Experimental option, might cause visible spaces between blocks\nwhen set to higher number than 0."); gettext("Shaders"); From b2aabdfe074d361b69c59f7ea29a85f060330f12 Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Sat, 20 Feb 2016 04:42:35 +0100 Subject: [PATCH 12/17] Camera: remove auto tune FPS, single view range setting --- builtin/settingtypes.txt | 18 ++--- minetest.conf.example | 20 ++---- src/camera.cpp | 151 +++------------------------------------ src/camera.h | 13 +--- src/clientmap.cpp | 2 +- src/clientmap.h | 5 +- src/defaultsettings.cpp | 8 +-- src/game.cpp | 23 +++--- 8 files changed, 33 insertions(+), 207 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index df9158254..c21a0325f 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -209,11 +209,11 @@ keymap_toggle_profiler (Profiler toggle key) key KEY_F6 # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_camera_mode (Toggle camera mode key) key KEY_F7 -# Key for increasing the viewing range. Modifies the minimum viewing range. +# Key for increasing the viewing range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_increase_viewing_range_min (View range increase key) key + -# Key for decreasing the viewing range. Modifies the minimum viewing range. +# Key for decreasing the viewing range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 keymap_decrease_viewing_range_min (View range decrease key) key - @@ -388,10 +388,6 @@ enable_waving_plants (Waving plants) bool false [***Advanced] -# Minimum wanted FPS. -# The amount of rendered stuff is dynamically set according to this. and viewing range min and max. -wanted_fps (Wanted FPS) int 30 - # If FPS would go higher than this, limit it by sleeping # to not waste CPU power for no benefit. fps_max (Maximum FPS) int 60 @@ -399,13 +395,9 @@ fps_max (Maximum FPS) int 60 # Maximum FPS when game is paused. pause_fps_max (FPS in pause menu) int 20 -# The allowed adjustment range for the automatic rendering range adjustment. -# Set this to be equal to viewing range minimum to disable the auto-adjustment algorithm. -viewing_range_nodes_max (Viewing range maximum) int 160 - -# The allowed adjustment range for the automatic rendering range adjustment. -# Set this to be equal to viewing range maximum to disable the auto-adjustment algorithm. -viewing_range_nodes_min (Viewing range minimum) int 35 +# View distance in nodes. +# Min = 20 +viewing_range (Viewing range) int 100 # Width component of the initial window size. screenW (Screen width) int 800 diff --git a/minetest.conf.example b/minetest.conf.example index 4813adadc..ee6054536 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -214,12 +214,12 @@ # type: key # keymap_camera_mode = KEY_F7 -# Key for increasing the viewing range. Modifies the minimum viewing range. +# Key for increasing the viewing range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # type: key # keymap_increase_viewing_range_min = + -# Key for decreasing the viewing range. Modifies the minimum viewing range. +# Key for decreasing the viewing range. # See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 # type: key # keymap_decrease_viewing_range_min = - @@ -431,11 +431,6 @@ #### Advanced -# Minimum wanted FPS. -# The amount of rendered stuff is dynamically set according to this. and viewing range min and max. -# type: int -# wanted_fps = 30 - # If FPS would go higher than this, limit it by sleeping # to not waste CPU power for no benefit. # type: int @@ -445,15 +440,10 @@ # type: int # pause_fps_max = 20 -# The allowed adjustment range for the automatic rendering range adjustment. -# Set this to be equal to viewing range minimum to disable the auto-adjustment algorithm. +# View range in nodes. +# Min = 20. # type: int -# viewing_range_nodes_max = 160 - -# The allowed adjustment range for the automatic rendering range adjustment. -# Set this to be equal to viewing range maximum to disable the auto-adjustment algorithm. -# type: int -# viewing_range_nodes_min = 35 +# viewing_range = 100 # Width component of the initial window size. # type: int diff --git a/src/camera.cpp b/src/camera.cpp index 0ce6bb345..6c0f5d546 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -60,13 +60,6 @@ Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, m_fov_x(1.0), m_fov_y(1.0), - m_added_busytime(0), - m_added_frames(0), - m_range_old(0), - m_busytime_old(0), - m_frametime_counter(0), - m_time_per_range(30. / 50), // a sane default of 30ms per 50 nodes of range - m_view_bobbing_anim(0), m_view_bobbing_state(0), m_view_bobbing_speed(0), @@ -109,7 +102,6 @@ Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, */ m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount"); m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); - m_cache_wanted_fps = g_settings->getFloat("wanted_fps"); m_cache_fov = g_settings->getFloat("fov"); m_cache_view_bobbing = g_settings->getBool("view_bobbing"); m_nametags.clear(); @@ -455,8 +447,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, m_wieldnode->setColor(player->light_color); - // Render distance feedback loop - updateViewingRange(frametime, busytime); + // Set render distance + updateViewingRange(); // If the player is walking, swimming, or climbing, // view bobbing is enabled and free_move is off, @@ -484,143 +476,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, } } -void Camera::updateViewingRange(f32 frametime_in, f32 busytime_in) +void Camera::updateViewingRange() { - if (m_draw_control.range_all) - return; - - m_added_busytime += busytime_in; - m_added_frames += 1; - - m_frametime_counter -= frametime_in; - if (m_frametime_counter > 0) - return; - m_frametime_counter = 0.2; // Same as ClientMap::updateDrawList interval - - /*dstream<getFloat("viewing_range_nodes_max"); - viewing_range_max = MYMAX(viewing_range_min, viewing_range_max); - - // Immediately apply hard limits - if(m_draw_control.wanted_range < viewing_range_min) - m_draw_control.wanted_range = viewing_range_min; - if(m_draw_control.wanted_range > viewing_range_max) - m_draw_control.wanted_range = viewing_range_max; - - // Just so big a value that everything rendered is visible - // Some more allowance than viewing_range_max * BS because of clouds, - // active objects, etc. - if(viewing_range_max < 200*BS) - m_cameranode->setFarValue(200 * BS * 10); - else - m_cameranode->setFarValue(viewing_range_max * BS * 10); - - f32 wanted_fps = m_cache_wanted_fps; - wanted_fps = MYMAX(wanted_fps, 1.0); - f32 wanted_frametime = 1.0 / wanted_fps; - - m_draw_control.wanted_min_range = viewing_range_min; - m_draw_control.wanted_max_blocks = (2.0*m_draw_control.blocks_would_have_drawn)+1; - if (m_draw_control.wanted_max_blocks < 10) - m_draw_control.wanted_max_blocks = 10; - - f32 block_draw_ratio = 1.0; - if (m_draw_control.blocks_would_have_drawn != 0) - { - block_draw_ratio = (f32)m_draw_control.blocks_drawn - / (f32)m_draw_control.blocks_would_have_drawn; - } - - // Calculate the average frametime in the case that all wanted - // blocks had been drawn - f32 frametime = m_added_busytime / m_added_frames / block_draw_ratio; - - m_added_busytime = 0.0; - m_added_frames = 0; - - f32 wanted_frametime_change = wanted_frametime - frametime; - //dstream<<"wanted_frametime_change="<avg("wanted_frametime_change", wanted_frametime_change); - - // If needed frametime change is small, just return - // This value was 0.4 for many months until 2011-10-18 by c55; - if (fabs(wanted_frametime_change) < wanted_frametime*0.33) - { - //dstream<<"ignoring small wanted_frametime_change"<setFarValue(100000.0); return; } - f32 range = m_draw_control.wanted_range; - f32 new_range = range; - - f32 d_range = range - m_range_old; - f32 d_busytime = busytime_in - m_busytime_old; - if (d_range != 0) - { - m_time_per_range = d_busytime / d_range; - } - //dstream<<"time_per_range="<avg("time_per_range", m_time_per_range); - - // The minimum allowed calculated frametime-range derivative: - // Practically this sets the maximum speed of changing the range. - // The lower this value, the higher the maximum changing speed. - // A low value here results in wobbly range (0.001) - // A low value can cause oscillation in very nonlinear time/range curves. - // A high value here results in slow changing range (0.0025) - // SUGG: This could be dynamically adjusted so that when - // the camera is turning, this is lower - //f32 min_time_per_range = 0.0010; // Up to 0.4.7 - f32 min_time_per_range = 0.0005; - if(m_time_per_range < min_time_per_range) - { - m_time_per_range = min_time_per_range; - //dstream<<"m_time_per_range="<getFloat("viewing_range"); + m_draw_control.wanted_range = viewing_range; + m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); } void Camera::setDigging(s32 button) diff --git a/src/camera.h b/src/camera.h index 36fc72f1a..ce46c3190 100644 --- a/src/camera.h +++ b/src/camera.h @@ -136,8 +136,8 @@ public: void update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio, ClientEnvironment &c_env); - // Render distance feedback loop - void updateViewingRange(f32 frametime_in, f32 busytime_in); + // Update render distance + void updateViewingRange(); // Start digging animation // Pass 0 for left click, 1 for right click @@ -204,14 +204,6 @@ private: f32 m_fov_x; f32 m_fov_y; - // Stuff for viewing range calculations - f32 m_added_busytime; - s16 m_added_frames; - f32 m_range_old; - f32 m_busytime_old; - f32 m_frametime_counter; - f32 m_time_per_range; - // View bobbing animation frame (0 <= m_view_bobbing_anim < 1) f32 m_view_bobbing_anim; // If 0, view bobbing is off (e.g. player is standing). @@ -238,7 +230,6 @@ private: f32 m_cache_fall_bobbing_amount; f32 m_cache_view_bobbing_amount; - f32 m_cache_wanted_fps; f32 m_cache_fov; bool m_cache_view_bobbing; diff --git a/src/clientmap.cpp b/src/clientmap.cpp index ea2b8e4e3..a0a780250 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -327,7 +327,7 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) blocks_would_have_drawn++; if (blocks_drawn >= m_control.wanted_max_blocks && !m_control.range_all && - d > m_control.wanted_min_range * BS) + d > m_control.wanted_range * BS) continue; // Add to set diff --git a/src/clientmap.h b/src/clientmap.h index 445f10c97..396e68f64 100644 --- a/src/clientmap.h +++ b/src/clientmap.h @@ -30,9 +30,8 @@ struct MapDrawControl { MapDrawControl(): range_all(false), - wanted_range(50), + wanted_range(0), wanted_max_blocks(0), - wanted_min_range(0), blocks_drawn(0), blocks_would_have_drawn(0), farthest_drawn(0) @@ -44,8 +43,6 @@ struct MapDrawControl float wanted_range; // Maximum number of blocks to draw u32 wanted_max_blocks; - // Blocks in this range are drawn regardless of number of blocks drawn - float wanted_min_range; // Number of blocks rendered is written here by the renderer u32 blocks_drawn; // Number of blocks that would have been drawn in wanted_range diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index a043ef003..b1e754870 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -89,12 +89,9 @@ void set_default_settings(Settings *settings) settings->setDefault("show_debug", "true"); #endif - settings->setDefault("wanted_fps", "30"); settings->setDefault("fps_max", "60"); settings->setDefault("pause_fps_max", "20"); - // A bit more than the server will send around the player, to make fog blend well - settings->setDefault("viewing_range_nodes_max", "240"); - settings->setDefault("viewing_range_nodes_min", "35"); + settings->setDefault("viewing_range", "100"); settings->setDefault("map_generation_limit", "31000"); settings->setDefault("screenW", "800"); settings->setDefault("screenH", "600"); @@ -345,8 +342,7 @@ void set_default_settings(Settings *settings) settings->setDefault("emergequeue_limit_diskonly", "8"); settings->setDefault("emergequeue_limit_generate", "8"); - settings->setDefault("viewing_range_nodes_max", "50"); - settings->setDefault("viewing_range_nodes_min", "20"); + settings->setDefault("viewing_range", "50"); settings->setDefault("inventory_image_hack", "false"); //check for device with small screen diff --git a/src/game.cpp b/src/game.cpp index 53db14fb3..9729ca477 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3039,10 +3039,10 @@ void Game::toggleProfiler(float *statustext_time, u32 *profiler_current_page, void Game::increaseViewRange(float *statustext_time) { - s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range = g_settings->getS16("viewing_range"); s16 range_new = range + 10; - g_settings->set("viewing_range_nodes_min", itos(range_new)); - statustext = utf8_to_wide("Minimum viewing range changed to " + g_settings->set("viewing_range", itos(range_new)); + statustext = utf8_to_wide("Viewing range changed to " + itos(range_new)); *statustext_time = 0; } @@ -3050,14 +3050,14 @@ void Game::increaseViewRange(float *statustext_time) void Game::decreaseViewRange(float *statustext_time) { - s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range = g_settings->getS16("viewing_range"); s16 range_new = range - 10; - if (range_new < 0) - range_new = range; + if (range_new < 20) + range_new = 20; - g_settings->set("viewing_range_nodes_min", itos(range_new)); - statustext = utf8_to_wide("Minimum viewing range changed to " + g_settings->set("viewing_range", itos(range_new)); + statustext = utf8_to_wide("Viewing range changed to " + itos(range_new)); *statustext_time = 0; } @@ -3935,12 +3935,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, if (draw_control->range_all) { runData->fog_range = 100000 * BS; } else { - runData->fog_range = draw_control->wanted_range * BS - + 0.0 * MAP_BLOCKSIZE * BS; - runData->fog_range = MYMIN( - runData->fog_range, - (draw_control->farthest_drawn + 20) * BS); - runData->fog_range *= 0.9; + runData->fog_range = 0.9 * draw_control->wanted_range * BS; } /* From a3892f5a6632550bf0c14c18e6902f6ae06bb567 Mon Sep 17 00:00:00 2001 From: Jeija Date: Wed, 17 Feb 2016 20:36:51 +0100 Subject: [PATCH 13/17] Fix HTTPFetchRequest performing a GET request if post_data is supplied Instead, perform a POST request with post_data. --- src/httpfetch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index f10351a01..1a19dd082 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -262,7 +262,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(HTTPFetchRequest request_, CurlHandlePool *po } // Set POST (or GET) data - if (request.post_fields.empty()) { + if (request.post_fields.empty() && request.post_data.empty()) { curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); } else if (request.multipart) { curl_httppost *last = NULL; From 31e0667a4a53a238d0321194b57b083bd74c0a5b Mon Sep 17 00:00:00 2001 From: Jeija Date: Thu, 18 Feb 2016 11:38:47 +0100 Subject: [PATCH 14/17] Add Lua interface to HTTPFetchRequest This allows mods to perform both asynchronous and synchronous HTTP requests. Mods are only granted access to HTTP APIs if either mod security is disabled or if they are whitelisted in any of the the secure.http_mods and secure.trusted_mods settings. Adds httpfetch_caller_alloc_secure to generate random, non-predictable caller IDs so that lua mods cannot spy on each others HTTP queries. --- builtin/game/misc.lua | 19 ++++ builtin/settingtypes.txt | 4 + doc/lua_api.txt | 54 +++++++++ src/defaultsettings.cpp | 1 + src/httpfetch.cpp | 37 ++++++- src/httpfetch.h | 3 + src/script/common/c_converter.cpp | 9 ++ src/script/common/c_converter.h | 2 + src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_http.cpp | 176 ++++++++++++++++++++++++++++++ src/script/lua_api/l_http.h | 50 +++++++++ src/script/scripting_game.cpp | 2 + 12 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 src/script/lua_api/l_http.cpp create mode 100644 src/script/lua_api/l_http.h diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index bacadf18f..d0e67bd88 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -178,3 +178,22 @@ function core.raillike_group(name) end return id end + +-- HTTP callback interface +function core.http_add_fetch(httpenv) + httpenv.fetch = function(req, callback) + local handle = httpenv.fetch_async(req) + + local function update_http_status() + local res = httpenv.fetch_async_get(handle) + if res.completed then + callback(res) + else + core.after(0, update_http_status) + end + end + core.after(0, update_http_status) + end + + return httpenv +end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index c21a0325f..9e1997cc6 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1100,6 +1100,10 @@ secure.enable_security (Enable mod security) bool false # functions even when mod security is on (via request_insecure_environment()). secure.trusted_mods (Trusted mods) string +# Comma-seperated list of mods that are allowed to access HTTP APIs, which +# allow them to upload and download data to/from the internet. +secure.http_mods (HTTP Mods) string + [Client and Server] # Name of the player. diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 2df0cac7c..aee3674d0 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2330,6 +2330,26 @@ These functions return the leftover itemstack. * If `lua_num_indent_spaces` is a nonzero number and `format` is "lua", the Lua code generated * will use that number of spaces as indentation instead of a tab character. +### HTTP Requests: +* `minetest.request_http_api()`: + * returns `HTTPApiTable` containing http functions if the calling mod has been granted + access by being listed in the `secure.http_mods` or `secure.trusted_mods` setting, + otherwise returns `nil`. + * The returned table contains the functions `fetch`, `fetch_async` and `fetch_async_get` + described below. + * Only works at init time. + * Function only exists if minetest server was built with cURL support. + * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED TABLE, STORE IT IN + A LOCAL VARIABLE!** +* `HTTPApiTable.fetch(HTTPRequest req, callback)` + * Performs given request asynchronously and calls callback upon completion + * callback: `function(HTTPRequestResult res)` + * Use this HTTP function if you are unsure, the others are for advanced use. +* `HTTPApiTable.fetch_async(HTTPRequest req)`: returns handle + * Performs given request asynchronously and returns handle for `minetest.http_fetch_async_get` +* `HTTPApiTable.fetch_async_get(handle)`: returns HTTPRequestResult + * Return response data for given asynchronous HTTP request + ### Misc. * `minetest.get_connected_players()`: returns list of `ObjectRefs` * `minetest.hash_node_position({x=,y=,z=})`: returns an 48-bit integer @@ -3837,3 +3857,37 @@ Definition tables playername = "singleplayer" -- ^ Playername is optional, if specified spawns particle only on the player's client } + +### `HTTPRequest` definition (`http_fetch`, `http_fetch_async`) + + { + url = "http://example.org", + timeout = 10, + -- ^ Timeout for connection in seconds. Default is 3 seconds. + post_data = "Raw POST request data string" OR { field1 = "data1", field2 = "data2" }, + -- ^ Optional, if specified a POST request with post_data is performed. + -- ^ Accepts both a string and a table. If a table is specified, encodes table + -- ^ as x-www-form-urlencoded key-value pairs. + -- ^ If post_data ist not specified, a GET request is performed instead. + user_agent = "ExampleUserAgent", + -- ^ Optional, if specified replaces the default minetest user agent with given string + extra_headers = { "Accept-Language: en-us", "Accept-Charset: utf-8" }, + -- ^ Optional, if specified adds additional headers to the HTTP request. You must make sure + -- ^ that the header strings follow HTTP specification ("Key: Value"). + multipart = boolean + -- ^ Optional, if true performs a multipart HTTP request. Default is false. + } + +### `HTTPRequestResult` definition (`http_fetch` callback, `http_fetch_async_get`) + + { + completed = true, + -- ^ If true, the request has finished (either succeeded, failed or timed out) + succeeded = true, + -- ^ If true, the request was succesful + timeout = false, + -- ^ If true, the request timed out + code = 200, + -- ^ HTTP status code + data = "response" + } diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index b1e754870..49e03a260 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -285,6 +285,7 @@ void set_default_settings(Settings *settings) settings->setDefault("num_emerge_threads", "1"); settings->setDefault("secure.enable_security", "false"); settings->setDefault("secure.trusted_mods", ""); + settings->setDefault("secure.http_mods", ""); // physics stuff settings->setDefault("movement_acceleration_default", "3"); diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 1a19dd082..f64c9f717 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "socket.h" // for select() -#include "porting.h" // for sleep_ms(), get_sysinfo() +#include "porting.h" // for sleep_ms(), get_sysinfo(), secure_rand_fill_buf() #include "httpfetch.h" #include #include @@ -34,9 +34,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/thread.h" #include "version.h" #include "settings.h" +#include "noise.h" Mutex g_httpfetch_mutex; std::map > g_httpfetch_results; +PcgRandom g_callerid_randomness; HTTPFetchRequest::HTTPFetchRequest() { @@ -84,6 +86,34 @@ unsigned long httpfetch_caller_alloc() return discard; } +unsigned long httpfetch_caller_alloc_secure() +{ + MutexAutoLock lock(g_httpfetch_mutex); + + // Generate random caller IDs and make sure they're not + // already used or equal to HTTPFETCH_DISCARD + // Give up after 100 tries to prevent infinite loop + u8 tries = 100; + unsigned long caller; + + do { + caller = (((u64) g_callerid_randomness.next()) << 32) | + g_callerid_randomness.next(); + + if (--tries < 1) { + FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs"); + return HTTPFETCH_DISCARD; + } + } while (g_httpfetch_results.find(caller) != g_httpfetch_results.end()); + + verbosestream << "httpfetch_caller_alloc_secure: allocating " + << caller << std::endl; + + // Access element to create it + g_httpfetch_results[caller]; + return caller; +} + void httpfetch_caller_free(unsigned long caller) { verbosestream<<"httpfetch_caller_free: freeing " @@ -710,6 +740,11 @@ void httpfetch_init(int parallel_limit) FATAL_ERROR_IF(res != CURLE_OK, "CURL init failed"); g_httpfetch_thread = new CurlFetchThread(parallel_limit); + + // Initialize g_callerid_randomness for httpfetch_caller_alloc_secure + u64 randbuf[2]; + porting::secure_rand_fill_buf(randbuf, sizeof(u64) * 2); + g_callerid_randomness = PcgRandom(randbuf[0], randbuf[1]); } void httpfetch_cleanup() diff --git a/src/httpfetch.h b/src/httpfetch.h index c44c8d2d3..f57ed8789 100644 --- a/src/httpfetch.h +++ b/src/httpfetch.h @@ -116,6 +116,9 @@ bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result); // Not required if you want to set caller = HTTPFETCH_DISCARD unsigned long httpfetch_caller_alloc(); +// Allocates a non-predictable caller ID for httpfetch_async +unsigned long httpfetch_caller_alloc_secure(); + // Frees a caller ID allocated with httpfetch_caller_alloc // Note: This can be expensive, because the httpfetch thread is told // to stop any ongoing fetches for the given caller. diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index f1d3cc421..55c4a5f5a 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -517,6 +517,15 @@ bool getboolfield_default(lua_State *L, int table, return result; } +void setstringfield(lua_State *L, int table, + const char *fieldname, const char *value) +{ + lua_pushstring(L, value); + if(table < 0) + table -= 1; + lua_setfield(L, table, fieldname); +} + void setintfield(lua_State *L, int table, const char *fieldname, int value) { diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 18a045d2a..eefac0ed7 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -69,6 +69,8 @@ bool getfloatfield(lua_State *L, int table, std::string checkstringfield(lua_State *L, int table, const char *fieldname); +void setstringfield(lua_State *L, int table, + const char *fieldname, const char *value); void setintfield(lua_State *L, int table, const char *fieldname, int value); void setfloatfield(lua_State *L, int table, diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 2501ce6d6..d507dcf70 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -16,6 +16,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_http.cpp PARENT_SCOPE) set(client_SCRIPT_LUA_API_SRCS diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp new file mode 100644 index 000000000..b0357e3e0 --- /dev/null +++ b/src/script/lua_api/l_http.cpp @@ -0,0 +1,176 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "lua_api/l_internal.h" +#include "common/c_converter.h" +#include "common/c_content.h" +#include "lua_api/l_http.h" +#include "httpfetch.h" +#include "settings.h" +#include "log.h" + +#include +#include +#include + +#define HTTP_API(name) \ + lua_pushstring(L, #name); \ + lua_pushcfunction(L, l_http_##name); \ + lua_settable(L, -3); + +#if USE_CURL +void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req) +{ + luaL_checktype(L, 1, LUA_TTABLE); + + req.caller = httpfetch_caller_alloc_secure(); + getstringfield(L, 1, "url", req.url); + lua_getfield(L, 1, "user_agent"); + if (lua_isstring(L, -1)) + req.useragent = getstringfield_default(L, 1, "user_agent", ""); + lua_pop(L, 1); + req.multipart = getboolfield_default(L, 1, "multipart", false); + req.timeout = getintfield_default(L, 1, "timeout", 3) * 1000; + + // post_data: if table, post form data, otherwise raw data + lua_getfield(L, 1, "post_data"); + if (lua_istable(L, 2)) { + lua_pushnil(L); + while (lua_next(L, 2) != 0) + { + req.post_fields[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1); + lua_pop(L, 1); + } + } else if (lua_isstring(L, 2)) { + req.post_data = lua_tostring(L, 2); + } + lua_pop(L, 1); + + lua_getfield(L, 1, "extra_headers"); + if (lua_istable(L, 2)) { + lua_pushnil(L); + while (lua_next(L, 2) != 0) + { + const char *header = luaL_checkstring(L, -1); + req.extra_headers.push_back(header); + lua_pop(L, 1); + } + } + lua_pop(L, 1); +} + +void ModApiHttp::push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed) +{ + lua_newtable(L); + setboolfield(L, -1, "succeeded", res.succeeded); + setboolfield(L, -1, "timeout", res.timeout); + setboolfield(L, -1, "completed", completed); + setintfield(L, -1, "code", res.response_code); + setstringfield(L, -1, "data", res.data.c_str()); +} + +// http_api.fetch_async(HTTPRequest definition) +int ModApiHttp::l_http_fetch_async(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + HTTPFetchRequest req; + read_http_fetch_request(L, req); + + actionstream << "Mod performs HTTP request with URL " << req.url << std::endl; + httpfetch_async(req); + + // Convert handle to hex string since lua can't handle 64-bit integers + std::stringstream handle_conversion_stream; + handle_conversion_stream << std::hex << req.caller; + std::string caller_handle(handle_conversion_stream.str()); + + lua_pushstring(L, caller_handle.c_str()); + return 1; +} + +// http_api.fetch_async_get(handle) +int ModApiHttp::l_http_fetch_async_get(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string handle_str = luaL_checkstring(L, 1); + + // Convert hex string back to 64-bit handle + u64 handle; + std::stringstream handle_conversion_stream; + handle_conversion_stream << std::hex << handle_str; + handle_conversion_stream >> handle; + + HTTPFetchResult res; + bool completed = httpfetch_async_get(handle, res); + + push_http_fetch_result(L, res, completed); + + return 1; +} + +int ModApiHttp::l_request_http_api(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // Mod must be listed in secure.http_mods or secure.trusted_mods + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) { + lua_pushnil(L); + return 1; + } + + const char *mod_name = lua_tostring(L, -1); + std::string http_mods = g_settings->get("secure.http_mods"); + http_mods.erase(std::remove(http_mods.begin(), http_mods.end(), ' '), http_mods.end()); + std::vector mod_list_http = str_split(http_mods, ','); + + std::string trusted_mods = g_settings->get("secure.trusted_mods"); + trusted_mods.erase(std::remove(trusted_mods.begin(), trusted_mods.end(), ' '), trusted_mods.end()); + std::vector mod_list_trusted = str_split(trusted_mods, ','); + + mod_list_http.insert(mod_list_http.end(), mod_list_trusted.begin(), mod_list_trusted.end()); + if (std::find(mod_list_http.begin(), mod_list_http.end(), mod_name) == mod_list_http.end()) { + lua_pushnil(L); + return 1; + } + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "http_add_fetch"); + + lua_newtable(L); + HTTP_API(fetch_async); + HTTP_API(fetch_async_get); + + // Stack now looks like this: + // + // Now call core.http_add_fetch to append .fetch(request, callback) to table + lua_call(L, 1, 1); + + return 1; +} +#endif + +void ModApiHttp::Initialize(lua_State *L, int top) +{ +#if USE_CURL + API_FCT(request_http_api); +#endif +} diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h new file mode 100644 index 000000000..077ade691 --- /dev/null +++ b/src/script/lua_api/l_http.h @@ -0,0 +1,50 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef L_HTTP_H_ +#define L_HTTP_H_ + +#include "lua_api/l_base.h" +#include "config.h" + +struct HTTPFetchRequest; +struct HTTPFetchResult; + +class ModApiHttp : public ModApiBase { +private: +#if USE_CURL + // Helpers for HTTP fetch functions + static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req); + static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true); + + // http_fetch_async({url=, timeout=, post_data=}) + static int l_http_fetch_async(lua_State *L); + + // http_fetch_async_get(handle) + static int l_http_fetch_async_get(lua_State *L); + + // request_http_api() + static int l_request_http_api(lua_State *L); +#endif + +public: + static void Initialize(lua_State *L, int top); +}; + +#endif /* L_HTTP_H_ */ diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index 33bc5c2a7..e313d55f8 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_util.h" #include "lua_api/l_vmanip.h" #include "lua_api/l_settings.h" +#include "lua_api/l_http.h" extern "C" { #include "lualib.h" @@ -89,6 +90,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) ModApiRollback::Initialize(L, top); ModApiServer::Initialize(L, top); ModApiUtil::Initialize(L, top); + ModApiHttp::Initialize(L, top); // Register reference classes (userdata) InvRef::Register(L); From 9961185550ff2a8ada61455ac351f85ea7122aa2 Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Sat, 20 Feb 2016 10:58:40 +0100 Subject: [PATCH 15/17] Fix getting pointed node Fixes #3719 Closes #3753 --- src/game.cpp | 136 +++++++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 9729ca477..18ca11d6f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -304,7 +304,7 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio INodeDefManager *nodedef = client->getNodeDefManager(); ClientMap &map = client->getEnv().getClientMap(); - f32 mindistance = BS * 1001; + f32 min_distance = BS * 1001; // First try to find a pointed at active object if (look_for_object) { @@ -324,7 +324,7 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio hud->setSelectionPos(pos, camera_offset); } - mindistance = (selected_object->getPosition() - camera_position).getLength(); + min_distance = (selected_object->getPosition() - camera_position).getLength(); result.type = POINTEDTHING_OBJECT; result.object_id = selected_object->getId(); @@ -333,14 +333,13 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio // That didn't work, try to find a pointed at node - v3s16 pos_i = floatToInt(player_position, BS); /*infostream<<"pos_i=("< 0 ? a : 1); @@ -357,24 +356,25 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio if (xend == 32767) xend = 32766; - for (s16 y = ystart; y <= yend; y++) - for (s16 z = zstart; z <= zend; z++) + v3s16 pointed_pos(0, 0, 0); + + for (s16 y = ystart; y <= yend; y++) { + for (s16 z = zstart; z <= zend; z++) { for (s16 x = xstart; x <= xend; x++) { MapNode n; bool is_valid_position; n = map.getNodeNoEx(v3s16(x, y, z), &is_valid_position); - if (!is_valid_position) + if (!is_valid_position) { continue; - - if (!isPointableNode(n, client, liquids_pointable)) + } + if (!isPointableNode(n, client, liquids_pointable)) { continue; - + } std::vector boxes = n.getSelectionBoxes(nodedef); v3s16 np(x, y, z); v3f npf = intToFloat(np, BS); - for (std::vector::const_iterator i = boxes.begin(); i != boxes.end(); ++i) { @@ -382,62 +382,80 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio box.MinEdge += npf; box.MaxEdge += npf; - for (u16 j = 0; j < 6; j++) { - v3s16 facedir = g_6dirs[j]; - aabb3f facebox = box; - - f32 d = 0.001 * BS; - - if (facedir.X > 0) - facebox.MinEdge.X = facebox.MaxEdge.X - d; - else if (facedir.X < 0) - facebox.MaxEdge.X = facebox.MinEdge.X + d; - else if (facedir.Y > 0) - facebox.MinEdge.Y = facebox.MaxEdge.Y - d; - else if (facedir.Y < 0) - facebox.MaxEdge.Y = facebox.MinEdge.Y + d; - else if (facedir.Z > 0) - facebox.MinEdge.Z = facebox.MaxEdge.Z - d; - else if (facedir.Z < 0) - facebox.MaxEdge.Z = facebox.MinEdge.Z + d; - - v3f centerpoint = facebox.getCenter(); - f32 distance = (centerpoint - camera_position).getLength(); - - if (distance >= mindistance) - continue; - - if (!facebox.intersectsWithLine(shootline)) - continue; - - v3s16 np_above = np + facedir; - - result.type = POINTEDTHING_NODE; - result.node_undersurface = np; - result.node_abovesurface = np_above; - mindistance = distance; - - selectionboxes->clear(); - for (std::vector::const_iterator - i2 = boxes.begin(); - i2 != boxes.end(); ++i2) { - aabb3f box = *i2; - box.MinEdge += v3f(-d, -d, -d); - box.MaxEdge += v3f(d, d, d); - selectionboxes->push_back(box); - } - hud->setSelectionPos(npf, camera_offset); + v3f centerpoint = box.getCenter(); + f32 distance = (centerpoint - camera_position).getLength(); + if (distance >= min_distance) { + continue; } + if (!box.intersectsWithLine(shootline)) { + continue; + } + result.type = POINTEDTHING_NODE; + min_distance = distance; + pointed_pos = np; } - } // for coords + } + } + } + + if (result.type == POINTEDTHING_NODE) { + f32 d = 0.001 * BS; + MapNode n = map.getNodeNoEx(pointed_pos); + v3f npf = intToFloat(pointed_pos, BS); + std::vector boxes = n.getSelectionBoxes(nodedef); + f32 face_min_distance = 1000 * BS; + for (std::vector::const_iterator + i = boxes.begin(); + i != boxes.end(); ++i) { + aabb3f box = *i; + box.MinEdge += npf; + box.MaxEdge += npf; + for (u16 j = 0; j < 6; j++) { + v3s16 facedir = g_6dirs[j]; + aabb3f facebox = box; + if (facedir.X > 0) { + facebox.MinEdge.X = facebox.MaxEdge.X - d; + } else if (facedir.X < 0) { + facebox.MaxEdge.X = facebox.MinEdge.X + d; + } else if (facedir.Y > 0) { + facebox.MinEdge.Y = facebox.MaxEdge.Y - d; + } else if (facedir.Y < 0) { + facebox.MaxEdge.Y = facebox.MinEdge.Y + d; + } else if (facedir.Z > 0) { + facebox.MinEdge.Z = facebox.MaxEdge.Z - d; + } else if (facedir.Z < 0) { + facebox.MaxEdge.Z = facebox.MinEdge.Z + d; + } + v3f centerpoint = facebox.getCenter(); + f32 distance = (centerpoint - camera_position).getLength(); + if (distance >= face_min_distance) + continue; + if (!facebox.intersectsWithLine(shootline)) + continue; + result.node_abovesurface = pointed_pos + facedir; + face_min_distance = distance; + } + } + selectionboxes->clear(); + for (std::vector::const_iterator + i = boxes.begin(); + i != boxes.end(); ++i) { + aabb3f box = *i; + box.MinEdge += v3f(-d, -d, -d); + box.MaxEdge += v3f(d, d, d); + selectionboxes->push_back(box); + } + hud->setSelectionPos(intToFloat(pointed_pos, BS), camera_offset); + result.node_undersurface = pointed_pos; + } // Update selection mesh light level and vertex colors if (selectionboxes->size() > 0) { v3f pf = hud->getSelectionPos(); - v3s16 p = floatToInt(pf, BS); + v3s16 p = floatToInt(pf, BS); // Get selection mesh light level - MapNode n = map.getNodeNoEx(p); + MapNode n = map.getNodeNoEx(p); u16 node_light = getInteriorLight(n, -1, nodedef); u16 light_level = node_light; From a26970cdd4aec995416566cd75dd271c6b485f16 Mon Sep 17 00:00:00 2001 From: est31 Date: Mon, 22 Feb 2016 17:43:42 +0100 Subject: [PATCH 16/17] Android: hardcode leveldb revision Newest leveldb commit breaks build. With no fix in sight, there is no other way than to fall back to the last working leveldb revision, and hardcode it. Workaround for upstream bug https://github.com/google/leveldb/issues/340 --- build/android/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/android/Makefile b/build/android/Makefile index 64a50330a..2fa4aa9ab 100644 --- a/build/android/Makefile +++ b/build/android/Makefile @@ -90,6 +90,7 @@ LEVELDB_LIB = $(LEVELDB_DIR)libleveldb.a LEVELDB_TIMESTAMP = $(LEVELDB_DIR)/timestamp LEVELDB_TIMESTAMP_INT = $(ROOT)/deps/leveldb_timestamp LEVELDB_URL_GIT = https://github.com/google/leveldb +LEVELDB_COMMIT = 2d0320a458d0e6a20fff46d5f80b18bfdcce7018 OPENAL_DIR = $(ROOT)/deps/openal-soft/ OPENAL_LIB = $(OPENAL_DIR)libs/$(TARGET_ABI)/libopenal.so @@ -363,6 +364,8 @@ leveldb_download : mkdir -p ${ROOT}/deps; \ cd ${ROOT}/deps ; \ git clone ${LEVELDB_URL_GIT} || exit 1; \ + cd ${LEVELDB_DIR} || exit 1; \ + git checkout ${LEVELDB_COMMIT} || exit 1; \ fi leveldb : $(LEVELDB_LIB) From e17fbb31d6db3e6d2d9b68f5793ad8b2146f7bcc Mon Sep 17 00:00:00 2001 From: orwell96 Date: Mon, 1 Feb 2016 19:29:53 +0100 Subject: [PATCH 17/17] Reset block send timer when invoking setBlock(s)NotSent() As stated in this forum thread [1], I noticed that there is a 2 second interval in which inventory changes are shown on the client. @yyt16384 found the source of these 2 seconds: m_nothing_to_send_pause_timer is set to 2.0 every time there are no changes to make, but this timer is not reset when SetBlockNotSent or setBlocksNotSent are invoked. So in worst case, the changed block will be sent over 2 seconds too late. With this change, changed inventories are updated almost immediately, but it causes additional connection load. --- src/clientiface.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/clientiface.cpp b/src/clientiface.cpp index e7f127b84..8a1a62694 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -391,6 +391,7 @@ void RemoteClient::SentBlock(v3s16 p) void RemoteClient::SetBlockNotSent(v3s16 p) { m_nearest_unsent_d = 0; + m_nothing_to_send_pause_timer = 0; if(m_blocks_sending.find(p) != m_blocks_sending.end()) m_blocks_sending.erase(p); @@ -401,6 +402,7 @@ void RemoteClient::SetBlockNotSent(v3s16 p) void RemoteClient::SetBlocksNotSent(std::map &blocks) { m_nearest_unsent_d = 0; + m_nothing_to_send_pause_timer = 0; for(std::map::iterator i = blocks.begin();