mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-10-31 07:25:22 +01:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/master'
This commit is contained in:
		| @@ -12,7 +12,7 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") | ||||
| # Also remember to set PROTOCOL_VERSION in clientserver.h when releasing | ||||
| set(VERSION_MAJOR 0) | ||||
| set(VERSION_MINOR 4) | ||||
| set(VERSION_PATCH 2-rc1) | ||||
| set(VERSION_PATCH 3) | ||||
| if(VERSION_EXTRA) | ||||
| 	set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA}) | ||||
| endif() | ||||
|   | ||||
| @@ -260,6 +260,16 @@ function minetest.node_punch(pos, node, puncher) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function minetest.handle_node_drops(pos, drops, digger) | ||||
| 	-- Add dropped items to object's inventory | ||||
| 	if digger:get_inventory() then | ||||
| 		local _, dropped_item | ||||
| 		for _, dropped_item in ipairs(drops) do | ||||
| 			digger:get_inventory():add_item("main", dropped_item) | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function minetest.node_dig(pos, node, digger) | ||||
| 	minetest.debug("node_dig") | ||||
|  | ||||
| @@ -284,14 +294,9 @@ function minetest.node_dig(pos, node, digger) | ||||
| 	local dp = minetest.get_dig_params(def.groups, tp) | ||||
| 	wielded:add_wear(dp.wear) | ||||
| 	digger:set_wielded_item(wielded) | ||||
|  | ||||
| 	-- Add dropped items to object's inventory | ||||
| 	if digger:get_inventory() then | ||||
| 		local _, dropped_item | ||||
| 		for _, dropped_item in ipairs(drops) do | ||||
| 			digger:get_inventory():add_item("main", dropped_item) | ||||
| 		end | ||||
| 	end | ||||
| 	 | ||||
| 	-- Handle drops | ||||
| 	minetest.handle_node_drops(pos, drops, digger) | ||||
|  | ||||
| 	local oldmetadata = nil | ||||
| 	if def.after_dig_node then | ||||
|   | ||||
| @@ -891,6 +891,12 @@ minetest.get_craft_recipe(output) -> input | ||||
| ^ input.items = for example { stack 1, stack 2, stack 3, stack 4, | ||||
|                               stack 5, stack 6, stack 7, stack 8, stack 9 } | ||||
| ^ input.items = nil if no recipe found | ||||
| minetest.handle_node_drops(pos, drops, digger) | ||||
| ^ drops: list of itemstrings | ||||
| ^ Handles drops from nodes after digging: Default action is to put them into | ||||
|   digger's inventory | ||||
| ^ Can be overridden to get different functionality (eg. dropping items on | ||||
|   ground) | ||||
|  | ||||
| Rollbacks: | ||||
| minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds | ||||
|   | ||||
| @@ -157,43 +157,21 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| void ClientMap::updateDrawList(video::IVideoDriver* driver) | ||||
| { | ||||
| 	ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); | ||||
| 	g_profiler->add("CM::updateDrawList() count", 1); | ||||
| 
 | ||||
| 	INodeDefManager *nodemgr = m_gamedef->ndef(); | ||||
| 
 | ||||
| 	//m_dout<<DTIME<<"Rendering map..."<<std::endl;
 | ||||
| 	DSTACK(__FUNCTION_NAME); | ||||
| 
 | ||||
| 	bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; | ||||
| 	 | ||||
| 	std::string prefix; | ||||
| 	if(pass == scene::ESNRP_SOLID) | ||||
| 		prefix = "CM: solid: "; | ||||
| 	else | ||||
| 		prefix = "CM: transparent: "; | ||||
| 
 | ||||
| 	/*
 | ||||
| 		This is called two times per frame, reset on the non-transparent one | ||||
| 	*/ | ||||
| 	if(pass == scene::ESNRP_SOLID) | ||||
| 	for(core::map<v3s16, MapBlock*>::Iterator | ||||
| 			i = m_drawlist.getIterator(); | ||||
| 			i.atEnd() == false; i++) | ||||
| 	{ | ||||
| 		m_last_drawn_sectors.clear(); | ||||
| 		MapBlock *block = i.getNode()->getValue(); | ||||
| 		block->refDrop(); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get time for measuring timeout. | ||||
| 		 | ||||
| 		Measuring time is very useful for long delays when the | ||||
| 		machine is swapping a lot. | ||||
| 	*/ | ||||
| 	int time1 = time(0); | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get animation parameters | ||||
| 	*/ | ||||
| 	float animation_time = m_client->getAnimationTime(); | ||||
| 	int crack = m_client->getCrackLevel(); | ||||
| 	u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); | ||||
| 	m_drawlist.clear(); | ||||
| 
 | ||||
