Ejemplo n.º 1
0
void NodeMetaRef::reportMetadataChange()
{
	// NOTE: This same code is in rollback_interface.cpp
	// Inform other things that the metadata has changed
	v3s16 blockpos = getNodeBlockPos(m_p);
	MapEditEvent event;
	event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
	event.p = blockpos;
	m_env->getMap().dispatchEvent(&event);
	// Set the block to be saved
	MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos);
	if (block) {
		block->raiseModified(MOD_STATE_WRITE_NEEDED,
			MOD_REASON_REPORT_META_CHANGE);
	}
}
Ejemplo n.º 2
0
void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool urgent)
{
/*
	{
		v3s16 p = nodepos;
		infostream<<"Client::addUpdateMeshTaskForNode(): "
				<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
				<<std::endl;
	}
*/

	v3s16 blockpos = getNodeBlockPos(nodepos);
	v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;

	try{
		addUpdateMeshTask(blockpos, urgent);
	}
	catch(InvalidPositionException &e){}

	// Leading edge
	if(nodepos.X == blockpos_relative.X){
		try{
			v3s16 p = blockpos + v3s16(-1,0,0);
			addUpdateMeshTask(p, urgent);
		}
		catch(InvalidPositionException &e){}
	}

	if(nodepos.Y == blockpos_relative.Y){
		try{
			v3s16 p = blockpos + v3s16(0,-1,0);
			addUpdateMeshTask(p, urgent);
		}
		catch(InvalidPositionException &e){}
	}

	if(nodepos.Z == blockpos_relative.Z){
		try{
			v3s16 p = blockpos + v3s16(0,0,-1);
			addUpdateMeshTask(p, urgent);
		}
		catch(InvalidPositionException &e){}
	}
}
Ejemplo n.º 3
0
	virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n,
			u32 active_object_count, u32 active_object_count_wider)
	{
		INodeDefManager *ndef = env->getGameDef()->ndef();
		ServerMap *map = &env->getServerMap();
		
		actionstream<<"A sapling grows into a tree at "
				<<PP(p)<<std::endl;

		core::map<v3s16, MapBlock*> modified_blocks;
		v3s16 tree_p = p;
		ManualMapVoxelManipulator vmanip(map);
		v3s16 tree_blockp = getNodeBlockPos(tree_p);
		vmanip.initialEmerge(tree_blockp - v3s16(1,1,1), tree_blockp + v3s16(1,1,1));
		bool is_apple_tree = myrand()%4 == 0;
		mapgen::make_tree(vmanip, tree_p, is_apple_tree, ndef);
		vmanip.blitBackAll(&modified_blocks);

		// update lighting
		core::map<v3s16, MapBlock*> lighting_modified_blocks;
		for(core::map<v3s16, MapBlock*>::Iterator
			i = modified_blocks.getIterator();
			i.atEnd() == false; i++)
		{
			lighting_modified_blocks.insert(i.getNode()->getKey(), i.getNode()->getValue());
		}
		map->updateLighting(lighting_modified_blocks, modified_blocks);

		// Send a MEET_OTHER event
		MapEditEvent event;
		event.type = MEET_OTHER;
		for(core::map<v3s16, MapBlock*>::Iterator
			i = modified_blocks.getIterator();
			i.atEnd() == false; i++)
		{
			v3s16 p = i.getNode()->getKey();
			event.modified_blocks.insert(p, true);
		}
		map->dispatchEvent(&event);
	}
Ejemplo n.º 4
0
s16 ServerMap::updateBlockHumidity(ServerEnvironment *env, v3POS p, MapBlock *block, std::map<v3POS, s16> * cache) {
	auto bp = getNodeBlockPos(p);
	auto gametime = env->getGameTime();
	if (block) {
		if (gametime < block->humidity_last_update)
			return block->humidity + myrand_range(0, 1);
	} else if (!cache) {
		block = getBlockNoCreateNoEx(bp, true);
	}
	if (cache && cache->count(bp))
		return cache->at(bp) + myrand_range(0, 1);

	auto value = m_emerge->biomemgr->calcBlockHumidity(p, getSeed(),
	             env->getTimeOfDayF(), gametime * env->getTimeOfDaySpeed(), env->m_use_weather);

	if(block) {
		block->humidity = value;
		block->humidity_last_update = env->m_use_weather ? gametime + 30 : -1;
	}
	if (cache)
		(*cache)[bp] = value;
	return value + myrand_range(0, 1);
}
Ejemplo n.º 5
0
void RemoteClient::GetNextBlocks (
		ServerEnvironment *env,
		EmergeManager * emerge,
		float dtime,
		std::vector<PrioritySortedBlockTransfer> &dest)
{
	DSTACK(FUNCTION_NAME);


	// Increment timers
	m_nothing_to_send_pause_timer -= dtime;
	m_nearest_unsent_reset_timer += dtime;

	if(m_nothing_to_send_pause_timer >= 0)
		return;

	RemotePlayer *player = env->getPlayer(peer_id);
	// This can happen sometimes; clients and players are not in perfect sync.
	if (!player)
		return;

	PlayerSAO *sao = player->getPlayerSAO();
	if (!sao)
		return;

	// Won't send anything if already sending
	if(m_blocks_sending.size() >= g_settings->getU16
			("max_simultaneous_block_sends_per_client"))
	{
		//infostream<<"Not sending any blocks, Queue full."<<std::endl;
		return;
	}

	v3f playerpos = sao->getBasePosition();
	const v3f &playerspeed = player->getSpeed();
	v3f playerspeeddir(0,0,0);
	if(playerspeed.getLength() > 1.0*BS)
		playerspeeddir = playerspeed / playerspeed.getLength();
	// Predict to next block
	v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;

	v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);

	v3s16 center = getNodeBlockPos(center_nodepos);

	// Camera position and direction
	v3f camera_pos = sao->getEyePosition();
	v3f camera_dir = v3f(0,0,1);
	camera_dir.rotateYZBy(sao->getPitch());
	camera_dir.rotateXZBy(sao->getYaw());

	/*infostream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
			<<camera_dir.Z<<")"<<std::endl;*/

	/*
		Get the starting value of the block finder radius.
	*/

	if(m_last_center != center)
	{
		m_nearest_unsent_d = 0;
		m_last_center = center;
	}

	/*infostream<<"m_nearest_unsent_reset_timer="
			<<m_nearest_unsent_reset_timer<<std::endl;*/

	// Reset periodically to workaround for some bugs or stuff
	if(m_nearest_unsent_reset_timer > 20.0)
	{
		m_nearest_unsent_reset_timer = 0;
		m_nearest_unsent_d = 0;
		//infostream<<"Resetting m_nearest_unsent_d for "
		//		<<server->getPlayerName(peer_id)<<std::endl;
	}

	//s16 last_nearest_unsent_d = m_nearest_unsent_d;
	s16 d_start = m_nearest_unsent_d;

	//infostream<<"d_start="<<d_start<<std::endl;

	u16 max_simul_sends_setting = g_settings->getU16
			("max_simultaneous_block_sends_per_client");
	u16 max_simul_sends_usually = max_simul_sends_setting;

	/*
		Check the time from last addNode/removeNode.

		Decrease send rate if player is building stuff.
	*/
	m_time_from_building += dtime;
	if(m_time_from_building < g_settings->getFloat(
				"full_block_send_enable_min_time_from_building"))
	{
		max_simul_sends_usually
			= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
	}

	/*
		Number of blocks sending + number of blocks selected for sending
	*/
	u32 num_blocks_selected = m_blocks_sending.size();

	/*
		next time d will be continued from the d from which the nearest
		unsent block was found this time.

		This is because not necessarily any of the blocks found this
		time are actually sent.
	*/
	s32 new_nearest_unsent_d = -1;

	// get view range and camera fov from the client
	s16 wanted_range = sao->getWantedRange();
	float camera_fov = sao->getFov();
	// if FOV, wanted_range are not available (old client), fall back to old default
	if (wanted_range <= 0) wanted_range = 1000;
	if (camera_fov <= 0) camera_fov = (72.0*M_PI/180) * 4./3.;

	const s16 full_d_max = MYMIN(g_settings->getS16("max_block_send_distance"), wanted_range);
	const s16 d_opt = MYMIN(g_settings->getS16("block_send_optimize_distance"), wanted_range);
	const s16 d_blocks_in_sight = full_d_max * BS * MAP_BLOCKSIZE;
	//infostream << "Fov from client " << camera_fov << " full_d_max " << full_d_max << std::endl;

	s16 d_max = full_d_max;
	s16 d_max_gen = MYMIN(g_settings->getS16("max_block_generate_distance"), wanted_range);

	// Don't loop very much at a time
	s16 max_d_increment_at_time = 2;
	if(d_max > d_start + max_d_increment_at_time)
		d_max = d_start + max_d_increment_at_time;

	s32 nearest_emerged_d = -1;
	s32 nearest_emergefull_d = -1;
	s32 nearest_sent_d = -1;
	//bool queue_is_full = false;

	const v3s16 cam_pos_nodes = floatToInt(camera_pos, BS);
	const bool occ_cull = g_settings->getBool("server_side_occlusion_culling");

	s16 d;
	for(d = d_start; d <= d_max; d++) {
		/*
			Get the border/face dot coordinates of a "d-radiused"
			box
		*/
		std::vector<v3s16> list = FacePositionCache::getFacePositions(d);

		std::vector<v3s16>::iterator li;
		for(li = list.begin(); li != list.end(); ++li) {
			v3s16 p = *li + center;

			/*
				Send throttling
				- Don't allow too many simultaneous transfers
				- EXCEPT when the blocks are very close

				Also, don't send blocks that are already flying.
			*/

			// Start with the usual maximum
			u16 max_simul_dynamic = max_simul_sends_usually;

			// If block is very close, allow full maximum
			if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
				max_simul_dynamic = max_simul_sends_setting;

			// Don't select too many blocks for sending
			if (num_blocks_selected >= max_simul_dynamic) {
				//queue_is_full = true;
				goto queue_full_break;
			}

			// Don't send blocks that are currently being transferred
			if (m_blocks_sending.find(p) != m_blocks_sending.end())
				continue;

			/*
				Do not go over max mapgen limit
			*/
			if (blockpos_over_max_limit(p))
				continue;

			// If this is true, inexistent block will be made from scratch
			bool generate = d <= d_max_gen;

			/*
				Don't generate or send if not in sight
				FIXME This only works if the client uses a small enough
				FOV setting. The default of 72 degrees is fine.
			*/

			f32 dist;
			if (!isBlockInSight(p, camera_pos, camera_dir, camera_fov, d_blocks_in_sight, &dist)) {
				continue;
			}

			/*
				Don't send already sent blocks
			*/
			{
				if(m_blocks_sent.find(p) != m_blocks_sent.end())
				{
					continue;
				}
			}

			/*
				Check if map has this block
			*/
			MapBlock *block = env->getMap().getBlockNoCreateNoEx(p);

			bool surely_not_found_on_disk = false;
			bool block_is_invalid = false;
			if (block) {
				// Reset usage timer, this block will be of use in the future.
				block->resetUsageTimer();

				// Block is dummy if data doesn't exist.
				// It means it has been not found from disk and not generated
				if (block->isDummy()) {
					surely_not_found_on_disk = true;
				}

				if (!block->isGenerated())
					block_is_invalid = true;

				/*
					If block is not close, don't send it unless it is near
					ground level.

					Block is near ground level if night-time mesh
					differs from day-time mesh.
				*/
				if (d >= d_opt) {
					if (!block->getDayNightDiff())
						continue;
				}

				if (occ_cull && !block_is_invalid &&
						env->getMap().isBlockOccluded(block, cam_pos_nodes)) {
					continue;
				}
			}

			/*
				If block has been marked to not exist on disk (dummy)
				and generating new ones is not wanted, skip block.
			*/
			if (!generate && surely_not_found_on_disk) {
				// get next one.
				continue;
			}

			/*
				Add inexistent block to emerge queue.
			*/
			if(block == NULL || surely_not_found_on_disk || block_is_invalid)
			{
				if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
					if (nearest_emerged_d == -1)
						nearest_emerged_d = d;
				} else {
					if (nearest_emergefull_d == -1)
						nearest_emergefull_d = d;
					goto queue_full_break;
				}

				// get next one.
				continue;
			}

			if(nearest_sent_d == -1)
				nearest_sent_d = d;

			/*
				Add block to send queue
			*/
			PrioritySortedBlockTransfer q((float)dist, p, peer_id);

			dest.push_back(q);

			num_blocks_selected += 1;
		}
	}
queue_full_break:

	// If nothing was found for sending and nothing was queued for
	// emerging, continue next time browsing from here
	if(nearest_emerged_d != -1){
		new_nearest_unsent_d = nearest_emerged_d;
	} else if(nearest_emergefull_d != -1){
		new_nearest_unsent_d = nearest_emergefull_d;
	} else {
		if(d > full_d_max){
			new_nearest_unsent_d = 0;
			m_nothing_to_send_pause_timer = 2.0;
		} else {
			if(nearest_sent_d != -1)
				new_nearest_unsent_d = nearest_sent_d;
			else
				new_nearest_unsent_d = d;
		}
	}

	if(new_nearest_unsent_d != -1)
		m_nearest_unsent_d = new_nearest_unsent_d;
}
Ejemplo n.º 6
0
bool MapBlockObjectList::wrapObject(MapBlockObject *object)
{
	DSTACK(__FUNCTION_NAME);
	
	// No lock here; this is called so that the lock is already locked.
	//JMutexAutoLock lock(m_mutex);

	assert(object->m_block == m_block);
	assert(m_objects.find(object->m_id) != NULL);
	assert(m_objects[object->m_id] == object);

	Map *map = m_block->getParent();
	
	// Calculate blockpos on map
	v3s16 oldblock_pos_i_on_map = m_block->getPosRelative();
	v3f pos_f_on_oldblock = object->m_pos;
	v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock, BS);
	v3s16 pos_i_on_map = pos_i_on_oldblock + oldblock_pos_i_on_map;
	v3s16 pos_blocks_on_map = getNodeBlockPos(pos_i_on_map);

	// Get new block
	MapBlock *newblock;
	try{
		newblock = map->getBlockNoCreate(pos_blocks_on_map);
	}
	catch(InvalidPositionException &e)
	{
		// Couldn't find block -> not wrapping
		/*dstream<<"WARNING: Wrapping object not possible: "
				<<"could not find new block"
				<<"("<<pos_blocks_on_map.X
				<<","<<pos_blocks_on_map.Y
				<<","<<pos_blocks_on_map.Z
				<<")"<<std::endl;*/
		/*dstream<<"pos_f_on_oldblock=("
				<<pos_f_on_oldblock.X<<","
				<<pos_f_on_oldblock.Y<<","
				<<pos_f_on_oldblock.Z<<")"
				<<std::endl;*/
		return true;
	}

	if(newblock == m_block)
	{
		dstream<<"WARNING: Wrapping object not possible: "
				"newblock == oldblock"<<std::endl;
		return true;
	}
	
	// Calculate position on new block
	v3f oldblock_pos_f_on_map = intToFloat(oldblock_pos_i_on_map, BS);
	v3s16 newblock_pos_i_on_map = newblock->getPosRelative();
	v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map, BS);
	v3f pos_f_on_newblock = pos_f_on_oldblock
			- newblock_pos_f_on_map + oldblock_pos_f_on_map;

	// Remove object from this block
	m_objects.remove(object->m_id);
	
	// Add object to new block
	object->m_pos = pos_f_on_newblock;
	object->m_id = -1;
	object->m_block = NULL;
	newblock->addObject(object);

	//dstream<<"NOTE: Wrapped object"<<std::endl;

	return false;
}
Ejemplo n.º 7
0
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: ";

    //ScopeProfiler sp(g_profiler, "CM::renderMap() " + prefix, SPT_AVG);

    /*
    	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;
    f32 camera_fov = m_camera_fov * 1.1;
    m_camera_mutex.Unlock();

    /*
    	Get all blocks and draw all visible ones
    */

    v3s16 cam_pos_nodes = floatToInt(camera_position, BS);

    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 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
    */

    {
        //ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);

        MeshBufListList drawbufs;
        std::vector<MapBlock::mesh_type> used_meshes; //keep shared_ptr
        auto drawlist = m_drawlist.load();
        auto lock = drawlist->lock_shared_rec();
        used_meshes.reserve(drawlist->size());
        //g_profiler->add("CM::renderMap()cnt"+ prefix, drawlist->size());
        for(auto & ir : *drawlist) {
            auto block = ir.second;

            int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes), block->getPos());
            // If the mesh of the block happened to get deleted, ignore it
            auto mapBlockMesh = block->getMesh(mesh_step);
            if (!mapBlockMesh)
                continue;

            float d = 0.0;
            if(isBlockInSight(block->getPos(), camera_position,
                              m_camera_direction, camera_fov,
                              100000*BS, &d) == false)
            {
                continue;
            }

            used_meshes.emplace_back(mapBlockMesh);

            // Mesh animation
            {
                //JMutexAutoLock lock(block->mesh_mutex);

                mapBlockMesh->updateCameraOffset(m_camera_offset);

                // 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);

                auto *mesh = mapBlockMesh->getMesh();
                if (!mesh)
                    continue;

                u32 c = mesh->getMeshBufferCount();
                for(u32 i=0; i<c; i++)
                {
                    scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);

                    buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, m_cache_trilinear_filter);
                    buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, m_cache_bilinear_filter);
                    buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, m_cache_anistropic_filter);

                    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);
                    }
                }
            }
        }

        std::vector<MeshBufList> &lists = drawbufs.lists;

        //int timecheck_counter = 0;
        for(std::vector<MeshBufList>::iterator i = lists.begin();
                i != lists.end(); ++i) {
#if 0
            timecheck_counter++;
            if(timecheck_counter > 50) {
                timecheck_counter = 0;
                int time2 = time(0);
                if(time2 > time1 + 4) {
                    infostream << "ClientMap::renderMap(): "
                               "Rendering takes ages, returning."
                               << std::endl;
                    return;
                }
            }
#endif

            MeshBufList &list = *i;

            driver->setMaterial(list.m);

            for(std::vector<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++;
            }

        }
    } // ScopeProfiler

    // Log only on solid pass because values are the same
    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("CM: PrimitiveDrawn", driver->getPrimitiveCountDrawn());

    /*infostream<<"renderMap(): is_transparent_pass="******", rendered "<<vertex_count<<" vertices."<<std::endl;*/
}
Ejemplo n.º 8
0
void ClientMap::updateDrawList(video::IVideoDriver* driver, float dtime)
{
	ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
	//g_profiler->add("CM::updateDrawList() count", 1);

	INodeDefManager *nodemgr = m_gamedef->ndef();

	if (!m_drawlist_last)
		m_drawlist_current = !m_drawlist_current;
	auto & drawlist = m_drawlist_current ? m_drawlist_1 : m_drawlist_0;

	float max_cycle_ms = 0.1/getControl().fps_wanted;
	u32 n = 0, calls = 0, end_ms = porting::getTimeMs() + max_cycle_ms;

	m_camera_mutex.Lock();
	v3f camera_position = m_camera_position;
	v3f camera_direction = m_camera_direction;
	f32 camera_fov = m_camera_fov;
	v3s16 camera_offset = m_camera_offset;
	m_camera_mutex.Unlock();

	// 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(
			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);
	
	// Number of blocks in rendering range
	u32 blocks_in_range = 0;
	// Number of blocks occlusion culled
	u32 blocks_occlusion_culled = 0;
	// Number of blocks in rendering range but don't have a mesh
	u32 blocks_in_range_without_mesh = 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;
	// Distance to farthest drawn block
	float farthest_drawn = 0;

	{
	auto lock = m_blocks.lock_shared_rec();
	for(auto & ir : m_blocks) {

		if (n++ < m_drawlist_last)
			continue;
		else
			m_drawlist_last = 0;
		++calls;

		MapBlock *block = ir.second;
		auto bp = block->getPos();

		if(m_control.range_all == false)
		{
			if(bp.X < p_blocks_min.X
			|| bp.X > p_blocks_max.X
			|| bp.Z > p_blocks_max.Z
			|| bp.Z < p_blocks_min.Z
			|| bp.Y < p_blocks_min.Y
			|| bp.Y > p_blocks_max.Y)
				continue;
		}

			int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes).getDistanceFrom(block->getPos()));
			/*
				Compare block position to camera position, skip
				if not seen on display
			*/
			
			if (block->getMesh(mesh_step) != NULL)
				block->getMesh(mesh_step)->updateCameraOffset(m_camera_offset);
			
			float range = 100000 * BS;
			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)
			{
				continue;
			}

			// This is ugly (spherical distance limit?)
			/*if(m_control.range_all == false &&
					d - 0.5*BS*MAP_BLOCKSIZE > range)
				continue;*/

			blocks_in_range++;
			
			/*
				Ignore if mesh doesn't exist
			*/
			{
				//JMutexAutoLock lock(block->mesh_mutex);

				if(block->getMesh(mesh_step) == NULL){
					blocks_in_range_without_mesh++;
					continue;
				}
			}

			/*
				Occlusion culling
			*/

			// No occlusion culling when free_move is on and camera is
			// inside ground
			bool occlusion_culling_enabled = true;
			if(g_settings->getBool("free_move")){
				MapNode n = getNodeNoEx(cam_pos_nodes);
				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;
			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;
			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)
			)
			{
				blocks_occlusion_culled++;
				continue;
			}
			
			// This block is in range. Reset usage timer.
			block->resetUsageTimer();

			// 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)
				continue;

			if (m_control.farmesh && mesh_step != block->getMesh(mesh_step)->step) { //&& !block->mesh->transparent
				m_client->addUpdateMeshTask(block->getPos(), false, mesh_step == 1);
			}

			block->getMesh(mesh_step)->incrementUsageTimer(dtime);

			// Add to set
			block->refGrab();
			drawlist[block->getPos()] = block;

			blocks_drawn++;
			if(d/BS > farthest_drawn)
				farthest_drawn = d/BS;

		if (porting::getTimeMs() > end_ms) {
			m_drawlist_last = n;
			break;
		}
	}
	}
	if (!calls)
		m_drawlist_last = 0;

	if (m_drawlist_last)
		return;

	for (auto & ir : *m_drawlist)
		ir.second->refDrop();

	m_drawlist->clear();
	m_drawlist = m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0;

	m_control.blocks_would_have_drawn = blocks_would_have_drawn;
	m_control.blocks_drawn = blocks_drawn;
	m_control.farthest_drawn = farthest_drawn;

	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: farthest drawn", farthest_drawn);
	g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
}
Ejemplo n.º 9
0
void ClientEnvironment::step(float dtime)
{
	DSTACK(__FUNCTION_NAME);

	// Get some settings
	bool free_move = g_settings.getBool("free_move");
	bool footprints = g_settings.getBool("footprints");

	// Get local player
	LocalPlayer *lplayer = getLocalPlayer();
	assert(lplayer);
	// collision info queue
	core::list<CollisionInfo> player_collisions;
	
	/*
		Get the speed the player is going
	*/
	bool is_climbing = lplayer->is_climbing;

	f32 player_speed = 0.001; // just some small value
	player_speed = lplayer->getSpeed().getLength();
	
	/*
		Maximum position increment
	*/
	//f32 position_max_increment = 0.05*BS;
	f32 position_max_increment = 0.1*BS;

	// Maximum time increment (for collision detection etc)
	// time = distance / speed
	f32 dtime_max_increment = position_max_increment / player_speed;
	
	// Maximum time increment is 10ms or lower
	if(dtime_max_increment > 0.01)
		dtime_max_increment = 0.01;
	
	// Don't allow overly huge dtime
	if(dtime > 0.5)
		dtime = 0.5;
	
	f32 dtime_downcount = dtime;

	/*
		Stuff that has a maximum time increment
	*/

	u32 loopcount = 0;
	do
	{
		loopcount++;

		f32 dtime_part;
		if(dtime_downcount > dtime_max_increment)
		{
			dtime_part = dtime_max_increment;
			dtime_downcount -= dtime_part;
		}
		else
		{
			dtime_part = dtime_downcount;
			/*
				Setting this to 0 (no -=dtime_part) disables an infinite loop
				when dtime_part is so small that dtime_downcount -= dtime_part
				does nothing
			*/
			dtime_downcount = 0;
		}
		
		/*
			Handle local player
		*/
		
		{
			v3f lplayerpos = lplayer->getPosition();
			
			// Apply physics
			if(free_move == false && is_climbing == false)
			{
				// Gravity
				v3f speed = lplayer->getSpeed();
				if(lplayer->swimming_up == false)
					speed.Y -= 9.81 * BS * dtime_part * 2;

				// Water resistance
				if(lplayer->in_water_stable || lplayer->in_water)
				{
					f32 max_down = 2.0*BS;
					if(speed.Y < -max_down) speed.Y = -max_down;

					f32 max = 2.5*BS;
					if(speed.getLength() > max)
					{
						speed = speed / speed.getLength() * max;
					}
				}

				lplayer->setSpeed(speed);
			}

			/*
				Move the lplayer.
				This also does collision detection.
			*/
			lplayer->move(dtime_part, *m_map, position_max_increment,
					&player_collisions);
		}
	}
	while(dtime_downcount > 0.001);
		
	//std::cout<<"Looped "<<loopcount<<" times."<<std::endl;

	for(core::list<CollisionInfo>::Iterator
			i = player_collisions.begin();
			i != player_collisions.end(); i++)
	{
		CollisionInfo &info = *i;
		if(info.t == COLLISION_FALL)
		{
			//f32 tolerance = BS*10; // 2 without damage
			f32 tolerance = BS*12; // 3 without damage
			f32 factor = 1;
			if(info.speed > tolerance)
			{
				f32 damage_f = (info.speed - tolerance)/BS*factor;
				u16 damage = (u16)(damage_f+0.5);
				if(lplayer->hp > damage)
					lplayer->hp -= damage;
				else
					lplayer->hp = 0;

				ClientEnvEvent event;
				event.type = CEE_PLAYER_DAMAGE;
				event.player_damage.amount = damage;
				m_client_event_queue.push_back(event);
			}
		}
	}
	
	/*
		Stuff that can be done in an arbitarily large dtime
	*/
	for(core::list<Player*>::Iterator i = m_players.begin();
			i != m_players.end(); i++)
	{
		Player *player = *i;
		v3f playerpos = player->getPosition();
		
		/*
			Handle non-local players
		*/
		if(player->isLocal() == false)
		{
			// Move
			player->move(dtime, *m_map, 100*BS);

			// Update lighting on remote players on client
			u8 light = LIGHT_MAX;
			try{
				// Get node at head
				v3s16 p = floatToInt(playerpos + v3f(0,BS+BS/2,0), BS);
				MapNode n = m_map->getNode(p);
				light = n.getLightBlend(getDayNightRatio());
			}
			catch(InvalidPositionException &e) {}
			player->updateLight(light);
		}
		
		/*
			Add footsteps to grass
		*/
		if(footprints)
		{
			// Get node that is at BS/4 under player
			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
			try{
				MapNode n = m_map->getNode(bottompos);
				if(n.getContent() == CONTENT_GRASS)
				{
					n.setContent(CONTENT_GRASS_FOOTSTEPS);
					m_map->setNode(bottompos, n);
					// Update mesh on client
					if(m_map->mapType() == MAPTYPE_CLIENT)
					{
						v3s16 p_blocks = getNodeBlockPos(bottompos);
						MapBlock *b = m_map->getBlockNoCreate(p_blocks);
						//b->updateMesh(getDayNightRatio());
						b->setMeshExpired(true);
					}
				}
			}
			catch(InvalidPositionException &e)
			{
			}
		}
	}
	
	/*
		Step active objects and update lighting of them
	*/
	
	for(core::map<u16, ClientActiveObject*>::Iterator
			i = m_active_objects.getIterator();
			i.atEnd()==false; i++)
	{
		ClientActiveObject* obj = i.getNode()->getValue();
		// Step object
		obj->step(dtime, this);

		if(m_active_object_light_update_interval.step(dtime, 0.21))
		{
			// Update lighting
			//u8 light = LIGHT_MAX;
			u8 light = 0;
			try{
				// Get node at head
				v3s16 p = obj->getLightPosition();
				MapNode n = m_map->getNode(p);
				light = n.getLightBlend(getDayNightRatio());
			}
			catch(InvalidPositionException &e) {}
			obj->updateLight(light);
		}
	}
}
Ejemplo n.º 10
0
void Schematic::placeOnMap(Map *map, v3s16 p, u32 flags,
                           Rotation rot, bool force_place)
{
    concurrent_map<v3POS, MapBlock *> lighting_modified_blocks;
    /*
    	std::map<v3s16, MapBlock *> lighting_modified_blocks;
    */
    std::map<v3s16, MapBlock *> modified_blocks;
    std::map<v3s16, MapBlock *>::iterator it;

    if(!map || !schemdata || !m_ndef)
        return;

    /*
    	assert(map != NULL);
    	assert(schemdata != NULL);
    	sanity_check(m_ndef != NULL);
    */

    //// Determine effective rotation and effective schematic dimensions
    if (rot == ROTATE_RAND)
        rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);

    v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
              v3s16(size.Z, size.Y, size.X) : size;

    //// Adjust placement position if necessary
    if (flags & DECO_PLACE_CENTER_X)
        p.X -= (s.X + 1) / 2;
    if (flags & DECO_PLACE_CENTER_Y)
        p.Y -= (s.Y + 1) / 2;
    if (flags & DECO_PLACE_CENTER_Z)
        p.Z -= (s.Z + 1) / 2;

    //// Create VManip for effected area, emerge our area, modify area
    //// inside VManip, then blit back.
    v3s16 bp1 = getNodeBlockPos(p);
    v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1));

    MMVManip vm(map);
    vm.initialEmerge(bp1, bp2);

    blitToVManip(&vm, p, rot, force_place);

    vm.blitBackAll(&modified_blocks);

    //// Carry out post-map-modification actions

    //// Update lighting
    // TODO: Optimize this by using Mapgen::calcLighting() instead
    lighting_modified_blocks.insert(modified_blocks.begin(), modified_blocks.end());
    map->updateLighting(lighting_modified_blocks, modified_blocks);

    //// Create & dispatch map modification events to observers
    MapEditEvent event;
    event.type = MEET_OTHER;
    /*
    	for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it)
    		event.modified_blocks.insert(it->first);
    */

    map->dispatchEvent(&event);
}
/*
	sender_peer_id given to this shall be quaranteed to be a valid peer
*/
void Client::ProcessData(NetworkPacket *pkt) {
	DSTACK(__FUNCTION_NAME);

	ScopeProfiler sp(g_profiler, "Client::ProcessData");

	auto datasize = pkt->getSize();
	auto sender_peer_id = pkt->getPeerId();

	int command;
	MsgpackPacket packet;
	msgpack::unpacked msg;

	if (!con::parse_msgpack_packet(pkt->getString(0), datasize, &packet, &command, &msg)) {
		// invalid packet
		return;
	}

	//infostream<<"Client: received command="<<command<<std::endl;
	m_packetcounter.add((u16)command);

	/*
		If this check is removed, be sure to change the queue
		system to know the ids
	*/
	if(sender_peer_id != PEER_ID_SERVER)
	{
		infostream<<"Client::ProcessData(): Discarding data not "
				"coming from server: peer_id="<<sender_peer_id
				<<std::endl;
		return;
	}

	u8 ser_version = m_server_ser_ver;

	//infostream<<"Client received command="<<(int)command<<std::endl;

	if(command == TOCLIENT_INIT)
	{
		u8 deployed;
		packet[TOCLIENT_INIT_DEPLOYED].convert(&deployed);

		infostream<<"Client: TOCLIENT_INIT received with "
				"deployed="<<((int)deployed&0xff)<<std::endl;

		if(!ser_ver_supported(deployed))
		{
			infostream<<"Client: TOCLIENT_INIT: Server sent "
					<<"unsupported ser_fmt_ver"<<std::endl;
			return;
		}

		m_server_ser_ver = deployed;

		// Set player position
		Player *player = m_env.getLocalPlayer();
		if(!player)
			return;

		packet[TOCLIENT_INIT_SEED].convert(&m_map_seed);
		infostream<<"Client: received map seed: "<<m_map_seed<<std::endl;

		packet[TOCLIENT_INIT_STEP].convert(&m_recommended_send_interval);
		infostream<<"Client: received recommended send interval "
				<<m_recommended_send_interval<<std::endl;

		// TOCLIENT_INIT_POS

		if (m_localserver) {
			Settings settings;
			packet[TOCLIENT_INIT_MAP_PARAMS].convert(&settings);
			m_localserver->getEmergeManager()->params.load(settings);
		}

		// Reply to server
		MSGPACK_PACKET_INIT(TOSERVER_INIT2, 0);
		m_con.Send(PEER_ID_SERVER, 1, buffer, true);

		m_state = LC_Init;

		return;
	}

	if(command == TOCLIENT_ACCESS_DENIED_LEGACY)
	{
		// The server didn't like our password. Note, this needs
		// to be processed even if the serialisation format has
		// not been agreed yet, the same as TOCLIENT_INIT.
		m_access_denied = true;
		packet[TOCLIENT_ACCESS_DENIED_CUSTOM_STRING].convert(&m_access_denied_reason);
		return;
	}

	if(ser_version == SER_FMT_VER_INVALID)
	{
		infostream<<"Client: Server serialization"
				" format invalid or not initialized."
				" Skipping incoming command="<<command<<std::endl;
		return;
	}

	/*
	  Handle runtime commands
	*/
	// there's no sane reason why we shouldn't have a player and
	// almost everyone needs a player reference
	Player *player = m_env.getLocalPlayer();
	if(!player)
		return;

	if(command == TOCLIENT_REMOVENODE)
	{
		v3s16 p = packet[TOCLIENT_REMOVENODE_POS].as<v3s16>();
		removeNode(p, 2); //use light from top node
	}
	else if(command == TOCLIENT_ADDNODE)
	{
		v3s16 p = packet[TOCLIENT_ADDNODE_POS].as<v3s16>();
		MapNode n = packet[TOCLIENT_ADDNODE_NODE].as<MapNode>();
		bool remove_metadata = packet[TOCLIENT_ADDNODE_REMOVE_METADATA].as<bool>();

		addNode(p, n, remove_metadata, 2); //fast add
	}
	else if(command == TOCLIENT_BLOCKDATA)
	{
		v3s16 p = packet[TOCLIENT_BLOCKDATA_POS].as<v3s16>();
		s8 step = 1;
		packet[TOCLIENT_BLOCKDATA_STEP].convert(&step);
		if (step == 1) {

		std::istringstream istr(packet[TOCLIENT_BLOCKDATA_DATA].as<std::string>(), std::ios_base::binary);

		MapBlock *block;

		block = m_env.getMap().getBlockNoCreateNoEx(p);
		bool new_block = !block;
		if (new_block)
			block = new MapBlock(&m_env.getMap(), p, this);

		block->deSerialize(istr, ser_version, false);
		s32 h; // for convert to atomic
		packet[TOCLIENT_BLOCKDATA_HEAT].convert(&h);
		block->heat = h;
		packet[TOCLIENT_BLOCKDATA_HUMIDITY].convert(&h);
		block->humidity = h;


		if (packet.count(TOCLIENT_BLOCKDATA_CONTENT_ONLY))
			block->content_only = packet[TOCLIENT_BLOCKDATA_CONTENT_ONLY].as<content_t>();


		if (m_localserver != NULL) {
			m_localserver->getMap().saveBlock(block);
		}

		if (new_block)
			if (!m_env.getMap().insertBlock(block))
				delete block;

		/*
			//Add it to mesh update queue and set it to be acknowledged after update.
		*/
		//infostream<<"Adding mesh update task for received block "<<p<<std::endl;
		updateMeshTimestampWithEdge(p);
		if (block->content_only != CONTENT_IGNORE && block->content_only != CONTENT_AIR) {
			if (getNodeBlockPos(floatToInt(m_env.getLocalPlayer()->getPosition(), BS)).getDistanceFrom(p) <= 1)
				addUpdateMeshTaskWithEdge(p);
		}

/*
#if !defined(NDEBUG)
		if (m_env.getClientMap().m_block_boundary.size() > 150)
			m_env.getClientMap().m_block_boundary.clear();
		m_env.getClientMap().m_block_boundary[p] = block;
#endif
*/

		}//step

	}
	else if(command == TOCLIENT_INVENTORY)
	{
		std::string datastring = packet[TOCLIENT_INVENTORY_DATA].as<std::string>();
		std::istringstream is(datastring, std::ios_base::binary);
		Player *player = m_env.getLocalPlayer();
		if(!player)
			return;

		player->inventory.deSerialize(is);

		m_inventory_updated = true;

		delete m_inventory_from_server;
		m_inventory_from_server = new Inventory(player->inventory);
		m_inventory_from_server_age = 0.0;
	}
	else if(command == TOCLIENT_TIME_OF_DAY)
	{
		u16 time_of_day = packet[TOCLIENT_TIME_OF_DAY_TIME].as<u16>();
		time_of_day = time_of_day % 24000;
		f32 time_speed = packet[TOCLIENT_TIME_OF_DAY_TIME_SPEED].as<f32>();

		// Update environment
		m_env.setTimeOfDay(time_of_day);
		m_env.setTimeOfDaySpeed(time_speed);
		m_time_of_day_set = true;

		u32 dr = m_env.getDayNightRatio();
		verbosestream<<"Client: time_of_day="<<time_of_day
				<<" time_speed="<<time_speed
				<<" dr="<<dr<<std::endl;
	}
	else if(command == TOCLIENT_CHAT_MESSAGE)
	{
		std::string message = packet[TOCLIENT_CHAT_MESSAGE_DATA].as<std::string>();
		m_chat_queue.push(message);
	}
	else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD)
	{
		std::vector<u16> removed_objects;
		packet[TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD_REMOVE].convert(&removed_objects);
		for (size_t i = 0; i < removed_objects.size(); ++i)
			m_env.removeActiveObject(removed_objects[i]);

		std::vector<ActiveObjectAddData> added_objects;
		packet[TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD_ADD].convert(&added_objects);
		for (size_t i = 0; i < added_objects.size(); ++i)
			m_env.addActiveObject(added_objects[i].id, added_objects[i].type, added_objects[i].data);
	}
	else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES)
	{
		ActiveObjectMessages messages;
		packet[TOCLIENT_ACTIVE_OBJECT_MESSAGES_MESSAGES].convert(&messages);
		for (size_t i = 0; i < messages.size(); ++i)
			m_env.processActiveObjectMessage(messages[i].first, messages[i].second);
	}
	else if(command == TOCLIENT_MOVEMENT)
	{
		Player *player = m_env.getLocalPlayer();
		packet[TOCLIENT_MOVEMENT_ACCELERATION_DEFAULT].convert(&player->movement_acceleration_default);
		packet[TOCLIENT_MOVEMENT_ACCELERATION_AIR].convert(&player->movement_acceleration_air);
		packet[TOCLIENT_MOVEMENT_ACCELERATION_FAST].convert(&player->movement_acceleration_fast);
		packet[TOCLIENT_MOVEMENT_SPEED_WALK].convert(&player->movement_speed_walk);
		packet[TOCLIENT_MOVEMENT_SPEED_CROUCH].convert(&player->movement_speed_crouch);
		packet[TOCLIENT_MOVEMENT_SPEED_FAST].convert(&player->movement_speed_fast);
		packet[TOCLIENT_MOVEMENT_SPEED_CLIMB].convert(&player->movement_speed_climb);
		packet[TOCLIENT_MOVEMENT_SPEED_JUMP].convert(&player->movement_speed_jump);
		packet[TOCLIENT_MOVEMENT_LIQUID_FLUIDITY].convert(&player->movement_liquid_fluidity);
		packet[TOCLIENT_MOVEMENT_LIQUID_FLUIDITY_SMOOTH].convert(&player->movement_liquid_fluidity_smooth);
		packet[TOCLIENT_MOVEMENT_LIQUID_SINK].convert(&player->movement_liquid_sink);
		packet[TOCLIENT_MOVEMENT_GRAVITY].convert(&player->movement_gravity);
	}
	else if(command == TOCLIENT_HP)
	{
		Player *player = m_env.getLocalPlayer();
		if(!player)
			return;

		u8 oldhp = player->hp;
		u8 hp = packet[TOCLIENT_HP_HP].as<u8>();
		player->hp = hp;

		if(hp < oldhp)
		{
			// Add to ClientEvent queue
			ClientEvent event;
			event.type = CE_PLAYER_DAMAGE;
			event.player_damage.amount = oldhp - hp;
			m_client_event_queue.push(event);
		}
	}
	else if(command == TOCLIENT_BREATH)
	{
		Player *player = m_env.getLocalPlayer();
		player->setBreath(packet[TOCLIENT_BREATH_BREATH].as<u16>()) ;
	}
	else if(command == TOCLIENT_MOVE_PLAYER)
	{
		Player *player = m_env.getLocalPlayer();
		if(!player)
			return;

		v3f pos = packet[TOCLIENT_MOVE_PLAYER_POS].as<v3f>();
		f32 pitch = packet[TOCLIENT_MOVE_PLAYER_PITCH].as<f32>();
		f32 yaw = packet[TOCLIENT_MOVE_PLAYER_YAW].as<f32>();
		player->setPosition(pos);

		infostream<<"Client got TOCLIENT_MOVE_PLAYER"
				<<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
				<<" pitch="<<pitch
				<<" yaw="<<yaw
				<<std::endl;

		/*
			Add to ClientEvent queue.
			This has to be sent to the main program because otherwise
			it would just force the pitch and yaw values to whatever
			the camera points to.
		*/
		ClientEvent event;
		event.type = CE_PLAYER_FORCE_MOVE;
		event.player_force_move.pitch = pitch;
		event.player_force_move.yaw = yaw;
		m_client_event_queue.push(event);

		// Ignore damage for a few seconds, so that the player doesn't
		// get damage from falling on ground
		m_ignore_damage_timer = 3.0;
	}
	else if(command == TOCLIENT_DEATHSCREEN)
	{
		bool set_camera_point_target = packet[TOCLIENT_DEATHSCREEN_SET_CAMERA].as<bool>();
		v3f camera_point_target = packet[TOCLIENT_DEATHSCREEN_CAMERA_POINT].as<v3f>();

		ClientEvent event;
		event.type = CE_DEATHSCREEN;
		event.deathscreen.set_camera_point_target = set_camera_point_target;
		event.deathscreen.camera_point_target_x = camera_point_target.X;
		event.deathscreen.camera_point_target_y = camera_point_target.Y;
		event.deathscreen.camera_point_target_z = camera_point_target.Z;
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_ANNOUNCE_MEDIA)
	{
		if (m_media_downloader == NULL ||
				m_media_downloader->isStarted()) {
			const char *problem = m_media_downloader ?
				"we already saw another announcement" :
				"all media has been received already";
			errorstream<<"Client: Received media announcement but "
				<<problem<<"!"
				<<std::endl;
			return;
		}

		// Mesh update thread must be stopped while
		// updating content definitions
		//assert(!m_mesh_update_thread.IsRunning());

		MediaAnnounceList announce_list;
		packet[TOCLIENT_ANNOUNCE_MEDIA_LIST].convert(&announce_list);
		for (size_t i = 0; i < announce_list.size(); ++i)
			m_media_downloader->addFile(announce_list[i].first, base64_decode(announce_list[i].second));

		std::vector<std::string> remote_media;
		std::string remote_media_string = packet[TOCLIENT_ANNOUNCE_MEDIA_REMOTE_SERVER].as<std::string>();
		Strfnd sf(remote_media_string);
		while(!sf.atend()) {
			std::string baseurl = trim(sf.next(","));
			if(baseurl != "")
				m_media_downloader->addRemoteServer(baseurl);
		}

		m_media_downloader->step(this);
	}
	else if(command == TOCLIENT_MEDIA)
	{
		MediaData media_data;
		packet[TOCLIENT_MEDIA_MEDIA].convert(&media_data);

		// Mesh update thread must be stopped while
		// updating content definitions
		//assert(!m_mesh_update_thread.IsRunning());

		for(size_t i = 0; i < media_data.size(); ++i)
			m_media_downloader->conventionalTransferDone(
					media_data[i].first, media_data[i].second, this);
	}
	else if(command == TOCLIENT_NODEDEF)
	{
		infostream<<"Client: Received node definitions: packet size: "
				<<datasize<<std::endl;

		// Mesh update thread must be stopped while
		// updating content definitions
		//assert(!m_mesh_update_thread.IsRunning());

		packet[TOCLIENT_NODEDEF_DEFINITIONS].convert(m_nodedef);
		m_nodedef_received = true;
	}
	else if(command == TOCLIENT_ITEMDEF)
	{
		infostream<<"Client: Received item definitions: packet size: "
				<<datasize<<std::endl;

		// Mesh update thread must be stopped while
		// updating content definitions
		//assert(!m_mesh_update_thread.IsRunning());

		packet[TOCLIENT_ITEMDEF_DEFINITIONS].convert(m_itemdef);
		m_itemdef_received = true;
	}
	else if(command == TOCLIENT_PLAY_SOUND)
	{
		s32 server_id = packet[TOCLIENT_PLAY_SOUND_ID].as<s32>();
		std::string name = packet[TOCLIENT_PLAY_SOUND_NAME].as<std::string>();
		float gain = packet[TOCLIENT_PLAY_SOUND_GAIN].as<f32>();
		int type = packet[TOCLIENT_PLAY_SOUND_TYPE].as<u8>(); // 0=local, 1=positional, 2=object
		v3f pos = packet[TOCLIENT_PLAY_SOUND_POS].as<v3f>();
		u16 object_id = packet[TOCLIENT_PLAY_SOUND_OBJECT_ID].as<u16>();
		bool loop = packet[TOCLIENT_PLAY_SOUND_LOOP].as<bool>();
		// Start playing
		int client_id = -1;
		switch(type){
		case 0: // local
			client_id = m_sound->playSound(name, loop, gain);
			break;
		case 1: // positional
			client_id = m_sound->playSoundAt(name, loop, gain, pos);
			break;
		case 2: { // object
			ClientActiveObject *cao = m_env.getActiveObject(object_id);
			if(cao)
				pos = cao->getPosition();
			client_id = m_sound->playSoundAt(name, loop, gain, pos);
			// TODO: Set up sound to move with object
			break; }
		default:
			break;
		}
		if(client_id != -1){
			m_sounds_server_to_client[server_id] = client_id;
			m_sounds_client_to_server[client_id] = server_id;
			if(object_id != 0)
				m_sounds_to_objects[client_id] = object_id;
		}
	}
	else if(command == TOCLIENT_STOP_SOUND)
	{
		s32 server_id = packet[TOCLIENT_STOP_SOUND_ID].as<s32>();
		std::map<s32, int>::iterator i =
				m_sounds_server_to_client.find(server_id);
		if(i != m_sounds_server_to_client.end()){
			int client_id = i->second;
			m_sound->stopSound(client_id);
		}
	}
	else if(command == TOCLIENT_PRIVILEGES)
	{
		packet[TOCLIENT_PRIVILEGES_PRIVILEGES].convert(&m_privileges);
	}
	else if(command == TOCLIENT_INVENTORY_FORMSPEC)
	{
		// Store formspec in LocalPlayer
		player->inventory_formspec = packet[TOCLIENT_INVENTORY_FORMSPEC_DATA].as<std::string>();
	}
	else if(command == TOCLIENT_DETACHED_INVENTORY)
	{
		std::string name = packet[TOCLIENT_DETACHED_INVENTORY_NAME].as<std::string>();
		std::string datastring = packet[TOCLIENT_DETACHED_INVENTORY_DATA].as<std::string>();
		std::istringstream is(datastring, std::ios_base::binary);

		infostream<<"Client: Detached inventory update: \""<<name<<"\""<<std::endl;

		Inventory *inv = NULL;
		if(m_detached_inventories.count(name) > 0)
			inv = m_detached_inventories[name];
		else{
			inv = new Inventory(m_itemdef);
			m_detached_inventories[name] = inv;
		}
		inv->deSerialize(is);
	}
	else if(command == TOCLIENT_SHOW_FORMSPEC)
	{
		std::string formspec = packet[TOCLIENT_SHOW_FORMSPEC_DATA].as<std::string>();
		std::string formname = packet[TOCLIENT_SHOW_FORMSPEC_NAME].as<std::string>();

		ClientEvent event;
		event.type = CE_SHOW_FORMSPEC;
		// pointer is required as event is a struct only!
		// adding a std:string to a struct isn't possible
		event.show_formspec.formspec = new std::string(formspec);
		event.show_formspec.formname = new std::string(formname);
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_SPAWN_PARTICLE)
	{
		v3f pos = packet[TOCLIENT_SPAWN_PARTICLE_POS].as<v3f>();
		v3f vel = packet[TOCLIENT_SPAWN_PARTICLE_VELOCITY].as<v3f>();
		v3f acc = packet[TOCLIENT_SPAWN_PARTICLE_ACCELERATION].as<v3f>();
		float expirationtime = packet[TOCLIENT_SPAWN_PARTICLE_EXPIRATIONTIME].as<float>();
		float size = packet[TOCLIENT_SPAWN_PARTICLE_SIZE].as<float>();
		bool collisiondetection = packet[TOCLIENT_SPAWN_PARTICLE_COLLISIONDETECTION].as<bool>();
		std::string texture = packet[TOCLIENT_SPAWN_PARTICLE_TEXTURE].as<std::string>();
		bool vertical = packet[TOCLIENT_SPAWN_PARTICLE_VERTICAL].as<bool>();

		ClientEvent event;
		event.type = CE_SPAWN_PARTICLE;
		event.spawn_particle.pos = new v3f (pos);
		event.spawn_particle.vel = new v3f (vel);
		event.spawn_particle.acc = new v3f (acc);

		event.spawn_particle.expirationtime = expirationtime;
		event.spawn_particle.size = size;
		event.spawn_particle.collisiondetection =
				collisiondetection;
		event.spawn_particle.vertical = vertical;
		event.spawn_particle.texture = new std::string(texture);

		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_ADD_PARTICLESPAWNER)
	{
		u16 amount;
		float spawntime, minexptime, maxexptime, minsize, maxsize;
		v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
		bool collisiondetection, vertical;
		u32 id;
		std::string texture;

		packet[TOCLIENT_ADD_PARTICLESPAWNER_AMOUNT].convert(&amount);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_SPAWNTIME].convert(&spawntime);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MINPOS].convert(&minpos);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MAXPOS].convert(&maxpos);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MINVEL].convert(&minvel);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MAXVEL].convert(&maxvel);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MINACC].convert(&minacc);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MAXACC].convert(&maxacc);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MINEXPTIME].convert(&minexptime);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MAXEXPTIME].convert(&maxexptime);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MINSIZE].convert(&minsize);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_MAXSIZE].convert(&maxsize);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_COLLISIONDETECTION].convert(&collisiondetection);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_TEXTURE].convert(&texture);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_VERTICAL].convert(&vertical);
		packet[TOCLIENT_ADD_PARTICLESPAWNER_ID].convert(&id);

		ClientEvent event;
		event.type = CE_ADD_PARTICLESPAWNER;
		event.add_particlespawner.amount = amount;
		event.add_particlespawner.spawntime = spawntime;

		event.add_particlespawner.minpos = new v3f (minpos);
		event.add_particlespawner.maxpos = new v3f (maxpos);
		event.add_particlespawner.minvel = new v3f (minvel);
		event.add_particlespawner.maxvel = new v3f (maxvel);
		event.add_particlespawner.minacc = new v3f (minacc);
		event.add_particlespawner.maxacc = new v3f (maxacc);

		event.add_particlespawner.minexptime = minexptime;
		event.add_particlespawner.maxexptime = maxexptime;
		event.add_particlespawner.minsize = minsize;
		event.add_particlespawner.maxsize = maxsize;
		event.add_particlespawner.collisiondetection = collisiondetection;
		event.add_particlespawner.vertical = vertical;
		event.add_particlespawner.texture = new std::string(texture);
		event.add_particlespawner.id = id;

		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_DELETE_PARTICLESPAWNER)
	{
		u32 id = packet[TOCLIENT_DELETE_PARTICLESPAWNER_ID].as<u32>();

		ClientEvent event;
		event.type = CE_DELETE_PARTICLESPAWNER;
		event.delete_particlespawner.id = id;

		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_HUDADD)
	{
		//std::string datastring((char *)&data[2], datasize - 2);
		//std::istringstream is(datastring, std::ios_base::binary);

		u32 id, number, item, dir;
		u8 type;
		v2f pos, scale, align, offset;
		std::string name, text;
		v3f world_pos;
		v2s32 size;

		packet[TOCLIENT_HUDADD_ID].convert(&id);
		packet[TOCLIENT_HUDADD_TYPE].convert(&type);
		packet[TOCLIENT_HUDADD_POS].convert(&pos);
		packet[TOCLIENT_HUDADD_NAME].convert(&name);
		packet[TOCLIENT_HUDADD_SCALE].convert(&scale);
		packet[TOCLIENT_HUDADD_TEXT].convert(&text);
		packet[TOCLIENT_HUDADD_NUMBER].convert(&number);
		packet[TOCLIENT_HUDADD_ITEM].convert(&item);
		packet[TOCLIENT_HUDADD_DIR].convert(&dir);
		packet[TOCLIENT_HUDADD_ALIGN].convert(&align);
		packet[TOCLIENT_HUDADD_OFFSET].convert(&offset);
		packet[TOCLIENT_HUDADD_WORLD_POS].convert(&world_pos);
		packet[TOCLIENT_HUDADD_SIZE].convert(&size);

		ClientEvent event;
		event.type = CE_HUDADD;
		event.hudadd.id     = id;
		event.hudadd.type   = type;
		event.hudadd.pos    = new v2f(pos);
		event.hudadd.name   = new std::string(name);
		event.hudadd.scale  = new v2f(scale);
		event.hudadd.text   = new std::string(text);
		event.hudadd.number = number;
		event.hudadd.item   = item;
		event.hudadd.dir    = dir;
		event.hudadd.align  = new v2f(align);
		event.hudadd.offset = new v2f(offset);
		event.hudadd.world_pos = new v3f(world_pos);
		event.hudadd.size      = new v2s32(size);
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_HUDRM)
	{
		u32 id = packet[TOCLIENT_HUDRM_ID].as<u32>();

		ClientEvent event;
		event.type = CE_HUDRM;
		event.hudrm.id = id;
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_HUDCHANGE)
	{
		std::string sdata;
		v2f v2fdata;
		v3f v3fdata;
		v2s32 v2s32data;
		u32 intdata = 0;

		u32 id = packet[TOCLIENT_HUDCHANGE_ID].as<u32>();
		u8 stat = packet[TOCLIENT_HUDCHANGE_STAT].as<int>();

		if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE ||
				stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET)
			packet[TOCLIENT_HUDCHANGE_V2F].convert(&v2fdata);
		else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT)
			packet[TOCLIENT_HUDCHANGE_STRING].convert(&sdata);
		else if (stat == HUD_STAT_WORLD_POS)
			packet[TOCLIENT_HUDCHANGE_V3F].convert(&v3fdata);
		else if (stat == HUD_STAT_SIZE)
			packet[TOCLIENT_HUDCHANGE_V2S32].convert(&v2s32data);
		else
			packet[TOCLIENT_HUDCHANGE_U32].convert(&intdata);

		ClientEvent event;
		event.type = CE_HUDCHANGE;
		event.hudchange.id      = id;
		event.hudchange.stat    = (HudElementStat)stat;
		event.hudchange.v2fdata = new v2f(v2fdata);
		event.hudchange.v3fdata = new v3f(v3fdata);
		event.hudchange.sdata   = new std::string(sdata);
		event.hudchange.data    = intdata;
		event.hudchange.v2s32data = new v2s32(v2s32data);
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_HUD_SET_FLAGS)
	{
		Player *player = m_env.getLocalPlayer();
		if(!player)
			return;

		u32 flags = packet[TOCLIENT_HUD_SET_FLAGS_FLAGS].as<u32>();
		u32 mask = packet[TOCLIENT_HUD_SET_FLAGS_MASK].as<u32>();

		player->hud_flags &= ~mask;
		player->hud_flags |= flags;
	}
	else if(command == TOCLIENT_HUD_SET_PARAM)
	{
		u16 param = packet[TOCLIENT_HUD_SET_PARAM_ID].as<u16>();
		std::string value = packet[TOCLIENT_HUD_SET_PARAM_VALUE].as<std::string>();

		if(param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4){
			s32 hotbar_itemcount = readS32((u8*) value.c_str());
			if(hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX)
				player->hud_hotbar_itemcount = hotbar_itemcount;
		} else if (param == HUD_PARAM_HOTBAR_IMAGE) {
			((LocalPlayer *) player)->hotbar_image = value;
		} else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) {
			((LocalPlayer *) player)->hotbar_selected_image = value;
		}
	}