| 	m_camera_mutex.Lock(); | ||||
| 	v3f camera_position = m_camera_position; | ||||
| @@ -201,17 +179,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 	f32 camera_fov = m_camera_fov; | ||||
| 	m_camera_mutex.Unlock(); | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get all blocks and draw all visible ones | ||||
| 	*/ | ||||
| 	// Use a higher fov to accomodate faster camera movements.
 | ||||
| 	// Blocks are cropped better when they are drawn.
 | ||||
| 	// Or maybe they aren't? Well whatever.
 | ||||
| 	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( | ||||
| @@ -223,13 +199,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 			p_nodes_max.Y / MAP_BLOCKSIZE + 1, | ||||
| 			p_nodes_max.Z / MAP_BLOCKSIZE + 1); | ||||
| 	 | ||||
| 	u32 vertex_count = 0; | ||||
| 	u32 meshbuffer_count = 0; | ||||
| 	 | ||||
| 	// For limiting number of mesh animations per frame
 | ||||
| 	u32 mesh_animate_count = 0; | ||||
| 	u32 mesh_animate_count_far = 0; | ||||
| 	 | ||||
| 	// Number of blocks in rendering range
 | ||||
| 	u32 blocks_in_range = 0; | ||||
| 	// Number of blocks occlusion culled
 | ||||
| @@ -242,18 +211,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 	// Blocks that were drawn and had a mesh
 | ||||
| 	u32 blocks_drawn = 0; | ||||
| 	// Blocks which had a corresponding meshbuffer for this pass
 | ||||
| 	u32 blocks_had_pass_meshbuf = 0; | ||||
| 	//u32 blocks_had_pass_meshbuf = 0;
 | ||||
| 	// Blocks from which stuff was actually drawn
 | ||||
| 	u32 blocks_without_stuff = 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Collect a set of blocks for drawing | ||||
| 	*/ | ||||
| 	 | ||||
| 	core::map<v3s16, MapBlock*> drawset; | ||||
| 
 | ||||
| 	{ | ||||
| 	ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG); | ||||
| 	//u32 blocks_without_stuff = 0;
 | ||||
| 
 | ||||
| 	for(core::map<v2s16, MapSector*>::Iterator | ||||
| 			si = m_sectors.getIterator(); | ||||
| @@ -380,36 +340,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 					&& d > m_control.wanted_min_range * BS) | ||||
| 				continue; | ||||
| 
 | ||||
| 			// Mesh animation
 | ||||
| 			{ | ||||
| 				//JMutexAutoLock lock(block->mesh_mutex);
 | ||||
| 				MapBlockMesh *mapBlockMesh = block->mesh; | ||||
| 				// Pretty random but this should work somewhat nicely
 | ||||
| 				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) | ||||
| 						mesh_animate_count++; | ||||
| 					if(animated && faraway) | ||||
| 						mesh_animate_count_far++; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					mapBlockMesh->decreaseAnimationForceTimer(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// Add to set
 | ||||
| 			drawset[block->getPos()] = block; | ||||
| 			 | ||||
| 			block->refGrab(); | ||||
| 			m_drawlist[block->getPos()] = block; | ||||
| 
 | ||||
| 			sector_blocks_drawn++; | ||||
| 			blocks_drawn++; | ||||
| 
 | ||||
| @@ -418,8 +352,127 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 		if(sector_blocks_drawn != 0) | ||||
| 			m_last_drawn_sectors[sp] = true; | ||||
| 	} | ||||
| 	} // ScopeProfiler
 | ||||
| 	 | ||||
| 	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) | ||||
| 		g_profiler->avg("CM: blocks in range without mesh (frac)", | ||||
| 				(float)blocks_in_range_without_mesh/blocks_in_range); | ||||
| 	g_profiler->avg("CM: blocks drawn", blocks_drawn); | ||||
| } | ||||
| 
 | ||||
| struct MeshBufList | ||||
| { | ||||
| 	video::SMaterial m; | ||||
| 	core::list<scene::IMeshBuffer*> bufs; | ||||
| }; | ||||
| 
 | ||||
| struct MeshBufListList | ||||
| { | ||||
| 	core::list<MeshBufList> lists; | ||||
| 	 | ||||
| 	void clear() | ||||
| 	{ | ||||
| 		lists.clear(); | ||||
| 	} | ||||
| 	 | ||||
| 	void add(scene::IMeshBuffer *buf) | ||||
| 	{ | ||||
| 		for(core::list<MeshBufList>::Iterator i = lists.begin(); | ||||
| 				i != lists.end(); i++){ | ||||
| 			MeshBufList &l = *i; | ||||
| 			if(l.m == buf->getMaterial()){ | ||||
| 				l.bufs.push_back(buf); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 		MeshBufList l; | ||||
| 		l.m = buf->getMaterial(); | ||||
| 		l.bufs.push_back(buf); | ||||
| 		lists.push_back(l); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| { | ||||
| 	DSTACK(__FUNCTION_NAME); | ||||
| 
 | ||||
| 	bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; | ||||
| 	 | ||||
| 	std::string prefix; | ||||
| 	if(pass == scene::ESNRP_SOLID) | ||||
| 		prefix = "CM: solid: "; | ||||
| 	else | ||||
| 		prefix = "CM: transparent: "; | ||||
| 
 | ||||
| 	/*
 | ||||
| 		This is called two times per frame, reset on the non-transparent one | ||||
| 	*/ | ||||
| 	if(pass == scene::ESNRP_SOLID) | ||||
| 	{ | ||||
| 		m_last_drawn_sectors.clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get time for measuring timeout. | ||||
| 		 | ||||
| 		Measuring time is very useful for long delays when the | ||||
| 		machine is swapping a lot. | ||||
| 	*/ | ||||
| 	int time1 = time(0); | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get animation parameters | ||||
| 	*/ | ||||
| 	float animation_time = m_client->getAnimationTime(); | ||||
| 	int crack = m_client->getCrackLevel(); | ||||
| 	u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); | ||||
| 
 | ||||
| 	m_camera_mutex.Lock(); | ||||
| 	v3f camera_position = m_camera_position; | ||||
| 	v3f camera_direction = m_camera_direction; | ||||
| 	f32 camera_fov = m_camera_fov; | ||||
| 	m_camera_mutex.Unlock(); | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Get all blocks and draw all visible ones | ||||
| 	*/ | ||||
| 
 | ||||
| 	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); | ||||
| 	 | ||||
| 	u32 vertex_count = 0; | ||||
| 	u32 meshbuffer_count = 0; | ||||
| 	 | ||||
| 	// For limiting number of mesh animations per frame
 | ||||
| 	u32 mesh_animate_count = 0; | ||||
| 	u32 mesh_animate_count_far = 0; | ||||
| 	 | ||||
| 	// Blocks that had mesh that would have been drawn according to
 | ||||
| 	// rendering range (if max blocks limit didn't kick in)
 | ||||
| 	u32 blocks_would_have_drawn = 0; | ||||
| 	// Blocks that were drawn and had a mesh
 | ||||
| 	u32 blocks_drawn = 0; | ||||
| 	// Blocks which had a corresponding meshbuffer for this pass
 | ||||
| 	u32 blocks_had_pass_meshbuf = 0; | ||||
| 	// Blocks from which stuff was actually drawn
 | ||||
| 	u32 blocks_without_stuff = 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Draw the selected MapBlocks | ||||
| 	*/ | ||||
| @@ -427,10 +480,90 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 	{ | ||||
| 	ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); | ||||
| 
 | ||||
| 	int timecheck_counter = 0; | ||||
| 	MeshBufListList drawbufs; | ||||
| 
 | ||||
| 	for(core::map<v3s16, MapBlock*>::Iterator | ||||
| 			i = drawset.getIterator(); | ||||
| 			i = m_drawlist.getIterator(); | ||||
| 			i.atEnd() == false; i++) | ||||
| 	{ | ||||
| 		MapBlock *block = i.getNode()->getValue(); | ||||
| 
 | ||||
| 		// If the mesh of the block happened to get deleted, ignore it
 | ||||
| 		if(block->mesh == NULL) | ||||
| 			continue; | ||||
| 		 | ||||
| 		float d = 0.0; | ||||
| 		if(isBlockInSight(block->getPos(), camera_position, | ||||
| 				camera_direction, camera_fov, | ||||
| 				100000*BS, &d) == false) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		// Mesh animation
 | ||||
| 		{ | ||||
| 			//JMutexAutoLock lock(block->mesh_mutex);
 | ||||
| 			MapBlockMesh *mapBlockMesh = block->mesh; | ||||
| 			assert(mapBlockMesh); | ||||
| 			// Pretty random but this should work somewhat nicely
 | ||||
| 			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) | ||||
| 					mesh_animate_count++; | ||||
| 				if(animated && faraway) | ||||
| 					mesh_animate_count_far++; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				mapBlockMesh->decreaseAnimationForceTimer(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/*
 | ||||
| 			Get the meshbuffers of the block | ||||
| 		*/ | ||||
| 		{ | ||||
| 			//JMutexAutoLock lock(block->mesh_mutex);
 | ||||
| 
 | ||||
| 			MapBlockMesh *mapBlockMesh = block->mesh; | ||||
| 			assert(mapBlockMesh); | ||||
| 
 | ||||
| 			scene::SMesh *mesh = mapBlockMesh->getMesh(); | ||||
| 			assert(mesh); | ||||
| 
 | ||||
| 			u32 c = mesh->getMeshBufferCount(); | ||||
| 			for(u32 i=0; i<c; i++) | ||||
| 			{ | ||||
| 				scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); | ||||
| 				const video::SMaterial& material = buf->getMaterial(); | ||||
| 				video::IMaterialRenderer* rnd = | ||||
| 						driver->getMaterialRenderer(material.MaterialType); | ||||
| 				bool transparent = (rnd && rnd->isTransparent()); | ||||
| 				if(transparent == is_transparent_pass) | ||||
| 				{ | ||||
| 					if(buf->getVertexCount() == 0) | ||||
| 						errorstream<<"Block ["<<analyze_block(block) | ||||
| 								<<"] contains an empty meshbuf"<<std::endl; | ||||
| 					drawbufs.add(buf); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	core::list<MeshBufList> &lists = drawbufs.lists; | ||||
| 	 | ||||
| 	int timecheck_counter = 0; | ||||
| 	for(core::list<MeshBufList>::Iterator i = lists.begin(); | ||||
| 			i != lists.end(); i++) | ||||
| 	{ | ||||
| 		{ | ||||
| 			timecheck_counter++; | ||||
| @@ -447,9 +580,20 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		MapBlock *block = i.getNode()->getValue(); | ||||
| 
 | ||||
| 		MeshBufList &list = *i; | ||||
| 		 | ||||
| 		driver->setMaterial(list.m); | ||||
| 		 | ||||
| 		for(core::list<scene::IMeshBuffer*>::Iterator j = list.bufs.begin(); | ||||
| 				j != list.bufs.end(); j++) | ||||
| 		{ | ||||
| 			scene::IMeshBuffer *buf = *j; | ||||
| 			driver->drawMeshBuffer(buf); | ||||
| 			vertex_count += buf->getVertexCount(); | ||||
| 			meshbuffer_count++; | ||||
| 		} | ||||
| #if 0 | ||||
| 		/*
 | ||||
| 			Draw the faces of the block | ||||
| 		*/ | ||||
| @@ -494,17 +638,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) | ||||
| 			else | ||||
| 				blocks_without_stuff++; | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| 	} // ScopeProfiler
 | ||||
| 	 | ||||
| 	// Log only on solid pass because values are the same
 | ||||
| 	if(pass == scene::ESNRP_SOLID){ | ||||
| 		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) | ||||
| 			g_profiler->avg("CM: blocks in range without mesh (frac)", | ||||
| 					(float)blocks_in_range_without_mesh/blocks_in_range); | ||||
| 		g_profiler->avg("CM: blocks drawn", blocks_drawn); | ||||
| 		g_profiler->avg("CM: animated meshes", mesh_animate_count); | ||||
| 		g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); | ||||
| 	} | ||||
|   | ||||
| @@ -113,7 +113,8 @@ public: | ||||
| 	{ | ||||
| 		return m_box; | ||||
| 	} | ||||
| 
 | ||||
| 	 | ||||
| 	void updateDrawList(video::IVideoDriver* driver); | ||||
| 	void renderMap(video::IVideoDriver* driver, s32 pass); | ||||
| 
 | ||||
| 	int getBackgroundBrightness(float max_d, u32 daylight_factor, | ||||
| @@ -141,6 +142,8 @@ private: | ||||
| 	v3f m_camera_direction; | ||||
| 	f32 m_camera_fov; | ||||
| 	JMutex m_camera_mutex; | ||||
| 
 | ||||
| 	core::map<v3s16, MapBlock*> m_drawlist; | ||||
| 	 | ||||
| 	core::map<v2s16, bool> m_last_drawn_sectors; | ||||
| }; | ||||
|   | ||||
| @@ -65,9 +65,11 @@ with this program; if not, write to the Free Software Foundation, Inc., | ||||
| 		TOSERVER_INVENTORY_FIELDS | ||||
| 		16-bit node ids | ||||
| 		TOCLIENT_DETACHED_INVENTORY | ||||
| 	PROTOCOL_VERSION 13: | ||||
| 		InventoryList field "Width" (deserialization fails with old versions) | ||||
| */ | ||||
| 
 | ||||
| #define PROTOCOL_VERSION 12 | ||||
| #define PROTOCOL_VERSION 13 | ||||
| 
 | ||||
| #define PROTOCOL_ID 0x4f457403 | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/game.cpp
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/game.cpp
									
									
									
									
									
								
							| @@ -1235,6 +1235,9 @@ void the_game( | ||||
| 	float object_hit_delay_timer = 0.0; | ||||
| 	float time_from_last_punch = 10; | ||||
| 
 | ||||
| 	float update_draw_list_timer = 0.0; | ||||
| 	v3f update_draw_list_last_cam_dir; | ||||
| 
 | ||||
| 	bool invert_mouse = g_settings->getBool("invert_mouse"); | ||||
| 
 | ||||
| 	bool respawn_menu_active = false; | ||||
| @@ -2697,7 +2700,19 @@ void the_game( | ||||
| 				item = mlist->getItem(client.getPlayerItem()); | ||||
| 			camera.wield(item); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		/*
 | ||||
| 			Update block draw list every 200ms or when camera direction has | ||||
| 			changed much | ||||
| 		*/ | ||||
| 		update_draw_list_timer += dtime; | ||||
| 		if(update_draw_list_timer >= 0.2 || | ||||
| 				update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2){ | ||||
| 			update_draw_list_timer = 0; | ||||
| 			client.getEnv().getClientMap().updateDrawList(driver); | ||||
| 			update_draw_list_last_cam_dir = camera_direction; | ||||
| 		} | ||||
| 
 | ||||
| 		/*
 | ||||
| 			Drawing begins | ||||
| 		*/ | ||||
|   | ||||
| @@ -733,6 +733,54 @@ void GUIFormSpecMenu::drawMenu() | ||||
| 
 | ||||
| void GUIFormSpecMenu::updateSelectedItem() | ||||
| { | ||||
| 	// WARNING: BLACK MAGIC
 | ||||
| 	// See if there is a stack suited for our current guess.
 | ||||
| 	// If such stack does not exist, clear the guess.
 | ||||
| 	if(m_selected_content_guess.name != "") | ||||
| 	{ | ||||
| 		bool found = false; | ||||
| 		for(u32 i=0; i<m_inventorylists.size() && !found; i++){ | ||||
| 			const ListDrawSpec &s = m_inventorylists[i]; | ||||
| 			Inventory *inv = m_invmgr->getInventory(s.inventoryloc); | ||||
| 			if(!inv) | ||||
| 				continue; | ||||
| 			InventoryList *list = inv->getList(s.listname); | ||||
| 			if(!list) | ||||
| 				continue; | ||||
| 			for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){ | ||||
| 				u32 item_i = i + s.start_item_i; | ||||
| 				if(item_i >= list->getSize()) | ||||
| 					continue; | ||||
| 				ItemStack stack = list->getItem(item_i); | ||||
| 				if(stack.name == m_selected_content_guess.name && | ||||
| 						stack.count == m_selected_content_guess.count){ | ||||
| 					found = true; | ||||
| 					if(m_selected_item){ | ||||
| 						// If guessed stack is already selected, all is fine
 | ||||
| 						if(m_selected_item->inventoryloc == s.inventoryloc && | ||||
| 								m_selected_item->listname == s.listname && | ||||
| 								m_selected_item->i == (s32)item_i && | ||||
| 								m_selected_amount == stack.count){ | ||||
| 							break; | ||||
| 						} | ||||
| 						delete m_selected_item; | ||||
| 						m_selected_item = NULL; | ||||
| 					} | ||||
| 					infostream<<"Client: Changing selected content guess to " | ||||
| 							<<s.inventoryloc.dump()<<" "<<s.listname | ||||
| 							<<" "<<item_i<<std::endl; | ||||
| 					m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i); | ||||
| 					m_selected_amount = stack.count; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if(!found){ | ||||
| 			infostream<<"Client: Discarding selected content guess: " | ||||
| 					<<m_selected_content_guess.getItemString()<<std::endl; | ||||
| 			m_selected_content_guess.name = ""; | ||||
| 		} | ||||
| 	} | ||||
| 	// If the selected stack has become empty for some reason, deselect it.
 | ||||
| 	// If the selected stack has become smaller, adjust m_selected_amount.
 | ||||
| 	if(m_selected_item) | ||||
| @@ -1054,21 +1102,28 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) | ||||
| 			// Check how many items can be moved
 | ||||
| 			move_amount = stack_from.count = MYMIN(move_amount, stack_from.count); | ||||
| 			ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef()); | ||||
| 			if(leftover.count == stack_from.count) | ||||
| 			// If source stack cannot be added to destination stack at all,
 | ||||
| 			// they are swapped
 | ||||
| 			if(leftover.count == stack_from.count && leftover.name == stack_from.name) | ||||
| 			{ | ||||
| 				// Swap the stacks
 | ||||
| 				m_selected_amount -= stack_to.count; | ||||
| 				m_selected_amount = stack_to.count; | ||||
| 				// In case the server doesn't directly swap them but instead
 | ||||
| 				// moves stack_to somewhere else, set this
 | ||||
| 				m_selected_content_guess = stack_to; | ||||
| 				m_selected_content_guess_inventory = s.inventoryloc; | ||||
| 			} | ||||
| 			// Source stack goes fully into destination stack
 | ||||
| 			else if(leftover.empty()) | ||||
| 			{ | ||||
| 				// Item fits
 | ||||
| 				m_selected_amount -= move_amount; | ||||
| 				m_selected_content_guess = ItemStack(); // Clear
 | ||||
| 			} | ||||
| 			// Source stack goes partly into destination stack
 | ||||
| 			else | ||||
| 			{ | ||||
| 				// Item only fits partially
 | ||||
| 				move_amount -= leftover.count; | ||||
| 				m_selected_amount -= move_amount; | ||||
| 				m_selected_content_guess = ItemStack(); // Clear
 | ||||
| 			} | ||||
| 
 | ||||
| 			infostream<<"Handing IACTION_MOVE to manager"<<std::endl; | ||||
| @@ -1084,6 +1139,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) | ||||
| 		} | ||||
| 		else if(drop_amount > 0) | ||||
| 		{ | ||||
| 			m_selected_content_guess = ItemStack(); // Clear
 | ||||
| 
 | ||||
| 			// Send IACTION_DROP
 | ||||
| 
 | ||||
| 			assert(m_selected_item && m_selected_item->isValid()); | ||||
| @@ -1107,6 +1164,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) | ||||
| 		} | ||||
| 		else if(craft_amount > 0) | ||||
| 		{ | ||||
| 			m_selected_content_guess = ItemStack(); // Clear
 | ||||
| 
 | ||||
| 			// Send IACTION_CRAFT
 | ||||
| 
 | ||||
| 			assert(s.isValid()); | ||||
| @@ -1126,6 +1185,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) | ||||
| 			m_selected_item = NULL; | ||||
| 			m_selected_amount = 0; | ||||
| 			m_selected_dragging = false; | ||||
| 			m_selected_content_guess = ItemStack(); | ||||
| 		} | ||||
| 	} | ||||
| 	if(event.EventType==EET_GUI_EVENT) | ||||
|   | ||||
| @@ -212,6 +212,12 @@ protected: | ||||
| 	ItemSpec *m_selected_item; | ||||
| 	u32 m_selected_amount; | ||||
| 	bool m_selected_dragging; | ||||
| 	 | ||||
| 	// WARNING: BLACK MAGIC
 | ||||
| 	// Used to guess and keep up with some special things the server can do.
 | ||||
| 	// If name is "", no guess exists.
 | ||||
| 	ItemStack m_selected_content_guess; | ||||
| 	InventoryLocation m_selected_content_guess_inventory; | ||||
| 
 | ||||
| 	v2s32 m_pointer; | ||||
| 	gui::IGUIStaticText *m_tooltip_element; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| enum | ||||
| { | ||||
| 	GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR, | ||||
| 	//buttons
 | ||||
| 	// buttons
 | ||||
| 	GUI_ID_KEY_FORWARD_BUTTON, | ||||
| 	GUI_ID_KEY_BACKWARD_BUTTON, | ||||
| 	GUI_ID_KEY_LEFT_BUTTON, | ||||
| @@ -53,7 +53,9 @@ enum | ||||
| 	GUI_ID_KEY_DROP_BUTTON, | ||||
| 	GUI_ID_KEY_INVENTORY_BUTTON, | ||||
| 	GUI_ID_KEY_DUMP_BUTTON, | ||||
| 	GUI_ID_KEY_RANGE_BUTTON | ||||
| 	GUI_ID_KEY_RANGE_BUTTON, | ||||
| 	// other
 | ||||
| 	GUI_ID_CB_AUX1_DESCENDS, | ||||
| }; | ||||
| 
 | ||||
| GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, | ||||
| @@ -135,6 +137,18 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) | ||||
| 		else | ||||
| 			offset += v2s32(0, 25); | ||||
| 	} | ||||
| 	 | ||||
| 	{ | ||||
| 		s32 option_x = offset.X + 10; | ||||
| 		s32 option_y = offset.Y; | ||||
| 		u32 option_w = 180; | ||||
| 		{ | ||||
| 			core::rect<s32> rect(0, 0, option_w, 30); | ||||
| 			rect += topleft + v2s32(option_x, option_y); | ||||
| 			Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this, | ||||
| 					GUI_ID_CB_AUX1_DESCENDS, wgettext("\"Use\" = climb down")); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	{ | ||||
| 		core::rect < s32 > rect(0, 0, 100, 30); | ||||
| @@ -177,6 +191,11 @@ bool GUIKeyChangeMenu::acceptInput() | ||||
| 		key_setting *k = key_settings.at(i); | ||||
| 		g_settings->set(k->setting_name, k->key.sym()); | ||||
| 	} | ||||
| 	{ | ||||
| 		gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS); | ||||
| 		if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) | ||||
| 			g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked()); | ||||
| 	} | ||||
| 	clearKeyCache(); | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., | ||||
| #include "tile.h" // getTexturePath
 | ||||