/*
	else if(command == TOCLIENT_ANIMATIONS)
	{
		LocalPlayer *player = m_env.getLocalPlayer();
		packet[TOCLIENT_ANIMATIONS_DEFAULT_START].convert(&player->animation_default_start);
		packet[TOCLIENT_ANIMATIONS_DEFAULT_STOP].convert(&player->animation_default_stop);
		packet[TOCLIENT_ANIMATIONS_WALK_START].convert(&player->animation_walk_start);
		packet[TOCLIENT_ANIMATIONS_WALK_STOP].convert(&player->animation_walk_stop);
		packet[TOCLIENT_ANIMATIONS_DIG_START].convert(&player->animation_dig_start);
		packet[TOCLIENT_ANIMATIONS_DIG_STOP].convert(&player->animation_dig_stop);
		packet[TOCLIENT_ANIMATIONS_WD_START].convert(&player->animation_wd_start);
		packet[TOCLIENT_ANIMATIONS_WD_STOP].convert(&player->animation_wd_stop);
	}
*/
	else if(command == TOCLIENT_SET_SKY)
	{
		video::SColor *bgcolor = new video::SColor(packet[TOCLIENT_SET_SKY_COLOR].as<video::SColor>());
		std::string *type = new std::string(packet[TOCLIENT_SET_SKY_TYPE].as<std::string>());
		std::vector<std::string> *params = new std::vector<std::string>;
		packet[TOCLIENT_SET_SKY_PARAMS].convert(params);

		ClientEvent event;
		event.type            = CE_SET_SKY;
		event.set_sky.bgcolor = bgcolor;
		event.set_sky.type    = type;
		event.set_sky.params  = params;
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO)
	{
		bool do_override;
		float day_night_ratio_f;
		packet[TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO_DO].convert(&do_override);
		packet[TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO_VALUE].convert(&day_night_ratio_f);

		ClientEvent event;
		event.type                                 = CE_OVERRIDE_DAY_NIGHT_RATIO;
		event.override_day_night_ratio.do_override = do_override;
		event.override_day_night_ratio.ratio_f     = day_night_ratio_f;
		m_client_event_queue.push(event);
	}
	else if(command == TOCLIENT_LOCAL_PLAYER_ANIMATIONS)
	{
		LocalPlayer *player = m_env.getLocalPlayer();
		if(!player)
			return;

		packet[TOCLIENT_LOCAL_PLAYER_ANIMATIONS_IDLE].convert(&player->local_animations[0]);
		packet[TOCLIENT_LOCAL_PLAYER_ANIMATIONS_WALK].convert(&player->local_animations[1]);
		packet[TOCLIENT_LOCAL_PLAYER_ANIMATIONS_DIG].convert(&player->local_animations[2]);
		packet[TOCLIENT_LOCAL_PLAYER_ANIMATIONS_WALKDIG].convert(&player->local_animations[3]);
		packet[TOCLIENT_LOCAL_PLAYER_ANIMATIONS_FRAME_SPEED].convert(&player->local_animation_speed);
	}
	else if(command == TOCLIENT_EYE_OFFSET)
	{
		LocalPlayer *player = m_env.getLocalPlayer();
		if(!player)
			return;

		packet[TOCLIENT_EYE_OFFSET_FIRST].convert(&player->eye_offset_first);
		packet[TOCLIENT_EYE_OFFSET_THIRD].convert(&player->eye_offset_third);
	}
	else
	{
		infostream<<"Client: Ignoring unknown command "
				<<command<<std::endl;
	}
}
Ejemplo n.º 12
0
int RemoteClient::GetNextBlocks(
		ServerEnvironment *env,
		EmergeManager * emerge,
		float dtime,
		double m_uptime,
		std::vector<PrioritySortedBlockTransfer> &dest)
{
	DSTACK(__FUNCTION_NAME);

	// Increment timers
	m_nothing_to_send_pause_timer -= dtime;
	m_nearest_unsent_reset_timer += dtime;

	if(m_nothing_to_send_pause_timer >= 0)
		return 0;

	Player *player = env->getPlayer(peer_id);
	// This can happen sometimes; clients and players are not in perfect sync.
	if(player == NULL)
		return 0;

	v3f playerpos = player->getPosition();
	v3f playerspeed = player->getSpeed();
	if(playerspeed.getLength() > 1000.0*BS) //cheater or bug, ignore him
		return 0;
	v3f playerspeeddir(0,0,0);
	if(playerspeed.getLength() > 1.0*BS)
		playerspeeddir = playerspeed / playerspeed.getLength();
	// Predict to next block
	v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;

	v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);

	v3s16 center = getNodeBlockPos(center_nodepos);

	// Camera position and direction
	v3f camera_pos = player->getEyePosition();
	v3f camera_dir = v3f(0,0,1);
	camera_dir.rotateYZBy(player->getPitch());
	camera_dir.rotateXZBy(player->getYaw());

	/*infostream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
			<<camera_dir.Z<<")"<<std::endl;*/

	/*
		Get the starting value of the block finder radius.
	*/

	if(m_last_center != center)
	{
		m_nearest_unsent_d = 0;
		m_last_center = center;
	}

	/*infostream<<"m_nearest_unsent_reset_timer="
			<<m_nearest_unsent_reset_timer<<std::endl;*/

	// Reset periodically to workaround for some bugs or stuff
	if(m_nearest_unsent_reset_timer > 120.0)
	{
		m_nearest_unsent_reset_timer = 0;
		m_nearest_unsent_d = 0;
		m_nearest_unsent_nearest = 0;
		//infostream<<"Resetting m_nearest_unsent_d for "<<peer_id<<std::endl;
	}

	//s16 last_nearest_unsent_d = m_nearest_unsent_d;
	s16 d_start = m_nearest_unsent_d;

	//infostream<<"d_start="<<d_start<<std::endl;

	u16 max_simul_sends_setting = g_settings->getU16
			("max_simultaneous_block_sends_per_client");
	u16 max_simul_sends_usually = max_simul_sends_setting;

	/*
		Check the time from last addNode/removeNode.

		Decrease send rate if player is building stuff.
	*/
	m_time_from_building += dtime;
	if(m_time_from_building < g_settings->getFloat(
				"full_block_send_enable_min_time_from_building"))
	{
		max_simul_sends_usually
			= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
	}

	/*
		Number of blocks sending + number of blocks selected for sending
	*/
	u32 num_blocks_selected = 0;
	u32 num_blocks_sending = 0;

	/*
		next time d will be continued from the d from which the nearest
		unsent block was found this time.

		This is because not necessarily any of the blocks found this
		time are actually sent.
	*/
	s32 new_nearest_unsent_d = -1;

	s16 full_d_max = g_settings->getS16("max_block_send_distance");
	if (wanted_range) {
		s16 wanted_blocks = wanted_range / MAP_BLOCKSIZE + 1;
		if (wanted_blocks < full_d_max)
			full_d_max = wanted_blocks;
	}

	s16 d_max = full_d_max;
	s16 d_max_gen = g_settings->getS16("max_block_generate_distance");

	// Don't loop very much at a time
	s16 max_d_increment_at_time = 5;
	if(d_max > d_start + max_d_increment_at_time)
		d_max = d_start + max_d_increment_at_time;
	/*if(d_max_gen > d_start+2)
		d_max_gen = d_start+2;*/

	//infostream<<"Starting from "<<d_start<<std::endl;

	s32 nearest_emerged_d = -1;
	s32 nearest_emergefull_d = -1;
	s32 nearest_sent_d = -1;
	bool queue_is_full = false;

	f32 speed_in_blocks = (playerspeed/(MAP_BLOCKSIZE*BS)).getLength();

	s16 d;
	for(d = d_start; d <= d_max; d++)
	{
		/*errorstream<<"checking d="<<d<<" for "
				<<server->getPlayerName(peer_id)<<std::endl;*/
		//infostream<<"RemoteClient::SendBlocks(): d="<<d<<" d_start="<<d_start<<" d_max="<<d_max<<" d_max_gen="<<d_max_gen<<std::endl;

		/*
			If m_nearest_unsent_d was changed by the EmergeThread
			(it can change it to 0 through SetBlockNotSent),
			update our d to it.
			Else update m_nearest_unsent_d
		*/
		/*if(m_nearest_unsent_d != last_nearest_unsent_d)
		{
			d = m_nearest_unsent_d;
			last_nearest_unsent_d = m_nearest_unsent_d;
		}*/

		std::list<v3s16> list;
		bool can_skip = d > 1;
		// Fast fall/move optimize. speed_in_blocks now limited to 6.4
		if (speed_in_blocks>0.8 && d <= 2) {
			can_skip = false;
			if (d == 0) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*2; ++addn)
					list.push_back(floatToInt(playerspeeddir*addn, 1));
			} else if (d == 1) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*1.5; ++addn) {
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( 0,  0,  1)); // back
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( -1, 0,  0)); // left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( 1,  0,  0)); // right
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( 0,  0, -1)); // front
				}
			} else if (d == 2) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*1.5; ++addn) {
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( -1, 0,  1)); // back left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( 1,  0,  1)); // left right
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( -1, 0, -1)); // right left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3s16( 1,  0, -1)); // front right
				}
			}
		} else {
		/*
			Get the border/face dot coordinates of a "d-radiused"
			box
		*/
			getFacePositions(list, d);
		}

		std::list<v3s16>::iterator li;
		for(li=list.begin(); li!=list.end(); ++li)
		{
			v3s16 p = *li + center;

			/*
				Send throttling
				- Don't allow too many simultaneous transfers
				- EXCEPT when the blocks are very close

				Also, don't send blocks that are already flying.
			*/

			// Start with the usual maximum
			u16 max_simul_dynamic = max_simul_sends_usually;

			// If block is very close, allow full maximum
			if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
				max_simul_dynamic = max_simul_sends_setting;

			// Don't select too many blocks for sending
			if(num_blocks_selected+num_blocks_sending >= max_simul_dynamic)
			{
				queue_is_full = true;
				goto queue_full_break;
			}

			/*
				Do not go over-limit
			*/
			if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
				continue;

			// If this is true, inexistent block will be made from scratch
			bool generate = d <= d_max_gen;

			{
				/*// Limit the generating area vertically to 2/3
				if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
					generate = false;*/

				/* maybe good idea (if not use block culling) but brokes far (25+) area generate by flooding emergequeue with no generate blocks
				// Limit the send area vertically to 1/2
				if(can_skip && abs(p.Y - center.Y) > full_d_max / 2)
					generate = false;
				*/
			}


			//infostream<<"d="<<d<<std::endl;
			/*
				Don't generate or send if not in sight
				FIXME This only works if the client uses a small enough
				FOV setting. The default of 72 degrees is fine.
			*/

			float camera_fov = (80.0*M_PI/180) * 4./3.;
			if(can_skip && isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false)
			{
				continue;
			}

			/*
				Don't send already sent blocks
			*/
			{
				if(m_blocks_sent.find(p) != m_blocks_sent.end() && m_blocks_sent[p] > 0 && m_blocks_sent[p] + (d <= 2 ? 1 : d*d*d) > m_uptime) {
					continue;
				}
			}

			/*
				Check if map has this block
			*/
			MapBlock *block = env->getMap().getBlockNoCreateNoEx(p);

			bool surely_not_found_on_disk = false;
			bool block_is_invalid = false;
			if(block != NULL)
			{

				if (m_blocks_sent[p] > 0 && m_blocks_sent[p] >= block->m_changed_timestamp) {
					continue;
				}

				// Reset usage timer, this block will be of use in the future.
				block->resetUsageTimer();

				// Block is dummy if data doesn't exist.
				// It means it has been not found from disk and not generated
				if(block->isDummy())
				{
					surely_not_found_on_disk = true;
				}

				if (block->getLightingExpired()) {
					continue;
				}
				// Block is valid if lighting is up-to-date and data exists
				if(block->isValid() == false)
				{
					block_is_invalid = true;
				}

				if(block->isGenerated() == false)
				{
					continue;
				}

				/*
					If block is not close, don't send it unless it is near
					ground level.

					Block is near ground level if night-time mesh
					differs from day-time mesh.
				*/
/*
				if(d >= 4)
				{
					if(block->getDayNightDiff() == false)
						continue;
				}
*/
			}

			/*
				If block has been marked to not exist on disk (dummy)
				and generating new ones is not wanted, skip block.
			*/
			if(generate == false && surely_not_found_on_disk == true)
			{
				// get next one.
				continue;
			}

			/*
				Add inexistent block to emerge queue.
			*/
			if(block == NULL || surely_not_found_on_disk || block_is_invalid)
			{
				//infostream<<"start gen d="<<d<<" p="<<p<<" notfound="<<surely_not_found_on_disk<<" invalid="<< block_is_invalid<<" block="<<block<<" generate="<<generate<<std::endl;

				if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
					if (nearest_emerged_d == -1)
						nearest_emerged_d = d;
				} else {
					if (nearest_emergefull_d == -1)
						nearest_emergefull_d = d;
					goto queue_full_break;
				}

				// get next one.
				continue;
			}

			if(nearest_sent_d == -1)
				nearest_sent_d = d;

			/*
				Add block to send queue
			*/

			PrioritySortedBlockTransfer q((float)d, p, peer_id);

			dest.push_back(q);

			num_blocks_selected += 1;
		}
	}