| #include "filesys.h" | ||||
| #include "util/string.h" | ||||
| #include "subgame.h" | ||||
| 
 | ||||
| struct CreateWorldDestMainMenu : public CreateWorldDest | ||||
| { | ||||
| @@ -47,6 +48,22 @@ struct CreateWorldDestMainMenu : public CreateWorldDest | ||||
| 	{} | ||||
| 	void accepted(std::wstring name, std::string gameid) | ||||
| 	{ | ||||
| 		std::string name_narrow = wide_to_narrow(name); | ||||
| 		if(!string_allowed_blacklist(name_narrow, WORLDNAME_BLACKLISTED_CHARS)) | ||||
| 		{ | ||||
| 			m_menu->displayMessageMenu(wgettext("Cannot create world: Name contains invalid characters")); | ||||
| 			return; | ||||
| 		} | ||||
| 		std::vector<WorldSpec> worlds = getAvailableWorlds(); | ||||
| 		for(std::vector<WorldSpec>::iterator i = worlds.begin(); | ||||
| 		    i != worlds.end(); i++) | ||||
| 		{ | ||||
| 			if((*i).name == name_narrow) | ||||
| 			{ | ||||
| 				m_menu->displayMessageMenu(wgettext("Cannot create world: A world by this name already exists")); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 		m_menu->createNewWorld(name, gameid); | ||||
| 	} | ||||
| 	GUIMainMenu *m_menu; | ||||
| @@ -929,3 +946,7 @@ int GUIMainMenu::getTab() | ||||
| 	return TAB_SINGLEPLAYER; // Default
 | ||||
| } | ||||
| 
 | ||||
| void GUIMainMenu::displayMessageMenu(std::wstring msg) | ||||
| { | ||||
| 	(new GUIMessageMenu(env, parent, -1, menumgr, msg))->drop(); | ||||
| } | ||||
|   | ||||
| @@ -92,6 +92,7 @@ public: | ||||
| 	void createNewWorld(std::wstring name, std::string gameid); | ||||
| 	void deleteWorld(const std::vector<std::string> &paths); | ||||
| 	int getTab(); | ||||
| 	void displayMessageMenu(std::wstring msg); | ||||
| 	 | ||||
| private: | ||||
| 	MainMenuData *m_data; | ||||
|   | ||||
| @@ -542,10 +542,6 @@ void InventoryList::deSerialize(std::istream &is) | ||||
| 				throw SerializationError("too many items"); | ||||
| 			m_items[item_i++].clear(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			throw SerializationError("Unknown inventory identifier"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @@ -907,10 +903,6 @@ void Inventory::deSerialize(std::istream &is) | ||||
| 
 | ||||
| 			m_lists.push_back(list); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			throw SerializationError("Unknown inventory identifier"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|   | ||||
| @@ -332,6 +332,18 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame | ||||
| 
 | ||||
| 	// If source is infinite, reset it's stack
 | ||||
| 	if(src_can_take_count == -1){ | ||||
| 		// If destination stack is of different type and there are leftover
 | ||||
| 		// items, attempt to put the leftover items to a different place in the
 | ||||
| 		// destination inventory.
 | ||||
| 		// The client-side GUI will try to guess if this happens.
 | ||||
| 		if(from_stack_was.name != to_stack_was.name){ | ||||
| 			for(u32 i=0; i<list_to->getSize(); i++){ | ||||
| 				if(list_to->getItem(i).empty()){ | ||||
| 					list_to->changeItem(i, to_stack_was); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		list_from->deleteItem(from_i); | ||||
| 		list_from->addItem(from_i, from_stack_was); | ||||
| 	} | ||||
| @@ -339,6 +351,8 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame | ||||
| 	if(dst_can_put_count == -1){ | ||||
| 		list_to->deleteItem(to_i); | ||||
| 		list_to->addItem(to_i, to_stack_was); | ||||
| 		list_from->deleteItem(from_i); | ||||
| 		list_from->addItem(from_i, from_stack_was); | ||||
| 		list_from->takeItem(from_i, count); | ||||
| 	} | ||||
| 
 | ||||
|   | ||||
| @@ -66,6 +66,29 @@ struct InventoryLocation | ||||
| 		name = name_; | ||||
| 	} | ||||
| 
 | ||||
| 	bool operator==(const InventoryLocation &other) const | ||||
| 	{ | ||||
| 		if(type != other.type) | ||||
| 			return false; | ||||
| 		switch(type){ | ||||
| 		case UNDEFINED: | ||||
| 			return false; | ||||
| 		case CURRENT_PLAYER: | ||||
| 			return true; | ||||
| 		case PLAYER: | ||||
| 			return (name == other.name); | ||||
| 		case NODEMETA: | ||||
| 			return (p == other.p); | ||||
| 		case DETACHED: | ||||
| 			return (name == other.name); | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	bool operator!=(const InventoryLocation &other) const | ||||
| 	{ | ||||
| 		return !(*this == other); | ||||
| 	} | ||||
| 
 | ||||
| 	void applyCurrentPlayer(const std::string &name_) | ||||
| 	{ | ||||
| 		if(type == CURRENT_PLAYER) | ||||
|   | ||||
| @@ -150,13 +150,16 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, | ||||
| 		position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd); | ||||
| 		position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd); | ||||
| 		 | ||||
| 		f32 min_y = lwn_f.Y + 0.5*BS; | ||||
| 		if(position.Y < min_y) | ||||
| 		if(!is_climbing) | ||||
| 		{ | ||||
| 			position.Y = min_y; | ||||
| 			f32 min_y = lwn_f.Y + 0.5*BS; | ||||
| 			if(position.Y < min_y) | ||||
| 			{ | ||||
| 				position.Y = min_y; | ||||
| 
 | ||||
| 			if(m_speed.Y < 0) | ||||
| 				m_speed.Y = 0; | ||||
| 				if(m_speed.Y < 0) | ||||
| 					m_speed.Y = 0; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   | ||||
| @@ -1468,8 +1468,8 @@ void Map::timerUpdate(float dtime, float unload_timeout, | ||||
| 			MapBlock *block = (*i); | ||||
| 			 | ||||
| 			block->incrementUsageTimer(dtime); | ||||
| 			 | ||||
| 			if(block->getUsageTimer() > unload_timeout) | ||||
| 
 | ||||
| 			if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout) | ||||
| 			{ | ||||
| 				v3s16 p = block->getPos(); | ||||
| 
 | ||||
|   | ||||
| @@ -56,7 +56,8 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy): | ||||
| 		m_generated(false), | ||||
| 		m_timestamp(BLOCK_TIMESTAMP_UNDEFINED), | ||||
| 		m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED), | ||||
| 		m_usage_timer(0) | ||||
| 		m_usage_timer(0), | ||||
| 		m_refcount(0) | ||||
| { | ||||
| 	data = NULL; | ||||
| 	if(dummy == false) | ||||
|   | ||||
| @@ -430,6 +430,22 @@ public: | ||||
| 	{ | ||||
| 		return m_usage_timer; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 		See m_refcount | ||||
| 	*/ | ||||
| 	void refGrab() | ||||
| 	{ | ||||
| 		m_refcount++; | ||||
| 	} | ||||
| 	void refDrop() | ||||
| 	{ | ||||
| 		m_refcount--; | ||||
| 	} | ||||
| 	int refGet() | ||||
| 	{ | ||||
| 		return m_refcount; | ||||
| 	} | ||||
| 	 | ||||
| 	/*
 | ||||
| 		Node Timers | ||||
| @@ -566,6 +582,12 @@ private: | ||||
| 		Map will unload the block when this reaches a timeout. | ||||
| 	*/ | ||||
| 	float m_usage_timer; | ||||
| 
 | ||||
| 	/*
 | ||||
| 		Reference count; currently used for determining if this block is in | ||||
| 		the list of blocks to be drawn. | ||||
| 	*/ | ||||
| 	int m_refcount; | ||||
| }; | ||||
| 
 | ||||
| inline bool blockpos_over_limit(v3s16 p) | ||||
|   | ||||
| @@ -24,6 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., | ||||
| #include <set> | ||||
| #include <vector> | ||||
| 
 | ||||
| #define WORLDNAME_BLACKLISTED_CHARS "/\\" | ||||
| 
 | ||||
| struct SubgameSpec | ||||
| { | ||||
| 	std::string id; // "" = game does not exist
 | ||||
|   | ||||
| @@ -242,6 +242,29 @@ inline bool string_allowed(const std::string &s, const std::string &allowed_char | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
| 	Checks if a string contains no blacklisted characters (opposite | ||||
| 	function of string_allowed()) | ||||
| */ | ||||
| inline bool string_allowed_blacklist(const std::string & s, const std::string & blacklisted_chars) | ||||
| { | ||||
| 	for(unsigned int i = 0; i < s.length(); i++) | ||||
| 	{ | ||||
| 		bool invalid = false; | ||||
| 		for(unsigned int j = 0; j < blacklisted_chars.length(); j++) | ||||
| 		{ | ||||
| 			if(s[i] == blacklisted_chars[j]) | ||||
| 			{ | ||||
| 				invalid = true; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if(invalid) | ||||
| 			return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
| 	Forcefully wraps string into rows using \n | ||||
| 	(no word wrap, used for showing paths in gui) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user