queue_full_break:

	//infostream<<"Stopped at "<<d<<" d_start="<<d_start<< " d_max="<<d_max<<" nearest_emerged_d="<<nearest_emerged_d<<" nearest_emergefull_d="<<nearest_emergefull_d<< " new_nearest_unsent_d="<<new_nearest_unsent_d<< " sel="<<num_blocks_selected<< "+"<<num_blocks_sending <<std::endl;
	num_blocks_selected += num_blocks_sending;
	if(!num_blocks_selected && d_start == d) {
		//new_nearest_unsent_d = 0;
		m_nothing_to_send_pause_timer = 1.0;
	}
		

	// If nothing was found for sending and nothing was queued for
	// emerging, continue next time browsing from here
	if(nearest_emerged_d != -1){
		new_nearest_unsent_d = nearest_emerged_d;
	} else if(nearest_emergefull_d != -1){
		new_nearest_unsent_d = nearest_emergefull_d;
	} else {
		if(d > full_d_max){
			new_nearest_unsent_d = 0;
			m_nothing_to_send_pause_timer = 2.0;
		} else {
			if(nearest_sent_d != -1)
				new_nearest_unsent_d = nearest_sent_d;
			else
				new_nearest_unsent_d = d;
		}
	}

	if(new_nearest_unsent_d != -1)
		m_nearest_unsent_d = new_nearest_unsent_d;
	return num_blocks_selected - num_blocks_sending;
}
Ejemplo n.º 13
0
void Server::handleCommand_Interact(NetworkPacket* pkt) {
	const auto peer_id = pkt->getPeerId();
	auto & packet = *(pkt->packet);
	auto player = m_env->getPlayer(pkt->getPeerId());
	if (!player) {
		m_con.DisconnectPeer(pkt->getPeerId());
		return;
	}
	auto playersao = player->getPlayerSAO();
	if (!playersao) {
		m_con.DisconnectPeer(pkt->getPeerId());
		return;
	}

	u8 action;
	u16 item_i;
	PointedThing pointed;

	packet[TOSERVER_INTERACT_ACTION].convert(action);
	packet[TOSERVER_INTERACT_ITEM].convert(item_i);
	packet[TOSERVER_INTERACT_POINTED_THING].convert(pointed);

	if(player->hp == 0) {
		verbosestream << "TOSERVER_INTERACT: " << player->getName()
		              << " tried to interact, but is dead!" << std::endl;
		return;
	}

	MAP_NOTHREAD_LOCK((&m_env->getMap()));

	v3f player_pos = playersao->getLastGoodPosition();

	// Update wielded item
	playersao->setWieldIndex(item_i);

	// Get pointed to node (undefined if not POINTEDTYPE_NODE)
	v3s16 p_under = pointed.node_undersurface;
	v3s16 p_above = pointed.node_abovesurface;

	// Get pointed to object (NULL if not POINTEDTYPE_OBJECT)
	ServerActiveObject *pointed_object = NULL;
	if(pointed.type == POINTEDTHING_OBJECT) {
		pointed_object = m_env->getActiveObject(pointed.object_id);
		if(pointed_object == NULL) {
			verbosestream << "TOSERVER_INTERACT: "
			              "pointed object is NULL" << std::endl;
			return;
		}

	}

	v3f pointed_pos_under = player_pos;
	v3f pointed_pos_above = player_pos;
	if(pointed.type == POINTEDTHING_NODE) {
		pointed_pos_under = intToFloat(p_under, BS);
		pointed_pos_above = intToFloat(p_above, BS);
	} else if(pointed.type == POINTEDTHING_OBJECT) {
		pointed_pos_under = pointed_object->getBasePosition();
		pointed_pos_above = pointed_pos_under;
	}

	/*
		Check that target is reasonably close
		(only when digging or placing things)
	*/
	static const bool enable_anticheat = !g_settings->getBool("disable_anticheat");
	if ((action == 0 || action == 2 || action == 3) &&
	        (enable_anticheat && !isSingleplayer())) {
		float d = player_pos.getDistanceFrom(pointed_pos_under);
		float max_d = BS * 14; // Just some large enough value
		if(d > max_d) {
			actionstream << "Player " << player->getName()
			             << " tried to access " << pointed.dump()
			             << " from too far: "
			             << "d=" << d << ", max_d=" << max_d
			             << ". ignoring." << std::endl;
			// Re-send block to revert change on client-side
			RemoteClient *client = getClient(peer_id);
			v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
			client->SetBlockNotSent(blockpos);
			// Call callbacks
			m_script->on_cheat(playersao, "interacted_too_far");
			// Do nothing else
			return;
		}
	}

	/*
		Make sure the player is allowed to do it
	*/
	if(!checkPriv(player->getName(), "interact")) {
		actionstream << player->getName() << " attempted to interact with "
		             << pointed.dump() << " without 'interact' privilege"
		             << std::endl;
		// Re-send block to revert change on client-side
		RemoteClient *client = getClient(peer_id);
		// Digging completed -> under
		if(action == 2) {
			v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
			client->SetBlockNotSent(blockpos);
		}
		// Placement -> above
		if(action == 3) {
			v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
			client->SetBlockNotSent(blockpos);
		}
		stat.add("interact_denied", player->getName());
		return;
	}

	/*
		If something goes wrong, this player is to blame
	*/
	RollbackScopeActor rollback_scope(m_rollback,
	                                  std::string("player:") + player->getName());

	/*
		0: start digging or punch object
	*/
	if(action == 0) {
		if(pointed.type == POINTEDTHING_NODE) {
			/*
				NOTE: This can be used in the future to check if
				somebody is cheating, by checking the timing.
			*/
			MapNode n = m_env->getMap().getNode(p_under);

			if (!n) {
				infostream << "Server: Not punching: Node not found."
				           << " Adding block to emerge queue."
				           << std::endl;
				m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false);
			}

			if(n.getContent() != CONTENT_IGNORE)
				m_script->node_on_punch(p_under, n, playersao, pointed);
			// Cheat prevention
			playersao->noCheatDigStart(p_under);
		} else if(pointed.type == POINTEDTHING_OBJECT) {
			// Skip if object has been removed
			if(pointed_object->m_removed)
				return;

			actionstream << player->getName() << " punches object "
			             << pointed.object_id << ": "
			             << pointed_object->getDescription() << std::endl;

			ItemStack punchitem = playersao->getWieldedItem();
			ToolCapabilities toolcap =
			    punchitem.getToolCapabilities(m_itemdef);
			v3f dir = (pointed_object->getBasePosition() -
			           (player->getPosition() + player->getEyeOffset())
			          ).normalize();
			float time_from_last_punch =
			    playersao->resetTimeFromLastPunch();

			s16 src_original_hp = pointed_object->getHP();
			s16 dst_origin_hp = playersao->getHP();

			pointed_object->punch(dir, &toolcap, playersao,
			                      time_from_last_punch);

			// If the object is a player and its HP changed
			if (src_original_hp != pointed_object->getHP() &&
			        pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
				SendPlayerHPOrDie(((PlayerSAO*)pointed_object));
			}

			// If the puncher is a player and its HP changed
			if (dst_origin_hp != playersao->getHP()) {
				SendPlayerHPOrDie(playersao);
			}

			stat.add("punch", player->getName());
		}

	} // action == 0

	/*
		1: stop digging
	*/
	else if(action == 1) {
	} // action == 1

	/*
		2: Digging completed
	*/
	else if(action == 2) {
		// Only digging of nodes
		if(pointed.type == POINTEDTHING_NODE) {
			MapNode n = m_env->getMap().getNode(p_under);
			if (!n) {
				infostream << "Server: Not finishing digging: Node not found."
				           << " Adding block to emerge queue."
				           << std::endl;
				m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false);
			}

			/* Cheat prevention */
			bool is_valid_dig = true;
			if (enable_anticheat && !isSingleplayer()) {
				v3s16 nocheat_p = playersao->getNoCheatDigPos();
				float nocheat_t = playersao->getNoCheatDigTime();
				playersao->noCheatDigEnd();
				// If player didn't start digging this, ignore dig
				if(nocheat_p != p_under) {
					infostream << "Server: NoCheat: " << player->getName()
					           << " started digging "
					           << PP(nocheat_p) << " and completed digging "
					           << PP(p_under) << "; not digging." << std::endl;
					is_valid_dig = false;
					// Call callbacks
					m_script->on_cheat(playersao, "finished_unknown_dig");
				}
				// Get player's wielded item
				ItemStack playeritem;
				InventoryList *mlist = playersao->getInventory()->getList("main");
				if(mlist != NULL)
					playeritem = mlist->getItem(playersao->getWieldIndex());
				ToolCapabilities playeritem_toolcap =
				    playeritem.getToolCapabilities(m_itemdef);
				// Get diggability and expected digging time
				DigParams params = getDigParams(m_nodedef->get(n).groups,
				                                &playeritem_toolcap);
				// If can't dig, try hand
				if(!params.diggable) {
					const ItemDefinition &hand = m_itemdef->get("");
					const ToolCapabilities *tp = hand.tool_capabilities;
					if(tp)
						params = getDigParams(m_nodedef->get(n).groups, tp);
				}
				// If can't dig, ignore dig
				if(!params.diggable) {
					infostream << "Server: NoCheat: " << player->getName()
					           << " completed digging " << PP(p_under)
					           << ", which is not diggable with tool. not digging."
					           << std::endl;
					is_valid_dig = false;
					// Call callbacks
					m_script->on_cheat(playersao, "dug_unbreakable");
				}
				// Check digging time
				// If already invalidated, we don't have to
				if(!is_valid_dig) {
					// Well not our problem then
				}
				// Clean and long dig
				else if(params.time > 2.0 && nocheat_t * 1.2 > params.time) {
					// All is good, but grab time from pool; don't care if
					// it's actually available
					playersao->getDigPool().grab(params.time);
				}
				// Short or laggy dig
				// Try getting the time from pool
				else if(playersao->getDigPool().grab(params.time)) {
					// All is good
				}
				// Dig not possible
				else {
					infostream << "Server: NoCheat: " << player->getName()
					           << " completed digging " << PP(p_under)
					           << "too fast; not digging." << std::endl;
					is_valid_dig = false;
					// Call callbacks
					m_script->on_cheat(playersao, "dug_too_fast");
				}
			}

			/* Actually dig node */

			if(is_valid_dig && n.getContent() != CONTENT_IGNORE) {
				m_script->node_on_dig(p_under, n, playersao);
				stat.add("dig", player->getName());
				stat.add("dig_" + m_nodedef->get(n).name , player->getName());
			}

			v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
			RemoteClient *client = getClient(peer_id);
			// Send unusual result (that is, node not being removed)
			if(m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) {
				// Re-send block to revert change on client-side
				client->SetBlockNotSent(blockpos);
			} else {
				client->ResendBlockIfOnWire(blockpos);
			}
			m_env->nodeUpdate(p_under, 5, 0);
		}
	} // action == 2

	/*
		3: place block or right-click object
	*/
	else if(action == 3) {
		ItemStack item = playersao->getWieldedItem();

		// Reset build time counter
		if(pointed.type == POINTEDTHING_NODE &&
		        item.getDefinition(m_itemdef).type == ITEM_NODE)
			getClient(peer_id)->m_time_from_building = 0.0;

		if(pointed.type == POINTEDTHING_OBJECT) {
			// Right click object

			// Skip if object has been removed
			if(pointed_object->m_removed)
				return;

			/* android bug - too many
							actionstream<<player->getName()<<" right-clicks object "
									<<pointed.object_id<<": "
									<<pointed_object->getDescription()<<std::endl;
			*/

			// Do stuff
			pointed_object->rightClick(playersao);
		} else if(m_script->item_OnPlace(
		              item, playersao, pointed)) {
			// Placement was handled in lua

			// Apply returned ItemStack
			if (playersao->setWieldedItem(item)) {
				SendInventory(playersao);
			}

			stat.add("place", player->getName());
			//stat.add("place_" + item.name, player->getName());
		}

		// If item has node placement prediction, always send the
		// blocks to make sure the client knows what exactly happened
		RemoteClient *client = getClient(peer_id);
		v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
		v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
		if(item.getDefinition(m_itemdef).node_placement_prediction != "") {
			client->SetBlockNotSent(blockpos);
			if(blockpos2 != blockpos) {
				client->SetBlockNotSent(blockpos2);
			}
		} else {
			client->ResendBlockIfOnWire(blockpos);
			if(blockpos2 != blockpos) {
				client->ResendBlockIfOnWire(blockpos2);
			}
		}
		m_env->nodeUpdate(p_under, 5, 0);
	} // action == 3

	/*
		4: use
	*/
	else if(action == 4) {
		ItemStack item = playersao->getWieldedItem();

		actionstream << player->getName() << " uses " << item.name
		             << ", pointing at " << pointed.dump() << std::endl;

		if(m_script->item_OnUse(
		            item, playersao, pointed)) {
			// Apply returned ItemStack
			if (playersao->setWieldedItem(item)) {
				SendInventory(playersao);
			}
			stat.add("use", player->getName());
			stat.add("use_" + item.name, player->getName());
			m_env->nodeUpdate(p_under, 5, 0);
		}

	} // action == 4

	/*
		5: rightclick air
	*/
	else if (action == 5) {
		ItemStack item = playersao->getWieldedItem();

		actionstream << player->getName() << " activates "
		             << item.name << std::endl;

		if (m_script->item_OnSecondaryUse(
		            item, playersao)) {
			if( playersao->setWieldedItem(item)) {
				SendInventory(playersao);
			}
		}
	}


	/*
		Catch invalid actions
	*/
	else {
		infostream << "WARNING: Server: Invalid action "
		           << action << std::endl;
	}

}
Ejemplo n.º 14
0
int RemoteClient::GetNextBlocks (
		ServerEnvironment *env,
		EmergeManager * emerge,
		float dtime,
		double m_uptime,
		std::vector<PrioritySortedBlockTransfer> &dest)
{
	DSTACK(FUNCTION_NAME);

	auto lock = lock_unique_rec();
	if (!lock->owns_lock())
		return 0;

	// Increment timers
	m_nothing_to_send_pause_timer -= dtime;
	m_nearest_unsent_reset_timer += dtime;
	m_time_from_building += dtime;

	if (m_nearest_unsent_reset) {
		m_nearest_unsent_reset = 0;
		m_nearest_unsent_reset_timer = 999;
		m_nothing_to_send_pause_timer = 0;
		m_time_from_building = 999;
	}

	if(m_nothing_to_send_pause_timer >= 0)
		return 0;

	Player *player = env->getPlayer(peer_id);
	// This can happen sometimes; clients and players are not in perfect sync.
	if(player == NULL)
		return 0;

	v3f playerpos = player->getPosition();
	v3f playerspeed = player->getSpeed();
	if(playerspeed.getLength() > 1000.0*BS) //cheater or bug, ignore him
		return 0;
	v3f playerspeeddir(0,0,0);
	if(playerspeed.getLength() > 1.0*BS)
		playerspeeddir = playerspeed / playerspeed.getLength();
	// Predict to next block
	v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;

	v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);

	v3s16 center = getNodeBlockPos(center_nodepos);

	// Camera position and direction
	v3f camera_pos = player->getEyePosition();
	v3f camera_dir = v3f(0,0,1);
	camera_dir.rotateYZBy(player->getPitch());
	camera_dir.rotateXZBy(player->getYaw());

	//infostream<<"camera_dir=("<<camera_dir<<")"<< " camera_pos="<<camera_pos<<std::endl;

	/*
		Get the starting value of the block finder radius.
	*/

	if(m_last_center != center)
	{
		m_last_center = center;
		m_nearest_unsent_reset_timer = 999;
	}

	if (m_last_direction.getDistanceFrom(camera_dir)>0.4) { // 1 = 90deg
		m_last_direction = camera_dir;
		m_nearest_unsent_reset_timer = 999;
	}

	/*infostream<<"m_nearest_unsent_reset_timer="
			<<m_nearest_unsent_reset_timer<<std::endl;*/

	// Reset periodically to workaround for some bugs or stuff
	if(m_nearest_unsent_reset_timer > 120.0)
	{
		m_nearest_unsent_reset_timer = 0;
		m_nearest_unsent_d = 0;
		m_nearest_unsent_reset = 0;
		//infostream<<"Resetting m_nearest_unsent_d for "<<peer_id<<std::endl;
	}

	//s16 last_nearest_unsent_d = m_nearest_unsent_d;
	s16 d_start = m_nearest_unsent_d;

	//infostream<<"d_start="<<d_start<<std::endl;

	static const u16 max_simul_sends_setting = g_settings->getU16
			("max_simultaneous_block_sends_per_client");
	static const u16 max_simul_sends_usually = max_simul_sends_setting;

	/*
		Check the time from last addNode/removeNode.

		Decrease send rate if player is building stuff.
	*/
	static const auto full_block_send_enable_min_time_from_building = g_settings->getFloat("full_block_send_enable_min_time_from_building");
	if(m_time_from_building < full_block_send_enable_min_time_from_building)
	{
		/*
		max_simul_sends_usually
			= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
		*/
		if(d_start<=1)
			d_start=2;
		m_nearest_unsent_reset_timer = 999; //magical number more than ^ other number 120 - need to reset d on next iteration
	}

	/*
		Number of blocks sending + number of blocks selected for sending
	*/
	u32 num_blocks_selected = 0;
	u32 num_blocks_sending = 0;

	/*
		next time d will be continued from the d from which the nearest
		unsent block was found this time.

		This is because not necessarily any of the blocks found this
		time are actually sent.
	*/
	s32 new_nearest_unsent_d = -1;

	static const auto max_block_send_distance = g_settings->getS16("max_block_send_distance");
	s16 full_d_max = max_block_send_distance;
	if (wanted_range) {
		s16 wanted_blocks = wanted_range / MAP_BLOCKSIZE + 1;
		if (wanted_blocks < full_d_max)
			full_d_max = wanted_blocks;
	}

	s16 d_max = full_d_max;
	static const s16 d_max_gen = g_settings->getS16("max_block_generate_distance");

	// Don't loop very much at a time
	s16 max_d_increment_at_time = 10;
	if(d_max > d_start + max_d_increment_at_time)
		d_max = d_start + max_d_increment_at_time;
	/*if(d_max_gen > d_start+2)
		d_max_gen = d_start+2;*/

	//infostream<<"Starting from "<<d_start<<std::endl;

	s32 nearest_emerged_d = -1;
	s32 nearest_emergefull_d = -1;
	s32 nearest_sent_d = -1;
	//bool queue_is_full = false;

	f32 speed_in_blocks = (playerspeed/(MAP_BLOCKSIZE*BS)).getLength();


	int blocks_occlusion_culled = 0;
	static const bool server_occlusion = g_settings->getBool("server_occlusion");
	bool occlusion_culling_enabled = server_occlusion;

	auto cam_pos_nodes = floatToInt(playerpos, BS);

	auto nodemgr = env->getGameDef()->getNodeDefManager();
	MapNode n;
	{
#if !ENABLE_THREADS
		auto lock = env->getServerMap().m_nothread_locker.lock_shared_rec();
#endif
		n = env->getMap().getNodeTry(cam_pos_nodes);
	}

	if(n && nodemgr->get(n).solidness == 2)
		occlusion_culling_enabled = false;

	unordered_map_v3POS<bool> occlude_cache;


	s16 d;
	for(d = d_start; d <= d_max; d++) {
		/*errorstream<<"checking d="<<d<<" for "
				<<server->getPlayerName(peer_id)<<std::endl;*/
		//infostream<<"RemoteClient::SendBlocks(): d="<<d<<" d_start="<<d_start<<" d_max="<<d_max<<" d_max_gen="<<d_max_gen<<std::endl;

		std::vector<v3POS> list;
		if (d > 2 && d == d_start && m_nearest_unsent_reset_timer != 999) { // oops, again magic number from up ^
			list.push_back(v3POS(0,0,0));
		}

		bool can_skip = d > 1;
		// Fast fall/move optimize. speed_in_blocks now limited to 6.4
		if (speed_in_blocks>0.8 && d <= 2) {
			can_skip = false;
			if (d == 0) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*2; ++addn)
					list.push_back(floatToInt(playerspeeddir*addn, 1));
			} else if (d == 1) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*1.5; ++addn) {
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( 0,  0,  1)); // back
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( -1, 0,  0)); // left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( 1,  0,  0)); // right
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( 0,  0, -1)); // front
				}
			} else if (d == 2) {
				for(s16 addn = 0; addn < (speed_in_blocks+1)*1.5; ++addn) {
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( -1, 0,  1)); // back left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( 1,  0,  1)); // left right
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( -1, 0, -1)); // right left
					list.push_back(floatToInt(playerspeeddir*addn, 1) + v3POS( 1,  0, -1)); // front right
				}
			}
		} else {
		/*
			Get the border/face dot coordinates of a "d-radiused"
			box
		*/
			list = FacePositionCache::getFacePositions(d);
		}


		for(auto li=list.begin(); li!=list.end(); ++li)
		{
			v3POS p = *li + center;

			/*
				Send throttling
				- Don't allow too many simultaneous transfers
				- EXCEPT when the blocks are very close

				Also, don't send blocks that are already flying.
			*/

			// Start with the usual maximum
			u16 max_simul_dynamic = max_simul_sends_usually;

			// If block is very close, allow full maximum
			if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
				max_simul_dynamic = max_simul_sends_setting;

			// Don't select too many blocks for sending
			if (num_blocks_selected + num_blocks_sending >= max_simul_dynamic) {
				//queue_is_full = true;
				goto queue_full_break;
			}

			/*
				Do not go over-limit
			*/
			if (blockpos_over_limit(p))
				continue;

			// If this is true, inexistent block will be made from scratch
			bool generate = d <= d_max_gen;

			{
				/*// Limit the generating area vertically to 2/3
				if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
					generate = false;*/

				/* maybe good idea (if not use block culling) but brokes far (25+) area generate by flooding emergequeue with no generate blocks
				// Limit the send area vertically to 1/2
				if(can_skip && abs(p.Y - center.Y) > full_d_max / 2)
					generate = false;
				*/
			}


			//infostream<<"d="<<d<<std::endl;
			/*
				Don't generate or send if not in sight
				FIXME This only works if the client uses a small enough
				FOV setting. The default of 72 degrees is fine.
			*/

			float camera_fov = ((fov+5)*M_PI/180) * 4./3.;
			if(can_skip && isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false)
			{
				continue;
			}

			/*
				Don't send already sent blocks
			*/
			unsigned int block_sent = 0;
			{
				auto lock = m_blocks_sent.lock_shared_rec();
				block_sent = m_blocks_sent.find(p) != m_blocks_sent.end() ? m_blocks_sent.get(p) : 0;
			}
			if(block_sent > 0 && (/* (block_overflow && d>1) || */ block_sent + (d <= 2 ? 1 : d*d*d) > m_uptime)) {
				continue;
			}

			/*
				Check if map has this block
			*/

			MapBlock *block;
			{
#if !ENABLE_THREADS
			auto lock = env->getServerMap().m_nothread_locker.lock_shared_rec();
#endif

			block = env->getMap().getBlockNoCreateNoEx(p);
			}

			bool surely_not_found_on_disk = false;
			bool block_is_invalid = false;
			if(block != NULL)
			{

				if (d > 3 && block->content_only == CONTENT_AIR) {
					continue;
				}

				if (block_sent > 0 && block_sent >= block->m_changed_timestamp) {
					continue;
				}

		if (occlusion_culling_enabled) {
			ScopeProfiler sp(g_profiler, "SMap: Occusion calls");
			//Occlusion culling
			auto cpn = p*MAP_BLOCKSIZE;

			// No occlusion culling when free_move is on and camera is
			// inside ground
			cpn += v3POS(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2);

			float step = 1;
			float stepfac = 1.3;
			float startoff = 5;
			float endoff = -MAP_BLOCKSIZE;
			v3POS spn = cam_pos_nodes + v3POS(0,0,0);
			s16 bs2 = MAP_BLOCKSIZE/2 + 1;
			u32 needed_count = 1;
#if !ENABLE_THREADS
			auto lock = env->getServerMap().m_nothread_locker.lock_shared_rec();
#endif
			//VERY BAD COPYPASTE FROM clientmap.cpp!
			if( d >= 1 &&
				occlusion_culling_enabled &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(0,0,0),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(bs2,bs2,bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(bs2,bs2,-bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(bs2,-bs2,bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(bs2,-bs2,-bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(-bs2,bs2,bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(-bs2,bs2,-bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(-bs2,-bs2,bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
				isOccluded(&env->getMap(), spn, cpn + v3POS(-bs2,-bs2,-bs2),
					step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache)
			)
			{
				//infostream<<" occlusion player="<<cam_pos_nodes<<" d="<<d<<" block="<<cpn<<" total="<<blocks_occlusion_culled<<"/"<<num_blocks_selected<<std::endl;
				g_profiler->add("SMap: Occlusion skip", 1);
				blocks_occlusion_culled++;
				continue;
			}
		}

				// Reset usage timer, this block will be of use in the future.
				block->resetUsageTimer();

				if (block->getLightingExpired()) {
					//env->getServerMap().lighting_modified_blocks.set(p, nullptr);
					env->getServerMap().lighting_modified_add(p, d);
				}

				if (block->lighting_broken > 0 && (block_sent || d > 0))
					continue;

				// Block is valid if lighting is up-to-date and data exists
				if(block->isValid() == false)
				{
					block_is_invalid = true;
				}

				if(block->isGenerated() == false)
				{
					continue;
				}

				/*
					If block is not close, don't send it unless it is near
					ground level.

					Block is near ground level if night-time mesh
					differs from day-time mesh.
				*/
/*
				if(d >= 4)
				{
					if(block->getDayNightDiff() == false)
						continue;
				}
*/
			}

			/*
				If block has been marked to not exist on disk (dummy)
				and generating new ones is not wanted, skip block.
			*/
			if(generate == false && surely_not_found_on_disk == true)
			{
				// get next one.
				continue;
			}

			/*
				Add inexistent block to emerge queue.
			*/
			if(block == NULL || surely_not_found_on_disk || block_is_invalid)
			{
				//infostream<<"start gen d="<<d<<" p="<<p<<" notfound="<<surely_not_found_on_disk<<" invalid="<< block_is_invalid<<" block="<<block<<" generate="<<generate<<std::endl;

				if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
					if (nearest_emerged_d == -1)
						nearest_emerged_d = d;
				} else {
					if (nearest_emergefull_d == -1)
						nearest_emergefull_d = d;
					goto queue_full_break;
				}

				// get next one.
				continue;
			}

			if(nearest_sent_d == -1)
				nearest_sent_d = d;

			/*
				Add block to send queue
			*/

			PrioritySortedBlockTransfer q((float)d, p, peer_id);

			dest.push_back(q);

			num_blocks_selected += 1;
		}
	}
queue_full_break:

	//infostream<<"Stopped at "<<d<<" d_start="<<d_start<< " d_max="<<d_max<<" nearest_emerged_d="<<nearest_emerged_d<<" nearest_emergefull_d="<<nearest_emergefull_d<< " new_nearest_unsent_d="<<new_nearest_unsent_d<< " sel="<<num_blocks_selected<< "+"<<num_blocks_sending << " culled=" << blocks_occlusion_culled <<" cEN="<<occlusion_culling_enabled<<std::endl;
	num_blocks_selected += num_blocks_sending;
	if(!num_blocks_selected && d_start <= d) {
		//new_nearest_unsent_d = 0;
		m_nothing_to_send_pause_timer = 1.0;
	}
		

	// If nothing was found for sending and nothing was queued for
	// emerging, continue next time browsing from here
	if(nearest_emerged_d != -1){
		new_nearest_unsent_d = nearest_emerged_d;
	} else if(nearest_emergefull_d != -1){
		new_nearest_unsent_d = nearest_emergefull_d;
	} else {
		if(d > full_d_max){
			new_nearest_unsent_d = 0;
			m_nothing_to_send_pause_timer = 1.0;
		} else {
			if(nearest_sent_d != -1)
				new_nearest_unsent_d = nearest_sent_d;
			else
				new_nearest_unsent_d = d;
		}
	}

	if(new_nearest_unsent_d != -1)
		m_nearest_unsent_d = new_nearest_unsent_d;
	return num_blocks_selected - num_blocks_sending;
}
Ejemplo n.º 15
0
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: ";

	bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
	bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
	bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");

	/*
		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 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
	*/

	{
	//ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);

	MeshBufListList drawbufs;

	for(auto & ir : *m_drawlist) {
		MapBlock *block = ir.second;

		int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes).getDistanceFrom(block->getPos()));
		// If the mesh of the block happened to get deleted, ignore it
		if(block->getMesh(mesh_step) == 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->getMesh(mesh_step);
			assert(mapBlockMesh);

			mapBlockMesh->updateCameraOffset(m_camera_offset);

			// 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->getMesh(mesh_step);
			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);

				buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
				buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
				buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);

				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);
				}
			}
		}
	}
	
	std::list<MeshBufList> &lists = drawbufs.lists;
	
	int timecheck_counter = 0;
	for(std::list<MeshBufList>::iterator i = lists.begin();
			i != lists.end(); ++i)
	{
		{
			timecheck_counter++;
			if(timecheck_counter > 50)
			{
				timecheck_counter = 0;
				int time2 = time(0);
				if(time2 > time1 + 4)
				{
					infostream<<"ClientMap::renderMap(): "
						"Rendering takes ages, returning."
						<<std::endl;
					return;
				}
			}
		}

		MeshBufList &list = *i;
		
		driver->setMaterial(list.m);
		
		for(std::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
		*/
		{
			//JMutexAutoLock lock(block->mesh_mutex);

			MapBlockMesh *mapBlockMesh = block->mesh;
			assert(mapBlockMesh);

			scene::SMesh *mesh = mapBlockMesh->getMesh();
			assert(mesh);

			u32 c = mesh->getMeshBufferCount();
			bool stuff_actually_drawn = false;
			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());
				// Render transparent on transparent pass and likewise.
				if(transparent == is_transparent_pass)
				{
					if(buf->getVertexCount() == 0)
						errorstream<<"Block ["<<analyze_block(block)
								<<"] contains an empty meshbuf"<<std::endl;
					/*
						This *shouldn't* hurt too much because Irrlicht
						doesn't change opengl textures if the old
						material has the same texture.
					*/
					driver->setMaterial(buf->getMaterial());
					driver->drawMeshBuffer(buf);
					vertex_count += buf->getVertexCount();
					meshbuffer_count++;
					stuff_actually_drawn = true;
				}
			}
			if(stuff_actually_drawn)
				blocks_had_pass_meshbuf++;
			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: 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);

	/*infostream<<"renderMap(): is_transparent_pass="******", rendered "<<vertex_count<<" vertices."<<std::endl;*/
}
void Server::ProcessData(NetworkPacket *pkt)
{
	DSTACK(__FUNCTION_NAME);
	// Environment is locked first.
	//JMutexAutoLock envlock(m_env_mutex);

	ScopeProfiler sp(g_profiler, "Server::ProcessData");

	auto peer_id = pkt->getPeerId();

	std::string addr_s;
	try{
		Address address = getPeerAddress(peer_id);
		addr_s = address.serializeString();

		// drop player if is ip is banned
		if(m_banmanager->isIpBanned(addr_s)){
			std::string ban_name = m_banmanager->getBanName(addr_s);
			infostream<<"Server: A banned client tried to connect from "
					<<addr_s<<"; banned name was "
					<<ban_name<<std::endl;
			// This actually doesn't seem to transfer to the client
			DenyAccess(peer_id, std::string("Your ip is banned. Banned name was ") + ban_name);
			return;
		}
	}
	catch(con::PeerNotFoundException &e)
	{
		/*
		 * no peer for this packet found
		 * most common reason is peer timeout, e.g. peer didn't
		 * respond for some time, your server was overloaded or
		 * things like that.
		 */
		verbosestream<<"Server::ProcessData(): Canceling: peer "
				<<peer_id<<" not found"<<std::endl;
		return;
	}

	try
	{

	auto datasize = pkt->getSize();

	if(datasize < 2)
		return;

	int command;
	std::map<int, msgpack::object> packet;
	msgpack::unpacked msg;
	if (!con::parse_msgpack_packet(pkt->getString(0), datasize, &packet, &command, &msg)) {
		verbosestream<<"Server: Ignoring broken packet from " <<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl;
		return;
	}

	if(command == TOSERVER_INIT_LEGACY)
	{
		RemoteClient* client = getClient(peer_id, CS_Created);

		// If net_proto_version is set, this client has already been handled
		if(client->getState() > CS_Created)
		{
			verbosestream<<"Server: Ignoring multiple TOSERVER_INITs from "
					<<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl;
			return;
		}

		verbosestream<<"Server: Got TOSERVER_INIT from "<<addr_s<<" (peer_id="
				<<peer_id<<")"<<std::endl;

		// Do not allow multiple players in simple singleplayer mode.
		// This isn't a perfect way to do it, but will suffice for now
		if(m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1){
			infostream<<"Server: Not allowing another client ("<<addr_s
					<<") to connect in simple singleplayer mode"<<std::endl;
			DenyAccess(peer_id, "Running in simple singleplayer mode.");
			return;
		}

		// First byte after command is maximum supported
		// serialization version
		u8 client_max;
		packet[TOSERVER_INIT_FMT].convert(&client_max);
		u8 our_max = SER_FMT_VER_HIGHEST_READ;
		// Use the highest version supported by both
		int deployed = std::min(client_max, our_max);
		// If it's lower than the lowest supported, give up.
		if(deployed < SER_FMT_CLIENT_VER_LOWEST)
			deployed = SER_FMT_VER_INVALID;

		if(deployed == SER_FMT_VER_INVALID)
		{
			actionstream<<"Server: A mismatched client tried to connect from "
					<<addr_s<<std::endl;
			infostream<<"Server: Cannot negotiate serialization version with "
					<<addr_s<<std::endl;
			DenyAccess(peer_id, std::string(
					"Your client's version is not supported.\n"
					"Server version is ")
					+ (g_version_string) + "."
			);
			return;
		}

		client->setPendingSerializationVersion(deployed);

		/*
			Read and check network protocol version
		*/

		u16 min_net_proto_version = 0;
		packet[TOSERVER_INIT_PROTOCOL_VERSION_MIN].convert(&min_net_proto_version);
		u16 max_net_proto_version = min_net_proto_version;
		packet[TOSERVER_INIT_PROTOCOL_VERSION_MAX].convert(&max_net_proto_version);

		// Start with client's maximum version
		u16 net_proto_version = max_net_proto_version;

		// Figure out a working version if it is possible at all
		if(max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN ||
				min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX)
		{
			// If maximum is larger than our maximum, go with our maximum
			if(max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX)
				net_proto_version = SERVER_PROTOCOL_VERSION_MAX;
			// Else go with client's maximum
			else
				net_proto_version = max_net_proto_version;
		}

		verbosestream<<"Server: "<<addr_s<<": Protocol version: min: "
				<<min_net_proto_version<<", max: "<<max_net_proto_version
				<<", chosen: "<<net_proto_version<<std::endl;

		client->net_proto_version = net_proto_version;

		if(net_proto_version < SERVER_PROTOCOL_VERSION_MIN ||
				net_proto_version > SERVER_PROTOCOL_VERSION_MAX)
		{
			actionstream<<"Server: A mismatched client tried to connect from "
					<<addr_s<<std::endl;
			DenyAccess(peer_id, std::string(
					"Your client's version is not supported.\n"
					"Server version is ")
					+ (g_version_string) + ",\n"
					+ "server's PROTOCOL_VERSION is "
					+ itos(SERVER_PROTOCOL_VERSION_MIN)
					+ "..."
					+ itos(SERVER_PROTOCOL_VERSION_MAX)
					+ ", client's PROTOCOL_VERSION is "
					+ itos(min_net_proto_version)
					+ "..."
					+ itos(max_net_proto_version)
			);
			return;
		}

		if(g_settings->getBool("strict_protocol_version_checking"))
		{
			if(net_proto_version != LATEST_PROTOCOL_VERSION)
			{
				actionstream<<"Server: A mismatched (strict) client tried to "
						<<"connect from "<<addr_s<<std::endl;
				DenyAccess(peer_id, std::string(
						"Your client's version is not supported.\n"
						"Server version is ")
						+ (g_version_string) + ",\n"
						+ "server's PROTOCOL_VERSION (strict) is "
						+ itos(LATEST_PROTOCOL_VERSION)
						+ ", client's PROTOCOL_VERSION is "
						+ itos(min_net_proto_version)
						+ "..."
						+ itos(max_net_proto_version)
				);
				return;
			}
		}

		/*
			Set up player
		*/

		// Get player name
		std::string playername;
		packet[TOSERVER_INIT_NAME].convert(&playername);

		if(playername.empty())
		{
			actionstream<<"Server: Player with an empty name "
					<<"tried to connect from "<<addr_s<<std::endl;
			DenyAccess(peer_id, "Empty name");
			return;
		}

		if(!g_settings->getBool("enable_any_name") && string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false)
		{
			actionstream<<"Server: Player with an invalid name ["<<playername
					<<"] tried to connect from "<<addr_s<<std::endl;
			DenyAccess(peer_id, "Name contains unallowed characters");
			return;
		}

		if(!isSingleplayer() && playername == "singleplayer")
		{
			actionstream<<"Server: Player with the name \"singleplayer\" "
					<<"tried to connect from "<<addr_s<<std::endl;
			DenyAccess(peer_id, "Name is not allowed");
			return;
		}

		{
			std::string reason;
			if(m_script->on_prejoinplayer(playername, addr_s, reason))
			{
				actionstream<<"Server: Player with the name \""<<playername<<"\" "
						<<"tried to connect from "<<addr_s<<" "
						<<"but it was disallowed for the following reason: "
						<<reason<<std::endl;
				DenyAccess(peer_id, reason);
				return;
			}
		}

		infostream<<"Server: New connection: \""<<playername<<"\" from "
				<<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl;

		// Get password
		std::string given_password;
		packet[TOSERVER_INIT_PASSWORD].convert(&given_password);

		if(!base64_is_valid(given_password.c_str())){
			actionstream<<"Server: "<<playername
					<<" supplied invalid password hash"<<std::endl;
			DenyAccess(peer_id, "Invalid password hash");
			return;
		}

		// Enforce user limit.
		// Don't enforce for users that have some admin right
		if(m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") &&
				!checkPriv(playername, "server") &&
				!checkPriv(playername, "ban") &&
				!checkPriv(playername, "privs") &&
				!checkPriv(playername, "password") &&
				playername != g_settings->get("name"))
		{
			actionstream<<"Server: "<<playername<<" tried to join, but there"
					<<" are already max_users="
					<<g_settings->getU16("max_users")<<" players."<<std::endl;
			DenyAccess(peer_id, "Too many users.");
			return;
		}

		std::string checkpwd; // Password hash to check against
		bool has_auth = m_script->getAuth(playername, &checkpwd, NULL);

		// If no authentication info exists for user, create it
		if(!has_auth){
			if(!isSingleplayer() &&
					g_settings->getBool("disallow_empty_password") &&
					given_password == ""){
				actionstream<<"Server: "<<playername
						<<" supplied empty password"<<std::endl;
				DenyAccess(peer_id, "Empty passwords are "
						"disallowed. Set a password and try again.");
				return;
			}
			std::string raw_default_password = g_settings->get("default_password");
			std::string initial_password =
				translatePassword(playername, raw_default_password);

			// If default_password is empty, allow any initial password
			if (raw_default_password.length() == 0)
				initial_password = given_password;

			m_script->createAuth(playername, initial_password);
		}

		has_auth = m_script->getAuth(playername, &checkpwd, NULL);

		if(!has_auth){
			actionstream<<"Server: "<<playername<<" cannot be authenticated"
					<<" (auth handler does not work?)"<<std::endl;
			DenyAccess(peer_id, "Not allowed to login");
			return;
		}

		if(given_password != checkpwd){
			actionstream<<"Server: "<<playername<<" supplied wrong password"
					<<std::endl;
			DenyAccess(peer_id, "Wrong password");
			return;
		}

		RemotePlayer *player =
				static_cast<RemotePlayer*>(m_env->getPlayer(playername.c_str()));

		if(player && player->peer_id != 0){
			errorstream<<"Server: "<<playername<<": Failed to emerge player"
					<<" (player allocated to an another client)"<<std::endl;
			DenyAccess(peer_id, "Another client is connected with this "
					"name. If your client closed unexpectedly, try again in "
					"a minute.");
		}

		m_clients.setPlayerName(peer_id,playername);

		/*
			Answer with a TOCLIENT_INIT
		*/
		{
			MSGPACK_PACKET_INIT(TOCLIENT_INIT, 4);
			PACK(TOCLIENT_INIT_DEPLOYED, deployed);
			PACK(TOCLIENT_INIT_SEED, m_env->getServerMap().getSeed());
			PACK(TOCLIENT_INIT_STEP, g_settings->getFloat("dedicated_server_step"));

			//if (player) //todo : remake me
			//	PACK(TOCLIENT_INIT_POS, player->getPosition());

			Settings params;
			m_emerge->params.save(params);
			PACK(TOCLIENT_INIT_MAP_PARAMS, params);

			// Send as reliable
			m_clients.send(peer_id, 0, buffer, true);
			m_clients.event(peer_id, CSE_Init);
		}

		return;
	}

	if(command == TOSERVER_INIT2)
	{
		verbosestream<<"Server: Got TOSERVER_INIT2 from "
				<<peer_id<<std::endl;

		m_clients.event(peer_id, CSE_GotInit2);
		u16 protocol_version = m_clients.getProtocolVersion(peer_id);


		///// begin compatibility code
		PlayerSAO* playersao = NULL;
		if (protocol_version <= 22) {
			playersao = StageTwoClientInit(peer_id);

			if (playersao == NULL) {
				errorstream
					<< "TOSERVER_INIT2 stage 2 client init failed for peer "
					<< peer_id << std::endl;
				return;
			}
		}
		///// end compatibility code

		/*
			Send some initialization data
		*/

		infostream<<"Server: Sending content to "
				<<getPlayerName(peer_id)<<std::endl;

		// Send player movement settings
		SendMovement(peer_id);

		// Send item definitions
		SendItemDef(peer_id, m_itemdef, protocol_version);

		// Send node definitions
		SendNodeDef(peer_id, m_nodedef, protocol_version);

		m_clients.event(peer_id, CSE_SetDefinitionsSent);

		// Send media announcement
		sendMediaAnnouncement(peer_id);

		// Send detached inventories
		sendDetachedInventories(peer_id);

		// Send time of day
		u16 time = m_env->getTimeOfDay();
		float time_speed = g_settings->getFloat("time_speed");
		SendTimeOfDay(peer_id, time, time_speed);

		///// begin compatibility code
		if (protocol_version <= 22) {
			m_clients.event(peer_id, CSE_SetClientReady);
			m_script->on_joinplayer(playersao);
		}
		///// end compatibility code

		// Warnings about protocol version can be issued here
		if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION)
		{
			SendChatMessage(peer_id, "# Server: WARNING: YOUR CLIENT'S "
					"VERSION MAY NOT BE FULLY COMPATIBLE WITH THIS SERVER!");
		}

		return;
	}

	u8 peer_ser_ver = getClient(peer_id, CS_InitDone)->serialization_version;
	u16 peer_proto_ver = getClient(peer_id, CS_InitDone)->net_proto_version;

	if(peer_ser_ver == SER_FMT_VER_INVALID)
	{
		errorstream<<"Server::ProcessData(): Canceling: Peer"
				" serialization format invalid or not initialized."
				" Skipping incoming command="<<command<<std::endl;
		return;
	}

	/* Handle commands relate to client startup */
	if(command == TOSERVER_REQUEST_MEDIA) {
		std::vector<std::string> tosend;
		packet[TOSERVER_REQUEST_MEDIA_FILES].convert(&tosend);

		sendRequestedMedia(peer_id, tosend);
		return;
	}
	else if(command == TOSERVER_RECEIVED_MEDIA) {
		return;
	}
	else if(command == TOSERVER_CLIENT_READY) {
		// clients <= protocol version 22 did not send ready message,
		// they're already initialized
		if (peer_proto_ver <= 22) {
			infostream << "Client sent message not expected by a "
				<< "client using protocol version <= 22,"
				<< "disconnecting peer_id: " << peer_id << std::endl;
			m_con.DisconnectPeer(peer_id);
			return;
		}

		PlayerSAO* playersao = StageTwoClientInit(peer_id);

		// If failed, cancel
		if (playersao == NULL) {
			errorstream
				<< "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: "
				<< peer_id << std::endl;
			m_con.DisconnectPeer(peer_id);
			return;
		}
		m_clients.setClientVersion(
			peer_id,
			packet[TOSERVER_CLIENT_READY_VERSION_MAJOR].as<int>(),
			packet[TOSERVER_CLIENT_READY_VERSION_MINOR].as<int>(),
			0, // packet[TOSERVER_CLIENT_READY_VERSION_PATCH].as<int>(), TODO
			packet[TOSERVER_CLIENT_READY_VERSION_STRING].as<std::string>()
		);
		m_clients.event(peer_id, CSE_SetClientReady);
		m_script->on_joinplayer(playersao);

		stat.add("join", playersao->getPlayer()->getName());
	}

	if (m_clients.getClientState(peer_id) < CS_Active)
	{
		if (command == TOSERVER_PLAYERPOS) return;

		errorstream<<"Got packet command: " << command << " for peer id "
				<< peer_id << " but client isn't active yet. Dropping packet "
				<<std::endl;
		return;
	}

	Player *player = m_env->getPlayer(peer_id);
	if(player == NULL) {
/*
		verbosestream<<"Server::ProcessData(): Canceling: "
				"No player for peer_id="<<peer_id
				<< " disconnecting peer!" <<std::endl;
*/
		m_con.DisconnectPeer(peer_id);
		return;
	}

	PlayerSAO *playersao = player->getPlayerSAO();
	if(playersao == NULL) {
		errorstream<<"Server::ProcessData(): Canceling: "
				"No player object for peer_id="<<peer_id
				<< " disconnecting peer!" <<std::endl;
		m_con.DisconnectPeer(peer_id);
		return;
	}

	if(command == TOSERVER_PLAYERPOS)
	{
	// If player is dead we don't care of this packet

		if (player->hp != 0 && playersao->m_ms_from_last_respawn > 1000)
		player->setPosition(packet[TOSERVER_PLAYERPOS_POSITION].as<v3f>());
		player->setSpeed(packet[TOSERVER_PLAYERPOS_SPEED].as<v3f>());
		player->setPitch(modulo360f(packet[TOSERVER_PLAYERPOS_PITCH].as<f32>()));
		player->setYaw(modulo360f(packet[TOSERVER_PLAYERPOS_YAW].as<f32>()));
		u32 keyPressed = packet[TOSERVER_PLAYERPOS_KEY_PRESSED].as<u32>();
		player->keyPressed = keyPressed;
		player->control.up = (bool)(keyPressed&1);
		player->control.down = (bool)(keyPressed&2);
		player->control.left = (bool)(keyPressed&4);
		player->control.right = (bool)(keyPressed&8);
		player->control.jump = (bool)(keyPressed&16);
		player->control.aux1 = (bool)(keyPressed&32);
		player->control.sneak = (bool)(keyPressed&64);
		player->control.LMB = (bool)(keyPressed&128);
		player->control.RMB = (bool)(keyPressed&256);

		auto old_pos = playersao->m_last_good_position;
		if(playersao->checkMovementCheat()){
			// Call callbacks
			m_script->on_cheat(playersao, "moved_too_fast");
			SendMovePlayer(peer_id);
		}
		else if (playersao->m_ms_from_last_respawn > 3000) {
			auto dist = (old_pos/BS).getDistanceFrom(playersao->m_last_good_position/BS);
			if (dist)
				stat.add("move", playersao->getPlayer()->getName(), dist);
		}

		if (playersao->m_ms_from_last_respawn > 2000) {
			auto obj = playersao; // copypasted from server step:
			auto uptime = m_uptime.get();
			if (!obj->m_uptime_last)  // not very good place, but minimum modifications
				obj->m_uptime_last = uptime - 0.1;
			if (uptime - obj->m_uptime_last > 0.5) {
				obj->step(uptime - obj->m_uptime_last, true); //todo: maybe limit count per time
				obj->m_uptime_last = uptime;
			}
		}

		/*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
															<<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
															<<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
	}
	else if(command == TOSERVER_DELETEDBLOCKS)
	{
		std::vector<v3s16> deleted_blocks;
		packet[TOSERVER_DELETEDBLOCKS_DATA].convert(&deleted_blocks);
		RemoteClient *client = getClient(peer_id);
		for (auto &block : deleted_blocks)
			client->SetBlockDeleted(block);
	}
	else if(command == TOSERVER_INVENTORY_ACTION)
	{
		std::string datastring;
		packet[TOSERVER_INVENTORY_ACTION_DATA].convert(&datastring);
		std::istringstream is(datastring, std::ios_base::binary);
		// Create an action
		InventoryAction *a = InventoryAction::deSerialize(is);
		if(a == NULL)
		{
			infostream<<"TOSERVER_INVENTORY_ACTION: "
					<<"InventoryAction::deSerialize() returned NULL"
					<<std::endl;
			return;
		}

		// If something goes wrong, this player is to blame
		RollbackScopeActor rollback_scope(m_rollback,
				std::string("player:")+player->getName());

		/*
			Note: Always set inventory not sent, to repair cases
			where the client made a bad prediction.
		*/

		/*
			Handle restrictions and special cases of the move action
		*/
		if(a->getType() == IACTION_MOVE)
		{
			IMoveAction *ma = (IMoveAction*)a;

			ma->from_inv.applyCurrentPlayer(player->getName());
			ma->to_inv.applyCurrentPlayer(player->getName());

			setInventoryModified(ma->from_inv, false);
			setInventoryModified(ma->to_inv, false);

			bool from_inv_is_current_player =
				(ma->from_inv.type == InventoryLocation::PLAYER) &&
				(ma->from_inv.name == player->getName());

			bool to_inv_is_current_player =
				(ma->to_inv.type == InventoryLocation::PLAYER) &&
				(ma->to_inv.name == player->getName());

			/*
				Disable moving items out of craftpreview
			*/
			if(ma->from_list == "craftpreview")
			{
				infostream<<"Ignoring IMoveAction from "
						<<(ma->from_inv.dump())<<":"<<ma->from_list
						<<" to "<<(ma->to_inv.dump())<<":"<<ma->to_list
						<<" because src is "<<ma->from_list<<std::endl;
				delete a;
				return;
			}

			/*
				Disable moving items into craftresult and craftpreview
			*/
			if(ma->to_list == "craftpreview" || ma->to_list == "craftresult")
			{
				infostream<<"Ignoring IMoveAction from "
						<<(ma->from_inv.dump())<<":"<<ma->from_list
						<<" to "<<(ma->to_inv.dump())<<":"<<ma->to_list
						<<" because dst is "<<ma->to_list<<std::endl;
				delete a;
				return;
			}

			// Disallow moving items in elsewhere than player's inventory
			// if not allowed to interact
			if(!checkPriv(player->getName(), "interact") &&
					(!from_inv_is_current_player ||
					!to_inv_is_current_player))
			{
				infostream<<"Cannot move outside of player's inventory: "
						<<"No interact privilege"<<std::endl;
				delete a;
				return;
			}
		}
		/*
			Handle restrictions and special cases of the drop action
		*/
		else if(a->getType() == IACTION_DROP)
		{
			IDropAction *da = (IDropAction*)a;

			da->from_inv.applyCurrentPlayer(player->getName());

			setInventoryModified(da->from_inv, false);

			/*
				Disable dropping items out of craftpreview
			*/
			if(da->from_list == "craftpreview")
			{
				infostream<<"Ignoring IDropAction from "
						<<(da->from_inv.dump())<<":"<<da->from_list
						<<" because src is "<<da->from_list<<std::endl;
				delete a;
				return;
			}

			// Disallow dropping items if not allowed to interact
			if(!checkPriv(player->getName(), "interact"))
			{
				delete a;
				return;
			}
			stat.add("drop", player->getName());
		}
		/*
			Handle restrictions and special cases of the craft action
		*/
		else if(a->getType() == IACTION_CRAFT)
		{
			ICraftAction *ca = (ICraftAction*)a;

			ca->craft_inv.applyCurrentPlayer(player->getName());

			setInventoryModified(ca->craft_inv, false);

			//bool craft_inv_is_current_player =
			//	(ca->craft_inv.type == InventoryLocation::PLAYER) &&
			//	(ca->craft_inv.name == player->getName());

			// Disallow crafting if not allowed to interact
			if(!checkPriv(player->getName(), "interact"))
			{
				infostream<<"Cannot craft: "
						<<"No interact privilege"<<std::endl;
				delete a;
				return;
			}
			stat.add("craft", player->getName());
		}

		// Do the action
		a->apply(this, playersao, this);
		// Eat the action
		delete a;

		SendInventory(playersao);

	}
	else if(command == TOSERVER_CHAT_MESSAGE)
	{
		std::string message = packet[TOSERVER_CHAT_MESSAGE_DATA].as<std::string>();

		// If something goes wrong, this player is to blame
		RollbackScopeActor rollback_scope(m_rollback,
				std::string("player:")+player->getName());

		// Get player name of this client
		std::string name = player->getName();

		// Run script hook
		bool ate = m_script->on_chat_message(player->getName(), message);
		// If script ate the message, don't proceed
		if(ate)
			return;

		// Line to send to players
		std::string line;
		// Whether to send to other players
		bool send_to_others = false;

		// Commands are implemented in Lua, so only catch invalid
		// commands that were not "eaten" and send an error back
		if(message[0] == '/')
		{
			message = message.substr(1);
			if(message.length() == 0)
				line += "-!- Empty command";
			else
				// TODO: str_split(message, ' ')[0]
				line += "-!- Invalid command: " + message;
		}
		else
		{
			if(checkPriv(player->getName(), "shout")){
				line += "<";
				line += name;
				line += "> ";
				line += message;
				send_to_others = true;
			} else
				line += "-!- You don't have permission to shout.";
		}

		if(!line.empty())
		{
			if(send_to_others) {
				stat.add("chat", player->getName());
				actionstream<<"CHAT: "<<line<<std::endl;
				SendChatMessage(PEER_ID_INEXISTENT, line);
			} else
				SendChatMessage(peer_id, line);
		}
	}
	else if(command == TOSERVER_DAMAGE)
	{
		u8 damage = packet[TOSERVER_DAMAGE_VALUE].as<u8>();

		if(g_settings->getBool("enable_damage"))
		{
			actionstream<<player->getName()<<" damaged by "
					<<(int)damage<<" hp at "<<PP(player->getPosition()/BS)
					<<std::endl;

			playersao->setHP(playersao->getHP() - damage);

			SendPlayerHPOrDie(playersao->getPeerID(), playersao->getHP() == 0);

			stat.add("damage", player->getName(), damage);
		}
	}
	else if(command == TOSERVER_BREATH)
	{

	/*
	 * If player is dead, we don't need to update the breath
	 * He is dead !
	 */
	if (!player->isDead()) {
		playersao->setBreath(packet[TOSERVER_BREATH_VALUE].as<u16>());
		SendPlayerBreath(peer_id);
	}
	}
	else if(command == TOSERVER_CHANGE_PASSWORD)
	{
		std::string oldpwd, newpwd;
		packet[TOSERVER_CHANGE_PASSWORD_OLD].convert(&oldpwd);
		packet[TOSERVER_CHANGE_PASSWORD_NEW].convert(&newpwd);

		if(!base64_is_valid(newpwd)){
			infostream<<"Server: "<<player->getName()<<" supplied invalid password hash"<<std::endl;
			// Wrong old password supplied!!
			SendChatMessage(peer_id, "Invalid new password hash supplied. Password NOT changed.");
			return;
		}

		infostream<<"Server: Client requests a password change from "
				<<"'"<<oldpwd<<"' to '"<<newpwd<<"'"<<std::endl;

		std::string playername = player->getName();

		std::string checkpwd;
		m_script->getAuth(playername, &checkpwd, NULL);

		if(oldpwd != checkpwd)
		{
			infostream<<"Server: invalid old password"<<std::endl;
			// Wrong old password supplied!!
			SendChatMessage(peer_id, "Invalid old password supplied. Password NOT changed.");
			return;
		}

		bool success = m_script->setPassword(playername, newpwd);
		if(success){
			actionstream<<player->getName()<<" changes password"<<std::endl;
			SendChatMessage(peer_id, "Password change successful.");
		} else {
			actionstream<<player->getName()<<" tries to change password but "
					<<"it fails"<<std::endl;
			SendChatMessage(peer_id, "Password change failed or inavailable.");
		}
	}
	else if(command == TOSERVER_PLAYERITEM)
	{
		u16 item = packet[TOSERVER_PLAYERITEM_VALUE].as<u16>();
		playersao->setWieldIndex(item);
	}
	else if(command == TOSERVER_RESPAWN)
	{
		if(!player->isDead())
			return;

		RespawnPlayer(peer_id);

		actionstream << player->getName() << " respawns at "
				<< PP(player->getPosition()/BS) << std::endl;

		// ActiveObject is added to environment in AsyncRunStep after
		// the previous addition has been successfully removed
	}
	else if(command == TOSERVER_INTERACT)
	{
		u8 action;
		u16 item_i;
		PointedThing pointed;

		packet[TOSERVER_INTERACT_ACTION].convert(&action);
		packet[TOSERVER_INTERACT_ITEM].convert(&item_i);
		packet[TOSERVER_INTERACT_POINTED_THING].convert(&pointed);

		if(player->hp == 0)
		{
			verbosestream<<"TOSERVER_INTERACT: "<<player->getName()
				<<" tried to interact, but is dead!"<<std::endl;
			return;
		}

		v3f player_pos = playersao->getLastGoodPosition();

		// Update wielded item
		playersao->setWieldIndex(item_i);

		// Get pointed to node (undefined if not POINTEDTYPE_NODE)
		v3s16 p_under = pointed.node_undersurface;
		v3s16 p_above = pointed.node_abovesurface;

		// Get pointed to object (NULL if not POINTEDTYPE_OBJECT)
		ServerActiveObject *pointed_object = NULL;
		if(pointed.type == POINTEDTHING_OBJECT)
		{
			pointed_object = m_env->getActiveObject(pointed.object_id);
			if(pointed_object == NULL)
			{
				verbosestream<<"TOSERVER_INTERACT: "
					"pointed object is NULL"<<std::endl;
				return;
			}

		}

		v3f pointed_pos_under = player_pos;
		v3f pointed_pos_above = player_pos;
		if(pointed.type == POINTEDTHING_NODE)
		{
			pointed_pos_under = intToFloat(p_under, BS);
			pointed_pos_above = intToFloat(p_above, BS);
		}
		else if(pointed.type == POINTEDTHING_OBJECT)
		{
			pointed_pos_under = pointed_object->getBasePosition();
			pointed_pos_above = pointed_pos_under;
		}

		/*
			Check that target is reasonably close
			(only when digging or placing things)
		*/
		if(action == 0 || action == 2 || action == 3)
		{
			float d = player_pos.getDistanceFrom(pointed_pos_under);
			float max_d = BS * 14; // Just some large enough value
			if(d > max_d){
				actionstream<<"Player "<<player->getName()
						<<" tried to access "<<pointed.dump()
						<<" from too far: "
						<<"d="<<d<<", max_d="<<max_d
						<<". ignoring."<<std::endl;
				// Re-send block to revert change on client-side
				RemoteClient *client = getClient(peer_id);
				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
				client->SetBlockNotSent(blockpos);
				// Call callbacks
				m_script->on_cheat(playersao, "interacted_too_far");
				// Do nothing else
				return;
			}
		}

		/*
			Make sure the player is allowed to do it
		*/
		if(!checkPriv(player->getName(), "interact"))
		{
			actionstream<<player->getName()<<" attempted to interact with "
					<<pointed.dump()<<" without 'interact' privilege"
					<<std::endl;
			// Re-send block to revert change on client-side
			RemoteClient *client = getClient(peer_id);
			// Digging completed -> under
			if(action == 2){
				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
				client->SetBlockNotSent(blockpos);
			}
			// Placement -> above
			if(action == 3){
				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
				client->SetBlockNotSent(blockpos);
			}
			stat.add("interact_denied", player->getName());
			return;
		}

		/*
			If something goes wrong, this player is to blame
		*/
		RollbackScopeActor rollback_scope(m_rollback,
				std::string("player:")+player->getName());

		/*
			0: start digging or punch object
		*/
		if(action == 0)
		{
			if(pointed.type == POINTEDTHING_NODE)
			{
				/*
					NOTE: This can be used in the future to check if
					somebody is cheating, by checking the timing.
				*/
				MapNode n(CONTENT_IGNORE);
				bool pos_ok;
				n = m_env->getMap().getNodeNoEx(p_under, &pos_ok);
				if (pos_ok)
					n = m_env->getMap().getNodeNoEx(p_under, &pos_ok);

				if (!pos_ok) {
					infostream<<"Server: Not punching: Node not found."
							<<" Adding block to emerge queue."
							<<std::endl;
					m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false);
				}

				if(n.getContent() != CONTENT_IGNORE)
					m_script->node_on_punch(p_under, n, playersao, pointed);
				// Cheat prevention
				playersao->noCheatDigStart(p_under);
			}
			else if(pointed.type == POINTEDTHING_OBJECT)
			{
				// Skip if object has been removed
				if(pointed_object->m_removed)
					return;

				actionstream<<player->getName()<<" punches object "
						<<pointed.object_id<<": "
						<<pointed_object->getDescription()<<std::endl;

				ItemStack punchitem = playersao->getWieldedItem();
				ToolCapabilities toolcap =
						punchitem.getToolCapabilities(m_itemdef);
				v3f dir = (pointed_object->getBasePosition() -
						(player->getPosition() + player->getEyeOffset())
							).normalize();
				float time_from_last_punch =
					playersao->resetTimeFromLastPunch();

			s16 src_original_hp = pointed_object->getHP();
			s16 dst_origin_hp = playersao->getHP();

				pointed_object->punch(dir, &toolcap, playersao,
						time_from_last_punch);

			// If the object is a player and its HP changed
			if (src_original_hp != pointed_object->getHP() &&
					pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
				SendPlayerHPOrDie(((PlayerSAO*)pointed_object)->getPeerID(),
						pointed_object->getHP() == 0);
			}

			// If the puncher is a player and its HP changed
			if (dst_origin_hp != playersao->getHP()) {
				SendPlayerHPOrDie(playersao->getPeerID(), playersao->getHP() == 0);
			}

				stat.add("punch", player->getName());
			}

		} // action == 0

		/*
			1: stop digging
		*/
		else if(action == 1)
		{
		} // action == 1

		/*
			2: Digging completed
		*/
		else if(action == 2)
		{
			// Only digging of nodes
			if(pointed.type == POINTEDTHING_NODE)
			{
				bool pos_ok;
				MapNode n = m_env->getMap().getNodeNoEx(p_under, &pos_ok);
				if (!pos_ok) {
					infostream << "Server: Not finishing digging: Node not found."
					           << " Adding block to emerge queue."
					           << std::endl;
					m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false);
				}

				/* Cheat prevention */
				bool is_valid_dig = true;
				if(!isSingleplayer() && !g_settings->getBool("disable_anticheat"))
				{
					v3s16 nocheat_p = playersao->getNoCheatDigPos();
					float nocheat_t = playersao->getNoCheatDigTime();
					playersao->noCheatDigEnd();
					// If player didn't start digging this, ignore dig
					if(nocheat_p != p_under){
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" started digging "
								<<PP(nocheat_p)<<" and completed digging "
								<<PP(p_under)<<"; not digging."<<std::endl;
						is_valid_dig = false;
						// Call callbacks
						m_script->on_cheat(playersao, "finished_unknown_dig");
					}
					// Get player's wielded item
					ItemStack playeritem;
					InventoryList *mlist = playersao->getInventory()->getList("main");
					if(mlist != NULL)
						playeritem = mlist->getItem(playersao->getWieldIndex());
					ToolCapabilities playeritem_toolcap =
							playeritem.getToolCapabilities(m_itemdef);
					// Get diggability and expected digging time
					DigParams params = getDigParams(m_nodedef->get(n).groups,
							&playeritem_toolcap);
					// If can't dig, try hand
					if(!params.diggable){
						const ItemDefinition &hand = m_itemdef->get("");
						const ToolCapabilities *tp = hand.tool_capabilities;
						if(tp)
							params = getDigParams(m_nodedef->get(n).groups, tp);
					}
					// If can't dig, ignore dig
					if(!params.diggable){
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" completed digging "<<PP(p_under)
								<<", which is not diggable with tool. not digging."
								<<std::endl;
						is_valid_dig = false;
						// Call callbacks
						m_script->on_cheat(playersao, "dug_unbreakable");
					}
					// Check digging time
					// If already invalidated, we don't have to
					if(!is_valid_dig){
						// Well not our problem then
					}
					// Clean and long dig
					else if(params.time > 2.0 && nocheat_t * 1.2 > params.time){
						// All is good, but grab time from pool; don't care if
						// it's actually available
						playersao->getDigPool().grab(params.time);
					}
					// Short or laggy dig
					// Try getting the time from pool
					else if(playersao->getDigPool().grab(params.time)){
						// All is good
					}
					// Dig not possible
					else{
						infostream<<"Server: NoCheat: "<<player->getName()
								<<" completed digging "<<PP(p_under)
								<<"too fast; not digging."<<std::endl;
						is_valid_dig = false;
						// Call callbacks
						m_script->on_cheat(playersao, "dug_too_fast");
					}
				}

				/* Actually dig node */

				if(is_valid_dig && n.getContent() != CONTENT_IGNORE)
				{
					m_script->node_on_dig(p_under, n, playersao);
					stat.add("dig", player->getName());
					stat.add("dig_"+ m_nodedef->get(n).name , player->getName());
				}

				v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
				RemoteClient *client = getClient(peer_id);
				// Send unusual result (that is, node not being removed)
				if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
				{
					// Re-send block to revert change on client-side
					client->SetBlockNotSent(blockpos);
				}
				else {
					client->ResendBlockIfOnWire(blockpos);
				}
			}
		} // action == 2

		/*
			3: place block or right-click object
		*/
		else if(action == 3)
		{
			ItemStack item = playersao->getWieldedItem();

			// Reset build time counter
			if(pointed.type == POINTEDTHING_NODE &&
					item.getDefinition(m_itemdef).type == ITEM_NODE)
				getClient(peer_id)->m_time_from_building = 0.0;

			if(pointed.type == POINTEDTHING_OBJECT)
			{
				// Right click object

				// Skip if object has been removed
				if(pointed_object->m_removed)
					return;

/* android bug - too many
				actionstream<<player->getName()<<" right-clicks object "
						<<pointed.object_id<<": "
						<<pointed_object->getDescription()<<std::endl;
*/

				// Do stuff
				pointed_object->rightClick(playersao);
			}
			else if(m_script->item_OnPlace(
					item, playersao, pointed))
			{
				// Placement was handled in lua

				// Apply returned ItemStack
			if (playersao->setWieldedItem(item)) {
				SendInventory(playersao);
			}

				stat.add("place", player->getName());
				//stat.add("place_" + item.name, player->getName());
			}

			// If item has node placement prediction, always send the
			// blocks to make sure the client knows what exactly happened
			RemoteClient *client = getClient(peer_id);
			v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
			v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
			if(item.getDefinition(m_itemdef).node_placement_prediction != "") {
				client->SetBlockNotSent(blockpos);
				if(blockpos2 != blockpos) {
					client->SetBlockNotSent(blockpos2);
				}
			}
			else {
				client->ResendBlockIfOnWire(blockpos);
				if(blockpos2 != blockpos) {
					client->ResendBlockIfOnWire(blockpos2);
				}
			}
		} // action == 3

		/*
			4: use
		*/
		else if(action == 4)
		{
			ItemStack item = playersao->getWieldedItem();

			actionstream<<player->getName()<<" uses "<<item.name
					<<", pointing at "<<pointed.dump()<<std::endl;

			if(m_script->item_OnUse(
					item, playersao, pointed))
			{
				// Apply returned ItemStack
				if (playersao->setWieldedItem(item)) {
					SendInventory(playersao);
				}
				stat.add("use", player->getName());
				stat.add("use_" + item.name, player->getName());
			}

		} // action == 4


		/*
			Catch invalid actions
		*/
		else
		{
			infostream<<"WARNING: Server: Invalid action "
					<<action<<std::endl;
		}
	}
	else if(command == TOSERVER_REMOVED_SOUNDS)
	{
		std::vector<s32> removed_ids;
		packet[TOSERVER_REMOVED_SOUNDS_IDS].convert(&removed_ids);
		for (auto id : removed_ids) {
			std::map<s32, ServerPlayingSound>::iterator i =
					m_playing_sounds.find(id);
			if(i == m_playing_sounds.end())
				continue;
			ServerPlayingSound &psound = i->second;
			psound.clients.erase(peer_id);
			if(psound.clients.empty())
				m_playing_sounds.erase(i);
		}
	}
	else if(command == TOSERVER_NODEMETA_FIELDS)
	{
		v3s16 p = packet[TOSERVER_NODEMETA_FIELDS_POS].as<v3s16>();
		std::string formname = packet[TOSERVER_NODEMETA_FIELDS_FORMNAME].as<std::string>();
		std::map<std::string, std::string> fields;
		packet[TOSERVER_NODEMETA_FIELDS_DATA].convert(&fields);

		// If something goes wrong, this player is to blame
		RollbackScopeActor rollback_scope(m_rollback,
				std::string("player:")+player->getName());

		// Check the target node for rollback data; leave others unnoticed
		RollbackNode rn_old(&m_env->getMap(), p, this);

		m_script->node_on_receive_fields(p, formname, fields,playersao);

		// Report rollback data
		RollbackNode rn_new(&m_env->getMap(), p, this);
		if(rollback() && rn_new != rn_old){
			RollbackAction action;
			action.setSetNode(p, rn_old, rn_new);
			rollback()->reportAction(action);
		}
	}
	else if(command == TOSERVER_INVENTORY_FIELDS)
	{
		std::string formname;
		std::map<std::string, std::string> fields;

		packet[TOSERVER_INVENTORY_FIELDS_FORMNAME].convert(&formname);
		packet[TOSERVER_INVENTORY_FIELDS_DATA].convert(&fields);

		m_script->on_playerReceiveFields(playersao, formname, fields);
	}
	else if(command == TOSERVER_DRAWCONTROL)
	{
		auto client = getClient(peer_id);
		client->wanted_range = packet[TOSERVER_DRAWCONTROL_WANTED_RANGE].as<u32>();
		client->range_all = packet[TOSERVER_DRAWCONTROL_RANGE_ALL].as<u32>();
		client->farmesh  = packet[TOSERVER_DRAWCONTROL_FARMESH].as<u8>();
		client->fov  = packet[TOSERVER_DRAWCONTROL_FOV].as<f32>();
		client->block_overflow = packet[TOSERVER_DRAWCONTROL_BLOCK_OVERFLOW].as<bool>();
	}
	else
	{
		infostream<<"Server::ProcessData(): Ignoring "
				"unknown command "<<command<<std::endl;
	}

	} //try
	catch(SendFailedException &e)
	{
		errorstream<<"Server::ProcessData(): SendFailedException: "
				<<"what="<<e.what()
				<<std::endl;
	}
}
Ejemplo n.º 17
0
/*
	Convert objects that are not in active blocks to static.

	If m_known_by_count != 0, active object is not deleted, but static
	data is still updated.

	If force_delete is set, active object is deleted nevertheless. It
	shall only be set so in the destructor of the environment.
*/
void ServerEnvironment::deactivateFarObjects(bool force_delete)
{
	core::list<u16> objects_to_remove;
	for(core::map<u16, ServerActiveObject*>::Iterator
			i = m_active_objects.getIterator();
			i.atEnd()==false; i++)
	{
		ServerActiveObject* obj = i.getNode()->getValue();
		u16 id = i.getNode()->getKey();
		v3f objectpos = obj->getBasePosition();

		// This shouldn't happen but check it
		if(obj == NULL)
		{
			dstream<<"WARNING: NULL object found in ServerEnvironment"
					<<std::endl;
			assert(0);
			continue;
		}

		// The block in which the object resides in
		v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
		
		// If block is active, don't remove
		if(m_active_blocks.contains(blockpos_o))
			continue;

		/*
			Update the static data
		*/

		// Delete old static object
		MapBlock *oldblock = NULL;
		if(obj->m_static_exists)
		{
			MapBlock *block = m_map->getBlockNoCreateNoEx
					(obj->m_static_block);
			if(block)
			{
				block->m_static_objects.remove(id);
				oldblock = block;
			}
		}
		// Create new static object
		std::string staticdata = obj->getStaticData();
		StaticObject s_obj(obj->getType(), objectpos, staticdata);
		// Add to the block where the object is located in
		v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS));
		// Get or generate the block
		MapBlock *block = m_map->emergeBlock(blockpos);

		/*MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
		if(block == NULL)
		{
			// Block not found. Is the old block still ok?
			if(oldblock)
				block = oldblock;
			// Load from disk or generate
			else
				block = m_map->emergeBlock(blockpos);
		}*/

		if(block)
		{
			block->m_static_objects.insert(0, s_obj);
			block->setChangedFlag();
			obj->m_static_exists = true;
			obj->m_static_block = block->getPos();
		}
		else{
			dstream<<"WARNING: ServerEnv: Could not find or generate "
					<<"a block for storing static object"<<std::endl;
			obj->m_static_exists = false;
			continue;
		}

		/*
			Delete active object if not known by some client,
			else set pending deactivation
		*/

		// If known by some client, don't delete.
		if(obj->m_known_by_count > 0 && force_delete == false)
		{
			obj->m_pending_deactivation = true;
			continue;
		}
		
		/*dstream<<"INFO: Server: Stored static data. Deleting object."
				<<std::endl;*/
		// Delete active object
		delete obj;
		// Id to be removed from m_active_objects
		objects_to_remove.push_back(id);
	}

	// Remove references from m_active_objects
	for(core::list<u16>::Iterator i = objects_to_remove.begin();
			i != objects_to_remove.end(); i++)
	{
		m_active_objects.remove(*i);
	}
}
Ejemplo n.º 18
0
void ClientMap::updateDrawList(video::IVideoDriver* driver, float dtime, unsigned int max_cycle_ms)
{
    ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
    //g_profiler->add("CM::updateDrawList() count", 1);
    TimeTaker timer_step("ClientMap::updateDrawList");

    INodeDefManager *nodemgr = m_gamedef->ndef();

    if (!m_drawlist_last)
        m_drawlist_current = !m_drawlist_current;
    auto & drawlist = m_drawlist_current ? m_drawlist_1 : m_drawlist_0;

    if (!max_cycle_ms)
        max_cycle_ms = 300/getControl().fps_wanted;

    m_camera_mutex.Lock();
    v3f camera_position = m_camera_position;
    f32 camera_fov = m_camera_fov;
    //v3s16 camera_offset = m_camera_offset;
    m_camera_mutex.Unlock();

    // 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(
        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);

    // Number of blocks in rendering range
    u32 blocks_in_range = 0;
    // Number of blocks occlusion culled
    u32 blocks_occlusion_culled = 0;
    // Number of blocks in rendering range but don't have a mesh
    u32 blocks_in_range_without_mesh = 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;
    // Distance to farthest drawn block
    float farthest_drawn = 0;
    int m_mesh_queued = 0;

    bool free_move = g_settings->getBool("free_move");

    float range_max = 100000 * BS;
    if(m_control.range_all == false)
        range_max = m_control.wanted_range * BS;

    if (draw_nearest.empty()) {
        //ScopeProfiler sp(g_profiler, "CM::updateDrawList() make list", SPT_AVG);
        TimeTaker timer_step("ClientMap::updateDrawList make list");

        auto lock = m_blocks.try_lock_shared_rec();
        if (!lock->owns_lock())
            return;

        draw_nearest.clear();

        for(auto & ir : m_blocks) {
            auto bp = ir.first;

            if(m_control.range_all == false)
            {
                if(bp.X < p_blocks_min.X
                        || bp.X > p_blocks_max.X
                        || bp.Z > p_blocks_max.Z
                        || bp.Z < p_blocks_min.Z
                        || bp.Y < p_blocks_min.Y
                        || bp.Y > p_blocks_max.Y)
                    continue;
            }

            v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
            // Block center position
            v3f blockpos(
                ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS,
                ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS,
                ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS
            );

            f32 d = radius_box(blockpos, camera_position); //blockpos_relative.getLength();
            if (d> range_max)
                continue;
            int range = d / (MAP_BLOCKSIZE * BS);
            draw_nearest.emplace_back(std::make_pair(bp, range));
        }
    }

    const int maxq = 1000;

    // No occlusion culling when free_move is on and camera is
    // inside ground
    bool occlusion_culling_enabled = true;
    if(free_move) {
        MapNode n = getNodeNoEx(cam_pos_nodes);
        if(n.getContent() == CONTENT_IGNORE ||
                nodemgr->get(n).solidness == 2)
            occlusion_culling_enabled = false;
    }

    u32 calls = 0, end_ms = porting::getTimeMs() + u32(max_cycle_ms);

    std::unordered_map<v3POS, bool, v3POSHash, v3POSEqual> occlude_cache;

    while (!draw_nearest.empty()) {
        auto ir = draw_nearest.back();

        auto bp = ir.first;
        int range = ir.second;
        draw_nearest.pop_back();
        ++calls;

        //auto block = getBlockNoCreateNoEx(bp);
        auto block = m_blocks.get(bp);
        if (!block)
            continue;

        int mesh_step = getFarmeshStep(m_control, getNodeBlockPos(cam_pos_nodes), bp);
        /*
        	Compare block position to camera position, skip
        	if not seen on display
        */

        auto mesh = block->getMesh(mesh_step);
        if (mesh)
            mesh->updateCameraOffset(m_camera_offset);

        blocks_in_range++;

        auto smesh_size = block->mesh_size;
        /*
        	Ignore if mesh doesn't exist
        */
        {
            if(!mesh) {
                blocks_in_range_without_mesh++;
                if (m_mesh_queued < maxq || range <= 2) {
                    m_client->addUpdateMeshTask(bp, false);
                    ++m_mesh_queued;
                }
                continue;
            }
            if(mesh_step == mesh->step && block->getTimestamp() <= mesh->timestamp && !smesh_size) {
                blocks_in_range_without_mesh++;
                continue;
            }
        }

        /*
        	Occlusion culling
        */

        v3s16 cpn = bp * MAP_BLOCKSIZE;
        cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2);

        float step = BS*1;
        float stepfac = 1.2;
        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( range > 1 && smesh_size &&
                occlusion_culling_enabled &&
                isOccluded(this, spn, cpn + v3s16(0,0,0),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache) &&
                isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2),
                           step, stepfac, startoff, endoff, needed_count, nodemgr, occlude_cache)
          )
        {
            blocks_occlusion_culled++;
            continue;
        }

        // This block is in range. Reset usage timer.
        block->resetUsageTimer();

        // 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)
        				continue;
        */

        if (mesh_step != mesh->step && (m_mesh_queued < maxq*1.2 || range <= 2)) {
            m_client->addUpdateMeshTask(bp);
            ++m_mesh_queued;
        }
        if (block->getTimestamp() > mesh->timestamp + (smesh_size ? 0 : range >= 1 ? 60 : 5) && (m_mesh_queued < maxq*1.5 || range <= 2)) {
            m_client->addUpdateMeshTaskWithEdge(bp);
            ++m_mesh_queued;
        }

        if(!smesh_size)
            continue;

        mesh->incrementUsageTimer(dtime);

        // Add to set
        //block->refGrab();
        block->resetUsageTimer();
        drawlist.set(bp, block);

        blocks_drawn++;

        if(range * MAP_BLOCKSIZE > farthest_drawn)
            farthest_drawn = range * MAP_BLOCKSIZE;

        if (farthest_drawn > m_control.farthest_drawn)
            m_control.farthest_drawn = farthest_drawn;

        if (porting::getTimeMs() > end_ms) {
            break;
        }

    }
    m_drawlist_last = draw_nearest.size();

    //if (m_drawlist_last) infostream<<"breaked UDL "<<m_drawlist_last<<" collected="<<drawlist.size()<<" calls="<<calls<<" s="<<m_blocks.size()<<" maxms="<<max_cycle_ms<<" fw="<<getControl().fps_wanted<<" morems="<<porting::getTimeMs() - end_ms<< " meshq="<<m_mesh_queued<<" occache="<<occlude_cache.size()<<std::endl;

    if (m_drawlist_last)
        return;

    //for (auto & ir : *m_drawlist)
    //	ir.second->refDrop();

    auto m_drawlist_old = !m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0;
    m_drawlist = m_drawlist_current ? &m_drawlist_1 : &m_drawlist_0;
    m_drawlist_old->clear();

    m_control.blocks_would_have_drawn = blocks_would_have_drawn;
    m_control.blocks_drawn = blocks_drawn;
    m_control.farthest_drawn = farthest_drawn;

    g_profiler->avg("CM: blocks total", m_blocks.size());
    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: farthest drawn", farthest_drawn);
    g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
}
Ejemplo n.º 19
0
void ServerEnvironment::step(float dtime)
{
	DSTACK(__FUNCTION_NAME);
	
	//TimeTaker timer("ServerEnv step");

	// Get some settings
	bool footprints = g_settings.getBool("footprints");

	/*
		Increment game time
	*/
	{
		m_game_time_fraction_counter += dtime;
		u32 inc_i = (u32)m_game_time_fraction_counter;
		m_game_time += inc_i;
		m_game_time_fraction_counter -= (float)inc_i;
	}
	
	/*
		Handle players
	*/
	for(core::list<Player*>::Iterator i = m_players.begin();
			i != m_players.end(); i++)
	{
		Player *player = *i;
		
		// Ignore disconnected players
		if(player->peer_id == 0)
			continue;

		v3f playerpos = player->getPosition();
		
		// Move
		player->move(dtime, *m_map, 100*BS);
		
		/*
			Add footsteps to grass
		*/
		if(footprints)
		{
			// Get node that is at BS/4 under player
			v3s16 bottompos = floatToInt(playerpos + v3f(0,-BS/4,0), BS);
			try{
				MapNode n = m_map->getNode(bottompos);
				if(n.getContent() == CONTENT_GRASS)
				{
					n.setContent(CONTENT_GRASS_FOOTSTEPS);
					m_map->setNode(bottompos, n);
				}
			}
			catch(InvalidPositionException &e)
			{
			}
		}
	}

	/*
		Manage active block list
	*/
	if(m_active_blocks_management_interval.step(dtime, 2.0))
	{
		/*
			Get player block positions
		*/
		core::list<v3s16> players_blockpos;
		for(core::list<Player*>::Iterator
				i = m_players.begin();
				i != m_players.end(); i++)
		{
			Player *player = *i;
			// Ignore disconnected players
			if(player->peer_id == 0)
				continue;
			v3s16 blockpos = getNodeBlockPos(
					floatToInt(player->getPosition(), BS));
			players_blockpos.push_back(blockpos);
		}
		
		/*
			Update list of active blocks, collecting changes
		*/
		const s16 active_block_range = 5;
		core::map<v3s16, bool> blocks_removed;
		core::map<v3s16, bool> blocks_added;
		m_active_blocks.update(players_blockpos, active_block_range,
				blocks_removed, blocks_added);

		/*
			Handle removed blocks
		*/

		// Convert active objects that are no more in active blocks to static
		deactivateFarObjects(false);
		
		for(core::map<v3s16, bool>::Iterator
				i = blocks_removed.getIterator();
				i.atEnd()==false; i++)
		{
			v3s16 p = i.getNode()->getKey();

			/*dstream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
					<<") became inactive"<<std::endl;*/
			
			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
			if(block==NULL)
				continue;
			
			// Set current time as timestamp (and let it set ChangedFlag)
			block->setTimestamp(m_game_time);
		}

		/*
			Handle added blocks
		*/

		for(core::map<v3s16, bool>::Iterator
				i = blocks_added.getIterator();
				i.atEnd()==false; i++)
		{
			v3s16 p = i.getNode()->getKey();
			
			/*dstream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
					<<") became active"<<std::endl;*/

			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
			if(block==NULL)
				continue;

			activateBlock(block);
		}
	}

	/*
		Mess around in active blocks
	*/
	if(m_active_blocks_nodemetadata_interval.step(dtime, 1.0))
	{
		float dtime = 1.0;

		for(core::map<v3s16, bool>::Iterator
				i = m_active_blocks.m_list.getIterator();
				i.atEnd()==false; i++)
		{
			v3s16 p = i.getNode()->getKey();
			
			/*dstream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
					<<") being handled"<<std::endl;*/

			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
			if(block==NULL)
				continue;

			// Reset block usage timer
			block->resetUsageTimer();
			
			// Set current time as timestamp
			block->setTimestampNoChangedFlag(m_game_time);

			// Run node metadata
			bool changed = block->m_node_metadata.step(dtime);
			if(changed)
			{
				MapEditEvent event;
				event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
				event.p = p;
				m_map->dispatchEvent(&event);

				block->setChangedFlag();
			}
		}
	}
	if(m_active_blocks_test_interval.step(dtime, 10.0))
	{
		//float dtime = 10.0;
		
		for(core::map<v3s16, bool>::Iterator
				i = m_active_blocks.m_list.getIterator();
				i.atEnd()==false; i++)
		{
			v3s16 p = i.getNode()->getKey();
			
			/*dstream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
					<<") being handled"<<std::endl;*/

			MapBlock *block = m_map->getBlockNoCreateNoEx(p);
			if(block==NULL)
				continue;
			
			// Set current time as timestamp
			block->setTimestampNoChangedFlag(m_game_time);

			/*
				Do stuff!

				Note that map modifications should be done using the event-
				making map methods so that the server gets information
				about them.

				Reading can be done quickly directly from the block.

				Everything should bind to inside this single content
				searching loop to keep things fast.
			*/
			// TODO: Implement usage of ActiveBlockModifier
			
			// Find out how many objects the block contains
			u32 active_object_count = block->m_static_objects.m_active.size();
			// Find out how many objects this and all the neighbors contain
			u32 active_object_count_wider = 0;
			for(s16 x=-1; x<=1; x++)
			for(s16 y=-1; y<=1; y++)
			for(s16 z=-1; z<=1; z++)
			{
				MapBlock *block = m_map->getBlockNoCreateNoEx(p+v3s16(x,y,z));
				if(block==NULL)
					continue;
				active_object_count_wider +=
						block->m_static_objects.m_active.size();
			}

			v3s16 p0;
			for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
			for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
			for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
			{
				v3s16 p = p0 + block->getPosRelative();
				MapNode n = block->getNodeNoEx(p0);

				/*
					Test something:
					Convert mud under proper lighting to grass
				*/
				if(n.getContent() == CONTENT_MUD)
				{
					if(myrand()%20 == 0)
					{
						MapNode n_top = m_map->getNodeNoEx(p+v3s16(0,1,0));
						if(content_features(n_top).air_equivalent &&
								n_top.getLightBlend(getDayNightRatio()) >= 13)
						{
							n.setContent(CONTENT_GRASS);
							m_map->addNodeWithEvent(p, n);
						}
					}
				}
				/*
					Convert grass into mud if under something else than air
				*/
				if(n.getContent() == CONTENT_GRASS)
				{
					//if(myrand()%20 == 0)
					{
						MapNode n_top = m_map->getNodeNoEx(p+v3s16(0,1,0));
						if(content_features(n_top).air_equivalent == false)
						{
							n.setContent(CONTENT_MUD);
							m_map->addNodeWithEvent(p, n);
						}
					}
				}
				/*
					Rats spawn around regular trees
				*/
				if(n.getContent() == CONTENT_TREE ||
						n.getContent() == CONTENT_JUNGLETREE)
				{
   					if(myrand()%200 == 0 && active_object_count_wider == 0)
					{
						v3s16 p1 = p + v3s16(myrand_range(-2, 2),
								0, myrand_range(-2, 2));
						MapNode n1 = m_map->getNodeNoEx(p1);
						MapNode n1b = m_map->getNodeNoEx(p1+v3s16(0,-1,0));
						if(n1b.getContent() == CONTENT_GRASS &&
								n1.getContent() == CONTENT_AIR)
						{
							v3f pos = intToFloat(p1, BS);
							ServerActiveObject *obj = new RatSAO(this, 0, pos);
							addActiveObject(obj);
						}
					}
			 }
			}
		}
	}
	
	/*
		Step active objects
	*/
	{
		//TimeTaker timer("Step active objects");
		
		// This helps the objects to send data at the same time
		bool send_recommended = false;
		m_send_recommended_timer += dtime;
		if(m_send_recommended_timer > 0.15)
		{
			m_send_recommended_timer = 0;
			send_recommended = true;
		}

		for(core::map<u16, ServerActiveObject*>::Iterator
				i = m_active_objects.getIterator();
				i.atEnd()==false; i++)
		{
			ServerActiveObject* obj = i.getNode()->getValue();
			// Don't step if is to be removed or stored statically
			if(obj->m_removed || obj->m_pending_deactivation)
				continue;
			// Step object
			obj->step(dtime, send_recommended);
			// Read messages from object
			while(obj->m_messages_out.size() > 0)
			{
				m_active_object_messages.push_back(
						obj->m_messages_out.pop_front());
			}
		}
	}
	
	/*
		Manage active objects
	*/
	if(m_object_management_interval.step(dtime, 0.5))
	{
		/*
			Remove objects that satisfy (m_removed && m_known_by_count==0)
		*/
		removeRemovedObjects();
	}

	if(g_settings.getBool("enable_experimental"))
	{

	/*
		TEST CODE
	*/
#if 1
	m_random_spawn_timer -= dtime;
	if(m_random_spawn_timer < 0)
	{
		//m_random_spawn_timer += myrand_range(2.0, 20.0);
		//m_random_spawn_timer += 2.0;
		m_random_spawn_timer += 200.0;

		/*
			Find some position
		*/

		/*v2s16 p2d(myrand_range(-5,5), myrand_range(-5,5));
		s16 y = 1 + getServerMap().findGroundLevel(p2d);
		v3f pos(p2d.X*BS,y*BS,p2d.Y*BS);*/
		
		Player *player = getRandomConnectedPlayer();
		v3f pos(0,0,0);
		if(player)
			pos = player->getPosition();
		pos += v3f(
			myrand_range(-3,3)*BS,
			0,
			myrand_range(-3,3)*BS
		);

		/*
			Create a ServerActiveObject
		*/

		//TestSAO *obj = new TestSAO(this, 0, pos);
		//ServerActiveObject *obj = new ItemSAO(this, 0, pos, "CraftItem Stick 1");
		//ServerActiveObject *obj = new RatSAO(this, 0, pos);
		//ServerActiveObject *obj = new Oerkki1SAO(this, 0, pos);
		ServerActiveObject *obj = new FireflySAO(this, 0, pos);
		addActiveObject(obj);
	}
#endif

	} // enable_experimental
}
Ejemplo n.º 20
0
u32 Map::transformLiquidsReal(Server *m_server, unsigned int max_cycle_ms) {

	INodeDefManager *nodemgr = m_gamedef->ndef();

	DSTACK(__FUNCTION_NAME);
	//TimeTaker timer("transformLiquidsReal()");
	u32 loopcount = 0;
	u32 initial_size = transforming_liquid_size();

	s32 regenerated = 0;

#if LIQUID_DEBUG
	bool debug = 1;
#endif

	u8 relax = g_settings->getS16("liquid_relax");
	bool fast_flood = g_settings->getS16("liquid_fast_flood");
	int water_level = g_settings->getS16("water_level");
	s16 liquid_pressure = m_server->m_emerge->params.liquid_pressure;
	//g_settings->getS16NoEx("liquid_pressure", liquid_pressure);

	// list of nodes that due to viscosity have not reached their max level height
	//std::unordered_map<v3POS, bool, v3POSHash, v3POSEqual> must_reflow, must_reflow_second, must_reflow_third;
	std::list<v3POS> must_reflow, must_reflow_second, must_reflow_third;
	// List of MapBlocks that will require a lighting update (due to lava)
	u16 loop_rand = myrand();

	u32 end_ms = porting::getTimeMs() + max_cycle_ms;

NEXT_LIQUID:
	;
	while (transforming_liquid_size() > 0) {
		// This should be done here so that it is done when continue is used
		if (loopcount >= initial_size * 2 || porting::getTimeMs() > end_ms)
			break;
		++loopcount;
		/*
			Get a queued transforming liquid node
		*/
		v3POS p0;
		{
			//JMutexAutoLock lock(m_transforming_liquid_mutex);
			p0 = transforming_liquid_pop();
		}
		s16 total_level = 0;
		//u16 level_max = 0;
		// surrounding flowing liquid nodes
		NodeNeighbor neighbors[7] = { { } };
		// current level of every block
		s8 liquid_levels[7] = { -1, -1, -1, -1, -1, -1, -1};
		// target levels
		s8 liquid_levels_want[7] = { -1, -1, -1, -1, -1, -1, -1};
		s8 can_liquid_same_level = 0;
		s8 can_liquid = 0;
		// warning! when MINETEST_PROTO enabled - CONTENT_IGNORE != 0
		content_t liquid_kind = CONTENT_IGNORE;
		content_t liquid_kind_flowing = CONTENT_IGNORE;
		content_t melt_kind = CONTENT_IGNORE;
		content_t melt_kind_flowing = CONTENT_IGNORE;
		//s8 viscosity = 0;
		/*
			Collect information about the environment, start from self
		 */
		for (u8 e = 0; e < 7; e++) {
			u8 i = liquid_explore_map[e];
			NodeNeighbor & nb = neighbors[i];
			nb.pos = p0 + liquid_flow_dirs[i];
			nb.node = getNodeNoEx(neighbors[i].pos);
			nb.content = nb.node.getContent();
			NeighborType nt = NEIGHBOR_SAME_LEVEL;
			switch (i) {
			case D_TOP:
				nt = NEIGHBOR_UPPER;
				break;
			case D_BOTTOM:
				nt = NEIGHBOR_LOWER;
				break;
			}
			nb.type = nt;
			nb.liquid = 0;
			nb.infinity = 0;
			nb.weight = 0;
			nb.drop = 0;

			if (!nb.node) {
				//if (i == D_SELF && (loopcount % 2) && initial_size < m_liquid_step_flow * 3)
				//	must_reflow_third[nb.pos] = 1;
				//	must_reflow_third.push_back(nb.pos);
				continue;
			}

			auto f = nodemgr->get(nb.content);
			switch (f.liquid_type) {
			case LIQUID_NONE:
				if (nb.content == CONTENT_AIR) {
					liquid_levels[i] = 0;
					nb.liquid = 1;
				}
				//TODO: if (nb.content == CONTENT_AIR || nodemgr->get(nb.node).buildable_to && !nodemgr->get(nb.node).walkable) { // need lua drop api for drop torches
				else if (	melt_kind_flowing != CONTENT_IGNORE &&
				            nb.content == melt_kind_flowing &&
				            nb.type != NEIGHBOR_UPPER &&
				            !(loopcount % 2)) {
					u8 melt_max_level = nb.node.getMaxLevel(nodemgr);
					u8 my_max_level = MapNode(liquid_kind_flowing).getMaxLevel(nodemgr);
					liquid_levels[i] = (float)my_max_level / melt_max_level * nb.node.getLevel(nodemgr);
					if (liquid_levels[i])
						nb.liquid = 1;
				} else if (	melt_kind != CONTENT_IGNORE &&
				            nb.content == melt_kind &&
				            nb.type != NEIGHBOR_UPPER &&
				            !(loopcount % 8)) {
					liquid_levels[i] = nodemgr->get(liquid_kind_flowing).getMaxLevel();
					if (liquid_levels[i])
						nb.liquid = 1;
				} else {
					int drop = ((ItemGroupList) f.groups)["drop_by_liquid"];
					if (drop && !(loopcount % drop) ) {
						liquid_levels[i] = 0;
						nb.liquid = 1;
						nb.drop = 1;
					}
				}

				// todo: for erosion add something here..
				break;
			case LIQUID_SOURCE:
				// if this node is not (yet) of a liquid type,
				// choose the first liquid type we encounter
				if (liquid_kind_flowing == CONTENT_IGNORE)
					liquid_kind_flowing = nodemgr->getId(
					                          f.liquid_alternative_flowing);
				if (liquid_kind == CONTENT_IGNORE)
					liquid_kind = nb.content;
				if (liquid_kind_flowing == CONTENT_IGNORE)
					liquid_kind_flowing = liquid_kind;
				if (melt_kind == CONTENT_IGNORE)
					melt_kind = nodemgr->getId(f.melt);
				if (melt_kind_flowing == CONTENT_IGNORE)
					melt_kind_flowing =
					    nodemgr->getId(
					        nodemgr->get(nodemgr->getId(f.melt)
					                    ).liquid_alternative_flowing);
				if (melt_kind_flowing == CONTENT_IGNORE)
					melt_kind_flowing = melt_kind;
				if (nb.content == liquid_kind) {
					if (nb.node.param2 & LIQUID_STABLE_MASK)
						continue;
					liquid_levels[i] = nb.node.getLevel(nodemgr); //LIQUID_LEVEL_SOURCE;
					nb.liquid = 1;
					nb.infinity = (nb.node.param2 & LIQUID_INFINITY_MASK);
				}
				break;
			case LIQUID_FLOWING:
				// if this node is not (yet) of a liquid type,
				// choose the first liquid type we encounter
				if (liquid_kind_flowing == CONTENT_IGNORE)
					liquid_kind_flowing = nb.content;
				if (liquid_kind == CONTENT_IGNORE)
					liquid_kind = nodemgr->getId(
					                  f.liquid_alternative_source);
				if (liquid_kind == CONTENT_IGNORE)
					liquid_kind = liquid_kind_flowing;
				if (melt_kind_flowing == CONTENT_IGNORE)
					melt_kind_flowing = nodemgr->getId(f.melt);
				if (melt_kind == CONTENT_IGNORE)
					melt_kind = nodemgr->getId(nodemgr->get(nodemgr->getId(
					        f.melt)).liquid_alternative_source);
				if (melt_kind == CONTENT_IGNORE)
					melt_kind = melt_kind_flowing;
				if (nb.content == liquid_kind_flowing) {
					if (nb.node.param2 & LIQUID_STABLE_MASK)
						continue;
					liquid_levels[i] = nb.node.getLevel(nodemgr);
					nb.liquid = 1;
					nb.infinity = (nb.node.param2 & LIQUID_INFINITY_MASK);
				}
				break;
			}

			// only self, top, bottom swap
			if (f.liquid_type && e <= 2) {
				try {
					nb.weight = ((ItemGroupList) f.groups)["weight"];
					if (e == 1 && neighbors[D_BOTTOM].weight && neighbors[D_SELF].weight > neighbors[D_BOTTOM].weight) {
						setNode(neighbors[D_SELF].pos, neighbors[D_BOTTOM].node);
						setNode(neighbors[D_BOTTOM].pos, neighbors[D_SELF].node);
						//must_reflow_second[neighbors[D_SELF].pos] = 1;
						//must_reflow_second[neighbors[D_BOTTOM].pos] = 1;
						must_reflow_second.push_back(neighbors[D_SELF].pos);
						must_reflow_second.push_back(neighbors[D_BOTTOM].pos);
#if LIQUID_DEBUG
						infostream << "Liquid swap1" << neighbors[D_SELF].pos << nodemgr->get(neighbors[D_SELF].node).name << neighbors[D_SELF].node << " w=" << neighbors[D_SELF].weight << " VS " << neighbors[D_BOTTOM].pos << nodemgr->get(neighbors[D_BOTTOM].node).name << neighbors[D_BOTTOM].node << " w=" << neighbors[D_BOTTOM].weight << std::endl;
#endif
						goto NEXT_LIQUID;
					}
					if (e == 2 && neighbors[D_SELF].weight && neighbors[D_TOP].weight > neighbors[D_SELF].weight) {
						setNode(neighbors[D_SELF].pos, neighbors[D_TOP].node);
						setNode(neighbors[D_TOP].pos, neighbors[D_SELF].node);
						//must_reflow_second[neighbors[D_SELF].pos] = 1;
						//must_reflow_second[neighbors[D_TOP].pos] = 1;
						must_reflow_second.push_back(neighbors[D_SELF].pos);
						must_reflow_second.push_back(neighbors[D_TOP].pos);
#if LIQUID_DEBUG
						infostream << "Liquid swap2" << neighbors[D_TOP].pos << nodemgr->get(neighbors[D_TOP].node).name << neighbors[D_TOP].node  << " w=" << neighbors[D_TOP].weight << " VS " << neighbors[D_SELF].pos << nodemgr->get(neighbors[D_SELF].node).name << neighbors[D_SELF].node << " w=" << neighbors[D_SELF].weight << std::endl;
#endif
						goto NEXT_LIQUID;
					}
				} catch(InvalidPositionException &e) {
					verbosestream << "transformLiquidsReal: weight: setNode() failed:" << nb.pos << ":" << e.what() << std::endl;
					//goto NEXT_LIQUID;
				}
			}

			if (nb.liquid) {
				liquid_levels_want[i] = 0;
				++can_liquid;
				if(nb.type == NEIGHBOR_SAME_LEVEL)
					++can_liquid_same_level;
			}
			if (liquid_levels[i] > 0)
				total_level += liquid_levels[i];

#if LIQUID_DEBUG
			infostream << "get node i=" << (int)i << " " << PP(nb.pos) << " c="
			           << nb.content << " p0=" << (int)nb.node.param0 << " p1="
			           << (int)nb.node.param1 << " p2=" << (int)nb.node.param2 << " lt="
			           << f.liquid_type
			           //<< " lk=" << liquid_kind << " lkf=" << liquid_kind_flowing
			           << " l=" << nb.liquid	<< " inf=" << nb.infinity << " nlevel=" << (int)liquid_levels[i]
			           << " totallevel=" << (int)total_level << " cansame="
			           << (int)can_liquid_same_level << " Lmax=" << (int)nodemgr->get(liquid_kind_flowing).getMaxLevel() << std::endl;
#endif
		}

		if (liquid_kind == CONTENT_IGNORE || !neighbors[D_SELF].liquid || total_level <= 0)
			continue;

		s16 level_max = nodemgr->get(liquid_kind_flowing).getMaxLevel();
		s16 level_max_compressed = nodemgr->get(liquid_kind_flowing).getMaxLevel(1);
		s16 pressure = liquid_pressure ? ((ItemGroupList) nodemgr->get(liquid_kind).groups)["pressure"] : 0;
		auto liquid_renewable = nodemgr->get(liquid_kind).liquid_renewable;
#if LIQUID_DEBUG
		s16 total_was = total_level; //debug
#endif
		//viscosity = nodemgr->get(liquid_kind).viscosity;

		s16 level_avg = total_level / can_liquid;
		if (!pressure && level_avg) {
			level_avg = level_max;
		}

#if LIQUID_DEBUG
		if (debug)
			infostream << " go: "
			           << nodemgr->get(liquid_kind).name
			           << " total_level=" << (int)total_level
			           //<<" total_was="<<(int)total_was
			           << " level_max=" << (int)level_max
			           << " level_max_compressed=" << (int)level_max_compressed
			           << " level_avg=" << (int)level_avg
			           << " pressure=" << (int)pressure
			           << " can_liquid=" << (int)can_liquid
			           << " can_liquid_same_level=" << (int)can_liquid_same_level
			           << std::endl;
		;
#endif

		// fill bottom block
		if (neighbors[D_BOTTOM].liquid) {
			liquid_levels_want[D_BOTTOM] = level_avg > level_max ? level_avg : total_level > level_max ? level_max : total_level;
			total_level -= liquid_levels_want[D_BOTTOM];
			//if (pressure && total_level && liquid_levels_want[D_BOTTOM] < level_max_compressed) {
			//	++liquid_levels_want[D_BOTTOM];
			//	--total_level;
			//}
		}

		//relax up
		u16 relax_want = level_max * can_liquid_same_level;
		if (	liquid_renewable &&
		        relax &&
		        ((p0.Y == water_level) || (fast_flood && p0.Y <= water_level)) &&
		        level_max > 1 &&
		        liquid_levels[D_TOP] == 0 &&
		        liquid_levels[D_BOTTOM] >= level_max &&
		        total_level >= relax_want - (can_liquid_same_level - relax) &&
		        total_level < relax_want &&
		        can_liquid_same_level >= relax + 1) {
			regenerated += relax_want - total_level;
#if LIQUID_DEBUG
			infostream << " relax_up: " << " total_level=" << (int)total_level << " to=> " << int(relax_want) << std::endl;
#endif
			total_level = relax_want;
		}

		// prevent lakes in air above unloaded blocks
		if (	liquid_levels[D_TOP] == 0 &&
		        p0.Y > water_level &&
		        level_max > 1 &&
		        !neighbors[D_BOTTOM].node &&
		        !(loopcount % 3)) {
			--total_level;
#if LIQUID_DEBUG
			infostream << " above unloaded fix: " << " total_level=" << (int)total_level << std::endl;
#endif
		}

		// calculate self level 5 blocks
		u16 want_level = level_avg > level_max ? level_avg :
		                 total_level >= level_max * can_liquid_same_level
		                 ? level_max
		                 : total_level / can_liquid_same_level;
		total_level -= want_level * can_liquid_same_level;

		/*
				if (pressure && total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +1
					++liquid_levels_want[D_BOTTOM];
					--total_level;
		#if LIQUID_DEBUG
					infostream << " bottom1 pressure+1: " << " bottom=" << (int)liquid_levels_want[D_BOTTOM] << " total_level=" << (int)total_level << std::endl;
		#endif
				}
		*/

		//relax down
		if (	liquid_renewable &&
		        relax &&
		        p0.Y >= water_level - 1  &&
		        p0.Y <= water_level + 1  &&
		        liquid_levels[D_TOP] == 0 &&
		        (total_level <= 1 || !(loopcount % 2)) &&
		        level_max > 1 &&
		        liquid_levels[D_BOTTOM] >= level_max &&
		        want_level <= 0 &&
		        total_level <= (can_liquid_same_level - relax) &&
		        can_liquid_same_level >= relax + 1) {
#if LIQUID_DEBUG
			infostream << " relax_down: " << " total_level WAS=" << (int)total_level << " to => 0" << std::endl;
#endif
			regenerated -= total_level;
			total_level = 0;
		}

		for (u16 ir = D_SELF; ir < D_TOP; ++ir) { // fill only same level
			u16 ii = liquid_random_map[(loopcount + loop_rand + 1) % 4][ir];
			if (!neighbors[ii].liquid)
				continue;
			liquid_levels_want[ii] = want_level;
			//if (viscosity > 1 && (liquid_levels_want[ii]-liquid_levels[ii]>8-viscosity))
			// randomly place rest of divide
			if (liquid_levels_want[ii] < level_max && total_level > 0) {
				if (level_max > LIQUID_LEVEL_SOURCE || loopcount % 3 || liquid_levels[ii] <= 0) {
					if (liquid_levels[ii] > liquid_levels_want[ii]) {
						++liquid_levels_want[ii];
						--total_level;
					}
				} else {
					++liquid_levels_want[ii];
					--total_level;
				}
			}
		}

		for (u16 ir = D_SELF; ir < D_TOP; ++ir) {
			if (total_level < 1)
				break;
			u16 ii = liquid_random_map[(loopcount + loop_rand + 2) % 4][ir];
			if (liquid_levels_want[ii] >= 0 &&
			        liquid_levels_want[ii] < level_max) {
				++liquid_levels_want[ii];
				--total_level;
			}
		}

		// fill top block if can
		if (neighbors[D_TOP].liquid && total_level > 0) {
			//infostream<<"compressing to top was="<<liquid_levels_want[D_TOP]<<" add="<<total_level<<std::endl;
			//liquid_levels_want[D_TOP] = total_level>level_max_compressed?level_max_compressed:total_level;
			liquid_levels_want[D_TOP] = total_level > level_max ? level_max : total_level;
			total_level -= liquid_levels_want[D_TOP];

			//if (liquid_levels_want[D_TOP] && total_level && pressure) {
			if (total_level > 0 && pressure) {

				/*
								if (total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +2
									++liquid_levels_want[D_BOTTOM];
									--total_level;
								}
				*/
				//compressing self level while can
				//for (u16 ir = D_SELF; ir < D_TOP; ++ir) {
				for (u16 ir = D_BOTTOM; ir <= D_TOP; ++ir) {
					if (total_level < 1)
						break;
					u16 ii = liquid_random_map[(loopcount + loop_rand + 3) % 4][ir];
					if (neighbors[ii].liquid &&
					        liquid_levels_want[ii] < level_max_compressed) {
						++liquid_levels_want[ii];
						--total_level;
					}
				}

				/*
								if (total_level > 0 && neighbors[D_BOTTOM].liquid) { // bottom pressure +2
									++liquid_levels_want[D_BOTTOM];
									--total_level;
				#if LIQUID_DEBUG
							infostream << " bottom2 pressure+1: " << " bottom=" << (int)liquid_levels_want[D_BOTTOM] << " total_level=" << (int)total_level << std::endl;
				#endif
								}
				*/
			}
		}

		if (pressure) {
			if (neighbors[D_BOTTOM].liquid &&
			        liquid_levels_want[D_BOTTOM] < level_max_compressed &&
			        liquid_levels_want[D_TOP] > 0
			   ) {
				//if (liquid_levels_want[D_BOTTOM] <= liquid_levels_want[D_TOP]) {
				--liquid_levels_want[D_TOP];
				++liquid_levels_want[D_BOTTOM];
#if LIQUID_DEBUG
				infostream << " bottom1 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl;
#endif
				//}
			} else if (
			    neighbors[D_BOTTOM].liquid &&
			    liquid_levels_want[D_BOTTOM] < level_max_compressed &&
			    liquid_levels_want[D_SELF] > level_max
			) {
				if (liquid_levels_want[D_BOTTOM] <= liquid_levels_want[D_SELF]) {
					--liquid_levels_want[D_SELF];
					++liquid_levels_want[D_BOTTOM];
#if LIQUID_DEBUG
					infostream << " bottom2 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl;
#endif
				}
			} else if (
			    neighbors[D_TOP].liquid &&
			    liquid_levels_want[D_SELF] < level_max_compressed &&
			    liquid_levels_want[D_TOP] > level_max
			) {
				if (liquid_levels_want[D_SELF] <= liquid_levels_want[D_TOP]) {
					--liquid_levels_want[D_TOP];
					++liquid_levels_want[D_SELF];
#if LIQUID_DEBUG
					infostream << " bottom3 pressure+: " << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << " total_level=" << (int)total_level << std::endl;
#endif
				}
			}

			if (liquid_levels_want[D_TOP] > level_max && relax && total_level <= 0 && level_avg > level_max && liquid_levels_want[D_TOP] < level_avg) {
#if LIQUID_DEBUG
				infostream << " top pressure relax: " << " top=" << (int)liquid_levels_want[D_TOP] << " to=>" << level_avg << std::endl;
#endif

				//regenerated += level_avg - liquid_levels_want[D_TOP];
				//liquid_levels_want[D_TOP] = level_avg;
				regenerated += 1 ;
				liquid_levels_want[D_TOP] += 1;
			}
		}


#if LIQUID_DEBUG
		if (total_level > 0)
			infostream << " rest 1: "
			           << " wtop=" << (int)liquid_levels_want[D_TOP]
			           << " total_level=" << (int)total_level << std::endl;
#endif

		if (total_level > 0 && neighbors[D_TOP].liquid && liquid_levels_want[D_TOP] < level_max_compressed) {
			s16 add = (total_level > level_max_compressed - liquid_levels_want[D_TOP]) ? level_max_compressed - liquid_levels_want[D_TOP] : total_level;
			liquid_levels_want[D_TOP] += add;
			total_level -= add;
		}


		if (total_level > 0 && neighbors[D_SELF].liquid && liquid_levels_want[D_SELF] < level_max_compressed) { // very rare, compressed only
			s16 add = (total_level > level_max_compressed - liquid_levels_want[D_SELF]) ? level_max_compressed - liquid_levels_want[D_SELF] : total_level;
#if LIQUID_DEBUG
			if (total_level > 0)
				infostream << " rest 2: "
				           << " wself=" << (int)liquid_levels_want[D_SELF]
				           << " total_level=" << (int)total_level
				           << " add=" << (int)add
				           << std::endl;
#endif

			liquid_levels_want[D_SELF] += add;
			total_level -= add;
		}


#if LIQUID_DEBUG
		if (total_level > 0)
			infostream << " rest 3: "
			           << " total_level=" << (int)total_level << std::endl;
#endif

		for (u16 ii = 0; ii < 7; ii++) { // infinity and cave flood optimization
			if (neighbors[ii].infinity && liquid_levels_want[ii] < liquid_levels[ii]) {
#if LIQUID_DEBUG
				infostream << " infinity: was=" << (int)ii << " = "
				           << (int)liquid_levels_want[ii] << "  to=" << (int)liquid_levels[ii] << std::endl;
#endif

				regenerated += liquid_levels[ii] - liquid_levels_want[ii];
				liquid_levels_want[ii] = liquid_levels[ii];
			} else if ( liquid_levels_want[ii] >= 0	&&
			            liquid_levels_want[ii] < level_max &&
			            level_max > 1					&&
			            fast_flood					&&
			            p0.Y < water_level			&&
			            initial_size >= 1000			&&
			            ii != D_TOP					&&
			            want_level >= level_max / 4	&&
			            can_liquid_same_level >= 5	&&
			            liquid_levels[D_TOP] >= level_max) {
#if LIQUID_DEBUG
				infostream << " flood_fast: was=" << (int)ii << " = "
				           << (int)liquid_levels_want[ii] << "  to=" << (int)level_max << std::endl;
#endif
				regenerated += level_max - liquid_levels_want[ii];
				liquid_levels_want[ii] = level_max;
			}
		}

#if LIQUID_DEBUG
		if (total_level != 0) //|| flowed != volume)
			infostream << " AFTER err level=" << (int)total_level
			           //<< " flowed="<<flowed<< " volume=" << volume
			           << " max=" << (int)level_max
			           << " wantsame=" << (int)want_level << " top="
			           << (int)liquid_levels_want[D_TOP] << " topwas="
			           << (int)liquid_levels[D_TOP]
			           << " bot=" << (int)liquid_levels_want[D_BOTTOM]
			           << " botwas=" << (int)liquid_levels[D_BOTTOM]
			           << std::endl;

		s16 flowed = 0; // for debug
#endif

#if LIQUID_DEBUG
		if (debug) infostream << " dpress=" << " bot=" << (int)liquid_levels_want[D_BOTTOM] << " slf=" << (int)liquid_levels_want[D_SELF] << " top=" << (int)liquid_levels_want[D_TOP] << std::endl;
#endif

		for (u16 r = 0; r < 7; r++) {
			u16 i = liquid_random_map[(loopcount + loop_rand + 4) % 4][r];
			if (liquid_levels_want[i] < 0 || !neighbors[i].liquid)
				continue;

#if LIQUID_DEBUG
			if (debug) infostream << " set=" << i << " " << neighbors[i].pos << " want=" << (int)liquid_levels_want[i] << " was=" << (int) liquid_levels[i] << std::endl;
#endif

			/* disabled because brokes constant volume of lava
			u8 viscosity = nodemgr->get(liquid_kind).liquid_viscosity;
			if (viscosity > 1 && liquid_levels_want[i] != liquid_levels[i]) {
				// amount to gain, limited by viscosity
				// must be at least 1 in absolute value
				s8 level_inc = liquid_levels_want[i] - liquid_levels[i];
				if (level_inc < -viscosity || level_inc > viscosity)
					new_node_level = liquid_levels[i] + level_inc/viscosity;
				else if (level_inc < 0)
					new_node_level = liquid_levels[i] - 1;
				else if (level_inc > 0)
					new_node_level = liquid_levels[i] + 1;
			} else {
			*/

			// last level must flow down on stairs
			if (liquid_levels_want[i] != liquid_levels[i] &&
			        liquid_levels[D_TOP] <= 0 && (!neighbors[D_BOTTOM].liquid || level_max == 1) &&
			        liquid_levels_want[i] >= 1 && liquid_levels_want[i] <= 2) {
				for (u16 ir = D_SELF + 1; ir < D_TOP; ++ir) { // only same level
					u16 ii = liquid_random_map[(loopcount + loop_rand + 5) % 4][ir];
					if (neighbors[ii].liquid)
						must_reflow_second.push_back(neighbors[i].pos + liquid_flow_dirs[ii]);
					//must_reflow_second[neighbors[i].pos + liquid_flow_dirs[ii]] = 1;
				}
			}

#if LIQUID_DEBUG
			if (liquid_levels_want[i] > 0)
				flowed += liquid_levels_want[i];
#endif
			if (liquid_levels[i] == liquid_levels_want[i]) {
				continue;
			}

			if (neighbors[i].drop) {// && level_max > 1 && total_level >= level_max - 1
				m_server->getEnv().getScriptIface()->node_drop(neighbors[i].pos, 2);
			}

			neighbors[i].node.setContent(liquid_kind_flowing);
			neighbors[i].node.setLevel(nodemgr, liquid_levels_want[i], 1);

			try {
				setNode(neighbors[i].pos, neighbors[i].node);
			} catch(InvalidPositionException &e) {
				verbosestream << "transformLiquidsReal: setNode() failed:" << neighbors[i].pos << ":" << e.what() << std::endl;
			}

			// If node emits light, MapBlock requires lighting update
			// or if node removed
			v3POS blockpos = getNodeBlockPos(neighbors[i].pos);
			MapBlock *block = getBlockNoCreateNoEx(blockpos, true); // remove true if light bugs
			if(block != NULL) {
				//modified_blocks[blockpos] = block;
				if(!nodemgr->get(neighbors[i].node).light_propagates || nodemgr->get(neighbors[i].node).light_source) // better to update always
					lighting_modified_blocks.set_try(block->getPos(), block);
			}
			// fmtodo: make here random %2 or..
			if (total_level < level_max * can_liquid)
				must_reflow.push_back(neighbors[i].pos);

		}

#if LIQUID_DEBUG
		//if (total_was != flowed) {
		if (total_was > flowed) {
			infostream << " volume changed!  flowed=" << flowed << " total_was=" << total_was << " want_level=" << want_level;
			for (u16 rr = 0; rr <= 6; rr++) {
				infostream << "  i=" << rr << ",b" << (int)liquid_levels[rr] << ",a" << (int)liquid_levels_want[rr];
			}
			infostream << std::endl;
		}
#endif
		/* //for better relax  only same level
		if (changed)  for (u16 ii = D_SELF + 1; ii < D_TOP; ++ii) {
			if (!neighbors[ii].l) continue;
			must_reflow.push_back(p0 + dirs[ii]);
		}*/
		//g_profiler->graphAdd("liquids", 1);
	}

	u32 ret = loopcount >= initial_size ? 0 : transforming_liquid_size();
	if (ret || loopcount > m_liquid_step_flow)
		m_liquid_step_flow += (m_liquid_step_flow > loopcount ? -1 : 1) * (int)loopcount / 10;
	/*
	if (loopcount)
		infostream<<"Map::transformLiquidsReal(): loopcount="<<loopcount<<" initial_size="<<initial_size
		<<" avgflow="<<m_liquid_step_flow
		<<" reflow="<<must_reflow.size()
		<<" reflow_second="<<must_reflow_second.size()
		<<" reflow_third="<<must_reflow_third.size()
		<<" queue="<< transforming_liquid_size()
		<<" per="<< porting::getTimeMs() - (end_ms - max_cycle_ms)
		<<" ret="<<ret<<std::endl;
	*/

	{
		//TimeTaker timer13("transformLiquidsReal() reflow");
		//auto lock = m_transforming_liquid.lock_unique_rec();
		std::lock_guard<std::mutex> lock(m_transforming_liquid_mutex);

		//m_transforming_liquid.insert(must_reflow.begin(), must_reflow.end());
		for (const auto & p : must_reflow)
			m_transforming_liquid.push_back(p);
		must_reflow.clear();
		//m_transforming_liquid.insert(must_reflow_second.begin(), must_reflow_second.end());
		for (const auto & p : must_reflow_second)
			m_transforming_liquid.push_back(p);
		must_reflow_second.clear();
		//m_transforming_liquid.insert(must_reflow_third.begin(), must_reflow_third.end());
		for (const auto & p : must_reflow_third)
			m_transforming_liquid.push_back(p);
		must_reflow_third.clear();
	}

	g_profiler->add("Server: liquids real processed", loopcount);
	if (regenerated)
		g_profiler->add("Server: liquids regenerated", regenerated);
	if (loopcount < initial_size)
		g_profiler->add("Server: liquids queue", initial_size);

	return loopcount;
}