code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "clientmedia.h"
#include "httpfetch.h"
#include "client.h"
#include "filecache.h"
#include "filesys.h"
#include "debug.h"
#include "log.h"
#include "porting.h"
#include "settings.h"
#include "network/networkprotocol.h"
#include "util/hex.h"
#include "util/serialize.h"
#include "util/sha1.h"
#include "util/string.h"

static std::string getMediaCacheDir()
{
	return porting::path_cache + DIR_DELIM + "media";
}

/*
	ClientMediaDownloader
*/

ClientMediaDownloader::ClientMediaDownloader():
	m_media_cache(getMediaCacheDir()),
	m_initial_step_done(false),
	m_uncached_count(0),
	m_uncached_received_count(0),
	m_name_bound("")
{
	m_httpfetch_caller = HTTPFETCH_DISCARD;
	m_httpfetch_active = 0;
	m_httpfetch_active_limit = 0;
	m_httpfetch_next_id = 0;
	m_httpfetch_timeout = 0;
	m_outstanding_hash_sets = 0;
}

ClientMediaDownloader::~ClientMediaDownloader()
{
	if (m_httpfetch_caller != HTTPFETCH_DISCARD)
		httpfetch_caller_free(m_httpfetch_caller);

	for (std::map<std::string, FileStatus*>::iterator it = m_files.begin();
			it != m_files.end(); ++it)
		delete it->second;

	for (u32 i = 0; i < m_remotes.size(); ++i)
		delete m_remotes[i];
}

void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
{
	assert(!m_initial_step_done); // pre-condition

	// if name was already announced, ignore the new announcement
	if (m_files.count(name) != 0) {
		errorstream << "Client: ignoring duplicate media announcement "
				<< "sent by server: \"" << name << "\""
				<< std::endl;
		return;
	}

	// if name is empty or contains illegal characters, ignore the file
	if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
		errorstream << "Client: ignoring illegal file name "
				<< "sent by server: \"" << name << "\""
				<< std::endl;
		return;
	}

	// length of sha1 must be exactly 20 (160 bits), else ignore the file
	if (sha1.size() != 20) {
		errorstream << "Client: ignoring illegal SHA1 sent by server: "
				<< hex_encode(sha1) << " \"" << name << "\""
				<< std::endl;
		return;
	}

	FileStatus *filestatus = new FileStatus;
	filestatus->received = false;
	filestatus->sha1 = sha1;
	filestatus->current_remote = -1;
	m_files.insert(std::make_pair(name, filestatus));
}

void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
{
	assert(!m_initial_step_done);	// pre-condition

	#ifdef USE_CURL

	if (g_settings->getBool("enable_remote_media_server")) {
		infostream << "Client: Adding remote server \""
			<< baseurl << "\" for media download" << std::endl;

		RemoteServerStatus *remote = new RemoteServerStatus;
		remote->baseurl = baseurl;
		remote->active_count = 0;
		remote->request_by_filename = false;
		m_remotes.push_back(remote);
	}

	#else

	infostream << "Client: Ignoring remote server \""
		<< baseurl << "\" because cURL support is not compiled in"
		<< std::endl;

	#endif
}

void ClientMediaDownloader::step(Client *client)
{
	if (!m_initial_step_done) {
		initialStep(client);
		m_initial_step_done = true;
	}

	// Remote media: check for completion of fetches
	if (m_httpfetch_active) {
		bool fetched_something = false;
		HTTPFetchResult fetch_result;

		while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
			m_httpfetch_active--;
			fetched_something = true;

			// Is this a hashset (index.mth) or a media file?
			if (fetch_result.request_id < m_remotes.size())
				remoteHashSetReceived(fetch_result);
			else
				remoteMediaReceived(fetch_result, client);
		}

		if (fetched_something)
			startRemoteMediaTransfers();

		// Did all remote transfers end and no new ones can be started?
		// If so, request still missing files from the minetest server
		// (Or report that we have all files.)
		if (m_httpfetch_active == 0) {
			if (m_uncached_received_count < m_uncached_count) {
				infostream << "Client: Failed to remote-fetch "
					<< (m_uncached_count-m_uncached_received_count)
					<< " files. Requesting them"
					<< " the usual way." << std::endl;
			}
			startConventionalTransfers(client);
		}
	}
}

void ClientMediaDownloader::initialStep(Client *client)
{
	// Check media cache
	m_uncached_count = m_files.size();
	for (std::map<std::string, FileStatus*>::iterator
			it = m_files.begin();
			it != m_files.end(); ++it) {
		std::string name = it->first;
		FileStatus *filestatus = it->second;
		const std::string &sha1 = filestatus->sha1;

		std::ostringstream tmp_os(std::ios_base::binary);
		bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);

		// If found in cache, try to load it from there
		if (found_in_cache) {
			bool success = checkAndLoad(name, sha1,
					tmp_os.str(), true, client);
			if (success) {
				filestatus->received = true;
				m_uncached_count--;
			}
		}
	}

	assert(m_uncached_received_count == 0);

	// Create the media cache dir if we are likely to write to it
	if (m_uncached_count != 0) {
		bool did = fs::CreateAllDirs(getMediaCacheDir());
		if (!did) {
			errorstream << "Client: "
				<< "Could not create media cache directory: "
				<< getMediaCacheDir()
				<< std::endl;
		}
	}

	// If we found all files in the cache, report this fact to the server.
	// If the server reported no remote servers, immediately start
	// conventional transfers. Note: if cURL support is not compiled in,
	// m_remotes is always empty, so "!USE_CURL" is redundant but may
	// reduce the size of the compiled code
	if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
		startConventionalTransfers(client);
	}
	else {
		// Otherwise start off by requesting each server's sha1 set

		// This is the first time we use httpfetch, so alloc a caller ID
		m_httpfetch_caller = httpfetch_caller_alloc();
		m_httpfetch_timeout = g_settings->getS32("curl_timeout");

		// Set the active fetch limit to curl_parallel_limit or 84,
		// whichever is greater. This gives us some leeway so that
		// inefficiencies in communicating with the httpfetch thread
		// don't slow down fetches too much. (We still want some limit
		// so that when the first remote server returns its hash set,
		// not all files are requested from that server immediately.)
		// One such inefficiency is that ClientMediaDownloader::step()
		// is only called a couple times per second, while httpfetch
		// might return responses much faster than that.
		// Note that httpfetch strictly enforces curl_parallel_limit
		// but at no inter-thread communication cost. This however
		// doesn't help with the aforementioned inefficiencies.
		// The signifance of 84 is that it is 2*6*9 in base 13.
		m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
		m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);

		// Write a list of hashes that we need. This will be POSTed
		// to the server using Content-Type: application/octet-stream
		std::string required_hash_set = serializeRequiredHashSet();

		// minor fixme: this loop ignores m_httpfetch_active_limit

		// another minor fixme, unlikely to matter in normal usage:
		// these index.mth fetches do (however) count against
		// m_httpfetch_active_limit when starting actual media file
		// requests, so if there are lots of remote servers that are
		// not responding, those will stall new media file transfers.

		for (u32 i = 0; i < m_remotes.size(); ++i) {
			assert(m_httpfetch_next_id == i);

			RemoteServerStatus *remote = m_remotes[i];
			actionstream << "Client: Contacting remote server \""
				<< remote->baseurl << "\"" << std::endl;

			HTTPFetchRequest fetch_request;
			fetch_request.url =
				remote->baseurl + MTHASHSET_FILE_NAME;
			fetch_request.caller = m_httpfetch_caller;
			fetch_request.request_id = m_httpfetch_next_id; // == i
			fetch_request.timeout = m_httpfetch_timeout;
			fetch_request.connect_timeout = m_httpfetch_timeout;
			fetch_request.post_data = required_hash_set;
			fetch_request.extra_headers.push_back(
				"Content-Type: application/octet-stream");
			httpfetch_async(fetch_request);

			m_httpfetch_active++;
			m_httpfetch_next_id++;
			m_outstanding_hash_sets++;
		}
	}
}

void ClientMediaDownloader::remoteHashSetReceived(
		const HTTPFetchResult &fetch_result)
{
	u32 remote_id = fetch_result.request_id;
	assert(remote_id < m_remotes.size());
	RemoteServerStatus *remote = m_remotes[remote_id];

	m_outstanding_hash_sets--;

	if (fetch_result.succeeded) {
		try {
			// Server sent a list of file hashes that are
			// available on it, try to parse the list

			std::set<std::string> sha1_set;
			deSerializeHashSet(fetch_result.data, sha1_set);

			// Parsing succeeded: For every file that is
			// available on this server, add this server
			// to the available_remotes array

			for(std::map<std::string, FileStatus*>::iterator
					it = m_files.upper_bound(m_name_bound);
					it != m_files.end(); ++it) {
				FileStatus *f = it->second;
				if (!f->received && sha1_set.count(f->sha1))
					f->available_remotes.push_back(remote_id);
			}
		}
		catch (SerializationError &e) {
			infostream << "Client: Remote server \""
				<< remote->baseurl << "\" sent invalid hash set: "
				<< e.what() << std::endl;
		}
	}

	// For compatibility: If index.mth is not found, assume that the
	// server contains files named like the original files (not their sha1)

	// Do NOT check for any particular response code (e.g. 404) here,
	// because different servers respond differently

	if (!fetch_result.succeeded && !fetch_result.timeout) {
		infostream << "Client: Enabling compatibility mode for remote "
			<< "server \"" << remote->baseurl << "\"" << std::endl;
		remote->request_by_filename = true;

		// Assume every file is available on this server

		for(std::map<std::string, FileStatus*>::iterator
				it = m_files.upper_bound(m_name_bound);
				it != m_files.end(); ++it) {
			FileStatus *f = it->second;
			if (!f->received)
				f->available_remotes.push_back(remote_id);
		}
	}
}

void ClientMediaDownloader::remoteMediaReceived(
		const HTTPFetchResult &fetch_result,
		Client *client)
{
	// Some remote server sent us a file.
	// -> decrement number of active fetches
	// -> mark file as received if fetch succeeded
	// -> try to load media

	std::string name;
	{
		UNORDERED_MAP<unsigned long, std::string>::iterator it =
			m_remote_file_transfers.find(fetch_result.request_id);
		assert(it != m_remote_file_transfers.end());
		name = it->second;
		m_remote_file_transfers.erase(it);
	}

	sanity_check(m_files.count(name) != 0);

	FileStatus *filestatus = m_files[name];
	sanity_check(!filestatus->received);
	sanity_check(filestatus->current_remote >= 0);

	RemoteServerStatus *remote = m_remotes[filestatus->current_remote];

	filestatus->current_remote = -1;
	remote->active_count--;

	// If fetch succeeded, try to load media file

	if (fetch_result.succeeded) {
		bool success = checkAndLoad(name, filestatus->sha1,
				fetch_result.data, false, client);
		if (success) {
			filestatus->received = true;
			assert(m_uncached_received_count < m_uncached_count);
			m_uncached_received_count++;
		}
	}
}

s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
{
	// Pre-conditions
	assert(filestatus != NULL);
	assert(!filestatus->received);
	assert(filestatus->current_remote < 0);

	if (filestatus->available_remotes.empty())
		return -1;
	else {
		// Of all servers that claim to provide the file (and haven't
		// been unsuccessfully tried before), find the one with the
		// smallest number of currently active transfers

		s32 best = 0;
		s32 best_remote_id = filestatus->available_remotes[best];
		s32 best_active_count = m_remotes[best_remote_id]->active_count;

		for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
			s32 remote_id = filestatus->available_remotes[i];
			s32 active_count = m_remotes[remote_id]->active_count;
			if (active_count < best_active_count) {
				best = i;
				best_remote_id = remote_id;
				best_active_count = active_count;
			}
		}

		filestatus->available_remotes.erase(
				filestatus->available_remotes.begin() + best);

		return best_remote_id;
	}
}

void ClientMediaDownloader::startRemoteMediaTransfers()
{
	bool changing_name_bound = true;

	for (std::map<std::string, FileStatus*>::iterator
			files_iter = m_files.upper_bound(m_name_bound);
			files_iter != m_files.end(); ++files_iter) {

		// Abort if active fetch limit is exceeded
		if (m_httpfetch_active >= m_httpfetch_active_limit)
			break;

		const std::string &name = files_iter->first;
		FileStatus *filestatus = files_iter->second;

		if (!filestatus->received && filestatus->current_remote < 0) {
			// File has not been received yet and is not currently
			// being transferred. Choose a server for it.
			s32 remote_id = selectRemoteServer(filestatus);
			if (remote_id >= 0) {
				// Found a server, so start fetching
				RemoteServerStatus *remote =
					m_remotes[remote_id];

				std::string url = remote->baseurl +
					(remote->request_by_filename ? name :
					hex_encode(filestatus->sha1));
				verbosestream << "Client: "
					<< "Requesting remote media file "
					<< "\"" << name << "\" "
					<< "\"" << url << "\"" << std::endl;

				HTTPFetchRequest fetch_request;
				fetch_request.url = url;
				fetch_request.caller = m_httpfetch_caller;
				fetch_request.request_id = m_httpfetch_next_id;
				fetch_request.timeout = 0; // no data timeout!
				fetch_request.connect_timeout =
					m_httpfetch_timeout;
				httpfetch_async(fetch_request);

				m_remote_file_transfers.insert(std::make_pair(
							m_httpfetch_next_id,
							name));

				filestatus->current_remote = remote_id;
				remote->active_count++;
				m_httpfetch_active++;
				m_httpfetch_next_id++;
			}
		}

		if (filestatus->received ||
				(filestatus->current_remote < 0 &&
				 !m_outstanding_hash_sets)) {
			// If we arrive here, we conclusively know that we
			// won't fetch this file from a remote server in the
			// future. So update the name bound if possible.
			if (changing_name_bound)
				m_name_bound = name;
		}
		else
			changing_name_bound = false;
	}

}

void ClientMediaDownloader::startConventionalTransfers(Client *client)
{
	assert(m_httpfetch_active == 0);	// pre-condition

	if (m_uncached_received_count != m_uncached_count) {
		// Some media files have not been received yet, use the
		// conventional slow method (minetest protocol) to get them
		std::vector<std::string> file_requests;
		for (std::map<std::string, FileStatus*>::iterator
				it = m_files.begin();
				it != m_files.end(); ++it) {
			if (!it->second->received)
				file_requests.push_back(it->first);
		}
		assert((s32) file_requests.size() ==
				m_uncached_count - m_uncached_received_count);
		client->request_media(file_requests);
	}
}

void ClientMediaDownloader::conventionalTransferDone(
		const std::string &name,
		const std::string &data,
		Client *client)
{
	// Check that file was announced
	std::map<std::string, FileStatus*>::iterator
		file_iter = m_files.find(name);
	if (file_iter == m_files.end()) {
		errorstream << "Client: server sent media file that was"
			<< "not announced, ignoring it: \"" << name << "\""
			<< std::endl;
		return;
	}
	FileStatus *filestatus = file_iter->second;
	assert(filestatus != NULL);

	// Check that file hasn't already been received
	if (filestatus->received) {
		errorstream << "Client: server sent media file that we already"
			<< "received, ignoring it: \"" << name << "\""
			<< std::endl;
		return;
	}

	// Mark file as received, regardless of whether loading it works and
	// whether the checksum matches (because at this point there is no
	// other server that could send a replacement)
	filestatus->received = true;
	assert(m_uncached_received_count < m_uncached_count);
	m_uncached_received_count++;

	// Check that received file matches announced checksum
	// If so, load it
	checkAndLoad(name, filestatus->sha1, data, false, client);
}

bool ClientMediaDownloader::checkAndLoad(
		const std::string &name, const std::string &sha1,
		const std::string &data, bool is_from_cache, Client *client)
{
	const char *cached_or_received = is_from_cache ? "cached" : "received";
	const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
	std::string sha1_hex = hex_encode(sha1);

	// Compute actual checksum of data
	std::string data_sha1;
	{
		SHA1 data_sha1_calculator;
		data_sha1_calculator.addBytes(data.c_str(), data.size());
		unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
		data_sha1.assign((char*) data_tmpdigest, 20);
		free(data_tmpdigest);
	}

	// Check that received file matches announced checksum
	if (data_sha1 != sha1) {
		std::string data_sha1_hex = hex_encode(data_sha1);
		infostream << "Client: "
			<< cached_or_received_uc << " media file "
			<< sha1_hex << " \"" << name << "\" "
			<< "mismatches actual checksum " << data_sha1_hex
			<< std::endl;
		return false;
	}

	// Checksum is ok, try loading the file
	bool success = client->loadMedia(data, name);
	if (!success) {
		infostream << "Client: "
			<< "Failed to load " << cached_or_received << " media: "
			<< sha1_hex << " \"" << name << "\""
			<< std::endl;
		return false;
	}

	verbosestream << "Client: "
		<< "Loaded " << cached_or_received << " media: "
		<< sha1_hex << " \"" << name << "\""
		<< std::endl;

	// Update cache (unless we just loaded the file from the cache)
	if (!is_from_cache)
		m_media_cache.update(sha1_hex, data);

	return true;
}

/*
	Minetest Hashset File Format

	All values are stored in big-endian byte order.
	[u32] signature: 'MTHS'
	[u16] version: 1
	For each hash in set:
		[u8*20] SHA1 hash

	Version changes:
	1 - Initial version
*/

std::string ClientMediaDownloader::serializeRequiredHashSet()
{
	std::ostringstream os(std::ios::binary);

	writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
	writeU16(os, 1);                        // version

	// Write list of hashes of files that have not been
	// received (found in cache) yet
	for (std::map<std::string, FileStatus*>::iterator
			it = m_files.begin();
			it != m_files.end(); ++it) {
		if (!it->second->received) {
			FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
			os << it->second->sha1;
		}
	}

	return os.str();
}

void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
		std::set<std::string> &result)
{
	if (data.size() < 6 || data.size() % 20 != 6) {
		throw SerializationError(
				"ClientMediaDownloader::deSerializeHashSet: "
				"invalid hash set file size");
	}

	const u8 *data_cstr = (const u8*) data.c_str();

	u32 signature = readU32(&data_cstr[0]);
	if (signature != MTHASHSET_FILE_SIGNATURE) {
		throw SerializationError(
				"ClientMediaDownloader::deSerializeHashSet: "
				"invalid hash set file signature");
	}

	u16 version = readU16(&data_cstr[4]);
	if (version != 1) {
		throw SerializationError(
				"ClientMediaDownloader::deSerializeHashSet: "
				"unsupported hash set file version");
	}

	for (u32 pos = 6; pos < data.size(); pos += 20) {
		result.insert(data.substr(pos, 20));
	}
}


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CLIENTMEDIA_HEADER
#define CLIENTMEDIA_HEADER

#include "irrlichttypes.h"
#include "filecache.h"
#include <ostream>
#include <map>
#include <set>
#include <vector>
#include "util/cpp11_container.h"

class Client;
struct HTTPFetchResult;

#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
#define MTHASHSET_FILE_NAME "index.mth"

class ClientMediaDownloader
{
public:
	ClientMediaDownloader();
	~ClientMediaDownloader();

	float getProgress() const {
		if (m_uncached_count >= 1)
			return 1.0 * m_uncached_received_count /
				m_uncached_count;
		else
			return 0.0;
	}

	bool isStarted() const {
		return m_initial_step_done;
	}

	// If this returns true, the downloader is done and can be deleted
	bool isDone() const {
		return m_initial_step_done &&
			m_uncached_received_count == m_uncached_count;
	}

	// Add a file to the list of required file (but don't fetch it yet)
	void addFile(const std::string &name, const std::string &sha1);

	// Add a remote server to the list; ignored if not built with cURL
	void addRemoteServer(const std::string &baseurl);

	// Steps the media downloader:
	// - May load media into client by calling client->loadMedia()
	// - May check media cache for files
	// - May add files to media cache
	// - May start remote transfers by calling httpfetch_async
	// - May check for completion of current remote transfers
	// - May start conventional transfers by calling client->request_media()
	// - May inform server that all media has been loaded
	//   by calling client->received_media()
	// After step has been called once, don't call addFile/addRemoteServer.
	void step(Client *client);

	// Must be called for each file received through TOCLIENT_MEDIA
	void conventionalTransferDone(
			const std::string &name,
			const std::string &data,
			Client *client);

private:
	struct FileStatus {
		bool received;
		std::string sha1;
		s32 current_remote;
		std::vector<s32> available_remotes;
	};

	struct RemoteServerStatus {
		std::string baseurl;
		s32 active_count;
		bool request_by_filename;
	};

	void initialStep(Client *client);
	void remoteHashSetReceived(const HTTPFetchResult &fetch_result);
	void remoteMediaReceived(const HTTPFetchResult &fetch_result,
			Client *client);
	s32 selectRemoteServer(FileStatus *filestatus);
	void startRemoteMediaTransfers();
	void startConventionalTransfers(Client *client);

	bool checkAndLoad(const std::string &name, const std::string &sha1,
			const std::string &data, bool is_from_cache,
			Client *client);

	std::string serializeRequiredHashSet();
	static void deSerializeHashSet(const std::string &data,
			std::set<std::string> &result);

	// Maps filename to file status
	std::map<std::string, FileStatus*> m_files;

	// Array of remote media servers
	std::vector<RemoteServerStatus*> m_remotes;

	// Filesystem-based media cache
	FileCache m_media_cache;

	// Has an attempt been made to load media files from the file cache?
	// Have hash sets been requested from remote servers?
	bool m_initial_step_done;

	// Total number of media files to load
	s32 m_uncached_count;

	// Number of media files that have been received
	s32 m_uncached_received_count;

	// Status of remote transfers
	unsigned long m_httpfetch_caller;
	unsigned long m_httpfetch_next_id;
	long m_httpfetch_timeout;
	s32 m_httpfetch_active;
	s32 m_httpfetch_active_limit;
	s32 m_outstanding_hash_sets;
	UNORDERED_MAP<unsigned long, std::string> m_remote_file_transfers;

	// All files up to this name have either been received from a
	// remote server or failed on all remote servers, so those files
	// don't need to be looked at again
	// (use m_files.upper_bound(m_name_bound) to get an iterator)
	std::string m_name_bound;

};

#endif // !CLIENTMEDIA_HEADER

repo_name: pgimeno/minetest

license: unknown


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "clientobject.h"
#include "debug.h"
#include "porting.h"

/*
	ClientActiveObject
*/

ClientActiveObject::ClientActiveObject(u16 id, Client *client,
		ClientEnvironment *env):
	ActiveObject(id),
	m_client(client),
	m_env(env)
{
}

ClientActiveObject::~ClientActiveObject()
{
	removeFromScene(true);
}

ClientActiveObject* ClientActiveObject::create(ActiveObjectType type,
		Client *client, ClientEnvironment *env)
{
	// Find factory function
	UNORDERED_MAP<u16, Factory>::iterator n = m_types.find(type);
	if(n == m_types.end()) {
		// If factory is not found, just return.
		warningstream << "ClientActiveObject: No factory for type="
				<< (int)type << std::endl;
		return NULL;
	}

	Factory f = n->second;
	ClientActiveObject *object = (*f)(client, env);
	return object;
}

void ClientActiveObject::registerType(u16 type, Factory f)
{
	UNORDERED_MAP<u16, Factory>::iterator n = m_types.find(type);
	if(n != m_types.end())
		return;
	m_types[type] = f;
}


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CLIENTOBJECT_HEADER
#define CLIENTOBJECT_HEADER

#include "irrlichttypes_extrabloated.h"
#include "activeobject.h"
#include <map>
#include "util/cpp11_container.h"

class ClientEnvironment;
class ITextureSource;
class Client;
class IGameDef;
class LocalPlayer;
struct ItemStack;
class WieldMeshSceneNode;

class ClientActiveObject : public ActiveObject
{
public:
	ClientActiveObject(u16 id, Client *client, ClientEnvironment *env);
	virtual ~ClientActiveObject();

	virtual void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc,
			IrrlichtDevice *irr){}
	virtual void removeFromScene(bool permanent){}
	// 0 <= light_at_pos <= LIGHT_SUN
	virtual void updateLight(u8 light_at_pos){}
	virtual void updateLightNoCheck(u8 light_at_pos){}
	virtual v3s16 getLightPosition(){return v3s16(0,0,0);}
	virtual aabb3f *getSelectionBox() { return NULL; }
	virtual bool getCollisionBox(aabb3f *toset) const { return false; }
	virtual bool collideWithObjects() const { return false; }
	virtual v3f getPosition(){ return v3f(0,0,0); }
	virtual float getYaw() const { return 0; }
	virtual scene::ISceneNode *getSceneNode() { return NULL; }
	virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; }
	virtual bool isLocalPlayer() const {return false;}
	virtual void setAttachments() {}
	virtual bool doShowSelectionBox(){return true;}

	// Step object in time
	virtual void step(float dtime, ClientEnvironment *env){}

	// Process a message sent by the server side object
	virtual void processMessage(const std::string &data){}

	virtual std::string infoText() {return "";}
	virtual std::string debugInfoText() {return "";}

	/*
		This takes the return value of
		ServerActiveObject::getClientInitializationData
	*/
	virtual void initialize(const std::string &data){}

	// Create a certain type of ClientActiveObject
	static ClientActiveObject* create(ActiveObjectType type, Client *client,
			ClientEnvironment *env);

	// If returns true, punch will not be sent to the server
	virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
			float time_from_last_punch=1000000)
	{ return false; }

protected:
	// Used for creating objects based on type
	typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env);
	static void registerType(u16 type, Factory f);
	Client *m_client;
	ClientEnvironment *m_env;
private:
	// Used for creating objects based on type
	static UNORDERED_MAP<u16, Factory> m_types;
};

struct DistanceSortedActiveObject
{
	ClientActiveObject *obj;
	f32 d;

	DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d)
	{
		obj = a_obj;
		d = a_d;
	}

	bool operator < (const DistanceSortedActiveObject &other) const
	{
		return d < other.d;
	}
};

#endif


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CLIENTSIMPLEOBJECT_HEADER
#define CLIENTSIMPLEOBJECT_HEADER

#include "irrlichttypes_bloated.h"
class ClientEnvironment;

class ClientSimpleObject
{
protected:
public:
	bool m_to_be_removed;

	ClientSimpleObject() : m_to_be_removed(false) {}
	virtual ~ClientSimpleObject() {}
	virtual void step(float dtime) {}
};

#endif


code:
/*
Minetest
Copyright (C) 2017 bendeutsch, Ben Deutsch <ben@bendeutsch.de>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CLOUDPARAMS_HEADER
#define CLOUDPARAMS_HEADER

struct CloudParams
{
	float density;
	video::SColor color_bright;
	video::SColor color_ambient;
	float thickness;
	float height;
	v2f speed;
};

#endif


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "clouds.h"
#include "noise.h"
#include "constants.h"
#include "debug.h"
#include "profiler.h"
#include "settings.h"

// Menu clouds are created later
class Clouds;
Clouds *g_menuclouds = NULL;
irr::scene::ISceneManager *g_menucloudsmgr = NULL;

static void cloud_3d_setting_changed(const std::string &settingname, void *data)
{
	// TODO: only re-read cloud settings, not height or radius
	((Clouds *)data)->readSettings();
}

Clouds::Clouds(
		scene::ISceneNode* parent,
		scene::ISceneManager* mgr,
		s32 id,
		u32 seed,
		s16 cloudheight
):
	scene::ISceneNode(parent, mgr, id),
	m_seed(seed),
	m_camera_pos(0.0f, 0.0f),
	m_origin(0.0f, 0.0f),
	m_camera_offset(0.0f, 0.0f, 0.0f),
	m_color(1.0f, 1.0f, 1.0f, 1.0f)
{
	m_material.setFlag(video::EMF_LIGHTING, false);
	//m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
	m_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
	m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
	m_material.setFlag(video::EMF_FOG_ENABLE, true);
	m_material.setFlag(video::EMF_ANTI_ALIASING, true);
	//m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
	m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;

	m_params.density       = 0.4f;
	m_params.thickness     = 16.0f;
	m_params.color_bright  = video::SColor(229, 240, 240, 255);
	m_params.color_ambient = video::SColor(255, 0, 0, 0);
	m_params.speed         = v2f(0.0f, -2.0f);

	m_passed_cloud_y = cloudheight;
	readSettings();
	g_settings->registerChangedCallback("enable_3d_clouds",
		&cloud_3d_setting_changed, this);

	updateBox();
}

Clouds::~Clouds()
{
	g_settings->deregisterChangedCallback("enable_3d_clouds",
		&cloud_3d_setting_changed, this);
}

void Clouds::OnRegisterSceneNode()
{
	if(IsVisible)
	{
		SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
		//SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
	}

	ISceneNode::OnRegisterSceneNode();
}

#define MYROUND(x) (x > 0.0 ? (int)x : (int)x - 1)

void Clouds::render()
{

	if (m_params.density <= 0.0f)
		return; // no need to do anything

	video::IVideoDriver* driver = SceneManager->getVideoDriver();

	if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT)
	//if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID)
		return;

	ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG);

	int num_faces_to_draw = m_enable_3d ? 6 : 1;

	m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d);

	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
	driver->setMaterial(m_material);

	/*
		Clouds move from Z+ towards Z-
	*/

	static const float cloud_size = BS * 64.0f;

	const float cloud_full_radius = cloud_size * m_cloud_radius_i;

	// Position of cloud noise origin from the camera
	v2f cloud_origin_from_camera_f = m_origin - m_camera_pos;
	// The center point of drawing in the noise
	v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f;
	// The integer center point of drawing in the noise
	v2s16 center_of_drawing_in_noise_i(
		MYROUND(center_of_drawing_in_noise_f.X / cloud_size),
		MYROUND(center_of_drawing_in_noise_f.Y / cloud_size)
	);
	// The world position of the integer center point of drawing in the noise
	v2f world_center_of_drawing_in_noise_f = v2f(
		center_of_drawing_in_noise_i.X * cloud_size,
		center_of_drawing_in_noise_i.Y * cloud_size
	) + m_origin;

	/*video::SColor c_top(128,b*240,b*240,b*255);
	video::SColor c_side_1(128,b*230,b*230,b*255);
	video::SColor c_side_2(128,b*220,b*220,b*245);
	video::SColor c_bottom(128,b*205,b*205,b*230);*/
	video::SColorf c_top_f(m_color);
	video::SColorf c_side_1_f(m_color);
	video::SColorf c_side_2_f(m_color);
	video::SColorf c_bottom_f(m_color);
	c_side_1_f.r *= 0.95;
	c_side_1_f.g *= 0.95;
	c_side_1_f.b *= 0.95;
	c_side_2_f.r *= 0.90;
	c_side_2_f.g *= 0.90;
	c_side_2_f.b *= 0.90;
	c_bottom_f.r *= 0.80;
	c_bottom_f.g *= 0.80;
	c_bottom_f.b *= 0.80;
	video::SColor c_top = c_top_f.toSColor();
	video::SColor c_side_1 = c_side_1_f.toSColor();
	video::SColor c_side_2 = c_side_2_f.toSColor();
	video::SColor c_bottom = c_bottom_f.toSColor();

	// Get fog parameters for setting them back later
	video::SColor fog_color(0,0,0,0);
	video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR;
	f32 fog_start = 0;
	f32 fog_end = 0;
	f32 fog_density = 0;
	bool fog_pixelfog = false;
	bool fog_rangefog = false;
	driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
			fog_pixelfog, fog_rangefog);

	// Set our own fog
	driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5,
			cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);

	// Read noise

	bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2];

	float cloud_size_noise = cloud_size / BS / 200;

	for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) {
		u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i;

		for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) {
			u32 i = si + xi;

			v2s16 p_in_noise_i(
				xi + center_of_drawing_in_noise_i.X,
				zi + center_of_drawing_in_noise_i.Y
			);

			float noise = noise2d_perlin(
					(float)p_in_noise_i.X * cloud_size_noise,
					(float)p_in_noise_i.Y * cloud_size_noise,
					m_seed, 3, 0.5);
			// normalize to 0..1 (given 3 octaves)
			static const float noise_bound = 1.0f + 0.5f + 0.25f;
			float density = noise / noise_bound * 0.5f + 0.5f;
			grid[i] = (density < m_params.density);
		}
	}

#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius))
#define INAREA(x, z, radius) \
	((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius))

	for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++)
	for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++)
	{
		s16 zi = zi0;
		s16 xi = xi0;
		// Draw from front to back (needed for transparency)
		/*if(zi <= 0)
			zi = -m_cloud_radius_i - zi;
		if(xi <= 0)
			xi = -m_cloud_radius_i - xi;*/
		// Draw from back to front
		if(zi >= 0)
			zi = m_cloud_radius_i - zi - 1;
		if(xi >= 0)
			xi = m_cloud_radius_i - xi - 1;

		u32 i = GETINDEX(xi, zi, m_cloud_radius_i);

		if(grid[i] == false)
			continue;

		v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f;

		video::S3DVertex v[4] = {
			video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1),
			video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1),
			video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0),
			video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0)
		};

		/*if(zi <= 0 && xi <= 0){
			v[0].Color.setBlue(255);
			v[1].Color.setBlue(255);
			v[2].Color.setBlue(255);
			v[3].Color.setBlue(255);
		}*/

		f32 rx = cloud_size / 2.0f;
		// if clouds are flat, the top layer should be at the given height
		f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f;
		f32 rz = cloud_size / 2;

		for(int i=0; i<num_faces_to_draw; i++)
		{
			switch(i)
			{
			case 0:	// top
				for(int j=0;j<4;j++){
					v[j].Normal.set(0,1,0);
				}
				v[0].Pos.set(-rx, ry,-rz);
				v[1].Pos.set(-rx, ry, rz);
				v[2].Pos.set( rx, ry, rz);
				v[3].Pos.set( rx, ry,-rz);
				break;
			case 1: // back
				if (INAREA(xi, zi - 1, m_cloud_radius_i)) {
					u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i);
					if(grid[j])
						continue;
				}
				for(int j=0;j<4;j++){
					v[j].Color = c_side_1;
					v[j].Normal.set(0,0,-1);
				}
				v[0].Pos.set(-rx, ry,-rz);
				v[1].Pos.set( rx, ry,-rz);
				v[2].Pos.set( rx,  0,-rz);
				v[3].Pos.set(-rx,  0,-rz);
				break;
			case 2: //right
				if (INAREA(xi + 1, zi, m_cloud_radius_i)) {
					u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i);
					if(grid[j])
						continue;
				}
				for(int j=0;j<4;j++){
					v[j].Color = c_side_2;
					v[j].Normal.set(1,0,0);
				}
				v[0].Pos.set( rx, ry,-rz);
				v[1].Pos.set( rx, ry, rz);
				v[2].Pos.set( rx,  0, rz);
				v[3].Pos.set( rx,  0,-rz);
				break;
			case 3: // front
				if (INAREA(xi, zi + 1, m_cloud_radius_i)) {
					u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i);
					if(grid[j])
						continue;
				}
				for(int j=0;j<4;j++){
					v[j].Color = c_side_1;
					v[j].Normal.set(0,0,-1);
				}
				v[0].Pos.set( rx, ry, rz);
				v[1].Pos.set(-rx, ry, rz);
				v[2].Pos.set(-rx,  0, rz);
				v[3].Pos.set( rx,  0, rz);
				break;
			case 4: // left
				if (INAREA(xi-1, zi, m_cloud_radius_i)) {
					u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i);
					if(grid[j])
						continue;
				}
				for(int j=0;j<4;j++){
					v[j].Color = c_side_2;
					v[j].Normal.set(-1,0,0);
				}
				v[0].Pos.set(-rx, ry, rz);
				v[1].Pos.set(-rx, ry,-rz);
				v[2].Pos.set(-rx,  0,-rz);
				v[3].Pos.set(-rx,  0, rz);
				break;
			case 5: // bottom
				for(int j=0;j<4;j++){
					v[j].Color = c_bottom;
					v[j].Normal.set(0,-1,0);
				}
				v[0].Pos.set( rx,  0, rz);
				v[1].Pos.set(-rx,  0, rz);
				v[2].Pos.set(-rx,  0,-rz);
				v[3].Pos.set( rx,  0,-rz);
				break;
			}

			v3f pos(p0.X, m_params.height * BS, p0.Y);
			pos -= intToFloat(m_camera_offset, BS);

			for(u16 i=0; i<4; i++)
				v[i].Pos += pos;
			u16 indices[] = {0,1,2,2,3,0};
			driver->drawVertexPrimitiveList(v, 4, indices, 2,
					video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
		}
	}

	delete[] grid;

	// Restore fog settings
	driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density,
			fog_pixelfog, fog_rangefog);
}

void Clouds::step(float dtime)
{
	m_origin = m_origin + dtime * BS * m_params.speed;
}

void Clouds::update(v2f camera_p, video::SColorf color_diffuse)
{
	m_camera_pos = camera_p;
	m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(),
			m_params.color_ambient.getRed()), 255) / 255.0f;
	m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(),
			m_params.color_ambient.getGreen()), 255) / 255.0f;
	m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(),
			m_params.color_ambient.getBlue()), 255) / 255.0f;
	m_color.a = m_params.color_bright.getAlpha() / 255.0f;
}

void Clouds::readSettings()
{
	m_params.height = (m_passed_cloud_y ? m_passed_cloud_y :
		g_settings->getS16("cloud_height"));
	m_cloud_radius_i = g_settings->getU16("cloud_radius");
	m_enable_3d = g_settings->getBool("enable_3d_clouds");
}


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CLOUDS_HEADER
#define CLOUDS_HEADER

#include "irrlichttypes_extrabloated.h"
#include <iostream>
#include "constants.h"
#include "cloudparams.h"

// Menu clouds
class Clouds;
extern Clouds *g_menuclouds;

// Scene manager used for menu clouds
namespace irr{namespace scene{class ISceneManager;}}
extern irr::scene::ISceneManager *g_menucloudsmgr;

class Clouds : public scene::ISceneNode
{
public:
	Clouds(
			scene::ISceneNode* parent,
			scene::ISceneManager* mgr,
			s32 id,
			u32 seed,
			s16 cloudheight=0
	);

	~Clouds();

	/*
		ISceneNode methods
	*/

	virtual void OnRegisterSceneNode();

	virtual void render();

	virtual const aabb3f &getBoundingBox() const
	{
		return m_box;
	}

	virtual u32 getMaterialCount() const
	{
		return 1;
	}

	virtual video::SMaterial& getMaterial(u32 i)
	{
		return m_material;
	}

	/*
		Other stuff
	*/

	void step(float dtime);

	void update(v2f camera_p, video::SColorf color);

	void updateCameraOffset(v3s16 camera_offset)
	{
		m_camera_offset = camera_offset;
		updateBox();
	}

	void readSettings();

	void setDensity(float density)
	{
		m_params.density = density;
		// currently does not need bounding
	}

	void setColorBright(const video::SColor &color_bright)
	{
		m_params.color_bright = color_bright;
	}

	void setColorAmbient(const video::SColor &color_ambient)
	{
		m_params.color_ambient = color_ambient;
	}

	void setHeight(float height)
	{
		m_params.height = height; // add bounding when necessary
		updateBox();
	}

	void setSpeed(v2f speed)
	{
		m_params.speed = speed;
	}

	void setThickness(float thickness)
	{
		m_params.thickness = thickness;
		updateBox();
	}

private:
	void updateBox()
	{
		float height_bs    = m_params.height    * BS;
		float thickness_bs = m_params.thickness * BS;
		m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f,
				BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f);
	}

	video::SMaterial m_material;
	aabb3f m_box;
	s16 m_passed_cloud_y;
	u16 m_cloud_radius_i;
	bool m_enable_3d;
	u32 m_seed;
	v2f m_camera_pos;
	v2f m_origin;
	v2f m_speed;
	v3s16 m_camera_offset;
	video::SColorf m_color;
	CloudParams m_params;

};

#endif


code:
// Filled in by the build system

#ifndef CMAKE_CONFIG_H
#define CMAKE_CONFIG_H

#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_NAME_C "@PROJECT_NAME_CAPITALIZED@"
#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@
#define VERSION_PATCH @VERSION_PATCH@
#define VERSION_EXTRA "@VERSION_EXTRA@"
#define VERSION_STRING "@VERSION_STRING@"
#define PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@"
#define STATIC_SHAREDIR "@SHAREDIR@"
#define STATIC_LOCALEDIR "@LOCALEDIR@"
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define ICON_DIR "@ICONDIR@"
#cmakedefine01 RUN_IN_PLACE
#cmakedefine01 USE_GETTEXT
#cmakedefine01 USE_CURL
#cmakedefine01 USE_SOUND
#cmakedefine01 USE_FREETYPE
#cmakedefine01 USE_CURSES
#cmakedefine01 USE_LEVELDB
#cmakedefine01 USE_LUAJIT
#cmakedefine01 USE_POSTGRESQL
#cmakedefine01 USE_SPATIAL
#cmakedefine01 USE_SYSTEM_GMP
#cmakedefine01 USE_REDIS
#cmakedefine01 HAVE_ENDIAN_H
#cmakedefine01 CURSES_HAVE_CURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_NCURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_CURSES_H
#cmakedefine01 CURSES_HAVE_NCURSESW_NCURSES_H
#cmakedefine01 CURSES_HAVE_NCURSESW_CURSES_H

#endif


code:
// Filled in by the build system
// Separated from cmake_config.h to avoid excessive rebuilds on every commit

#ifndef CMAKE_CONFIG_GITHASH_H
#define CMAKE_CONFIG_GITHASH_H

#define VERSION_GITHASH "@VERSION_GITHASH@"

#endif


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "collision.h"
#include "mapblock.h"
#include "map.h"
#include "nodedef.h"
#include "gamedef.h"
#ifndef SERVER
#include "clientenvironment.h"
#endif
#include "serverenvironment.h"
#include "serverobject.h"
#include "util/timetaker.h"
#include "profiler.h"

// float error is 10 - 9.96875 = 0.03125
//#define COLL_ZERO 0.032 // broken unit tests
#define COLL_ZERO 0

struct NearbyCollisionInfo {
	NearbyCollisionInfo(bool is_ul, bool is_obj, int bouncy,
			const v3s16 &pos, const aabb3f &box) :
		is_unloaded(is_ul),
		is_step_up(false),
		is_object(is_obj),
		bouncy(bouncy),
		position(pos),
		box(box)
	{}

	bool is_unloaded;
	bool is_step_up;
	bool is_object;
	int bouncy;
	v3s16 position;
	aabb3f box;
};

// Helper function:
// Checks for collision of a moving aabbox with a static aabbox
// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
// The time after which the collision occurs is stored in dtime.
int axisAlignedCollision(
		const aabb3f &staticbox, const aabb3f &movingbox,
		const v3f &speed, f32 d, f32 *dtime)
{
	//TimeTaker tt("axisAlignedCollision");

	f32 xsize = (staticbox.MaxEdge.X - staticbox.MinEdge.X) - COLL_ZERO;     // reduce box size for solve collision stuck (flying sand)
	f32 ysize = (staticbox.MaxEdge.Y - staticbox.MinEdge.Y); // - COLL_ZERO; // Y - no sense for falling, but maybe try later
	f32 zsize = (staticbox.MaxEdge.Z - staticbox.MinEdge.Z) - COLL_ZERO;

	aabb3f relbox(
			movingbox.MinEdge.X - staticbox.MinEdge.X,
			movingbox.MinEdge.Y - staticbox.MinEdge.Y,
			movingbox.MinEdge.Z - staticbox.MinEdge.Z,
			movingbox.MaxEdge.X - staticbox.MinEdge.X,
			movingbox.MaxEdge.Y - staticbox.MinEdge.Y,
			movingbox.MaxEdge.Z - staticbox.MinEdge.Z
	);

	if(speed.X > 0) // Check for collision with X- plane
	{
		if (relbox.MaxEdge.X <= d) {
			*dtime = -relbox.MaxEdge.X / speed.X;
			if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
					(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
					(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
				return 0;
		}
		else if(relbox.MinEdge.X > xsize)
		{
			return -1;
		}
	}
	else if(speed.X < 0) // Check for collision with X+ plane
	{
		if (relbox.MinEdge.X >= xsize - d) {
			*dtime = (xsize - relbox.MinEdge.X) / speed.X;
			if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
					(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
					(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
				return 0;
		}
		else if(relbox.MaxEdge.X < 0)
		{
			return -1;
		}
	}

	// NO else if here

	if(speed.Y > 0) // Check for collision with Y- plane
	{
		if (relbox.MaxEdge.Y <= d) {
			*dtime = -relbox.MaxEdge.Y / speed.Y;
			if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
					(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
					(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
				return 1;
		}
		else if(relbox.MinEdge.Y > ysize)
		{
			return -1;
		}
	}
	else if(speed.Y < 0) // Check for collision with Y+ plane
	{
		if (relbox.MinEdge.Y >= ysize - d) {
			*dtime = (ysize - relbox.MinEdge.Y) / speed.Y;
			if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
					(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
					(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
				return 1;
		}
		else if(relbox.MaxEdge.Y < 0)
		{
			return -1;
		}
	}

	// NO else if here

	if(speed.Z > 0) // Check for collision with Z- plane
	{
		if (relbox.MaxEdge.Z <= d) {
			*dtime = -relbox.MaxEdge.Z / speed.Z;
			if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
					(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
					(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
				return 2;
		}
		//else if(relbox.MinEdge.Z > zsize)
		//{
		//	return -1;
		//}
	}
	else if(speed.Z < 0) // Check for collision with Z+ plane
	{
		if (relbox.MinEdge.Z >= zsize - d) {
			*dtime = (zsize - relbox.MinEdge.Z) / speed.Z;
			if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
					(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
					(relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
					(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
				return 2;
		}
		//else if(relbox.MaxEdge.Z < 0)
		//{
		//	return -1;
		//}
	}

	return -1;
}

// Helper function:
// Checks if moving the movingbox up by the given distance would hit a ceiling.
bool wouldCollideWithCeiling(
		const std::vector<NearbyCollisionInfo> &cinfo,
		const aabb3f &movingbox,
		f32 y_increase, f32 d)
{
	//TimeTaker tt("wouldCollideWithCeiling");

	assert(y_increase >= 0);	// pre-condition

	for (std::vector<NearbyCollisionInfo>::const_iterator it = cinfo.begin();
			it != cinfo.end(); ++it) {
		const aabb3f &staticbox = it->box;
		if ((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) &&
				(movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) &&
				(movingbox.MinEdge.X < staticbox.MaxEdge.X) &&
				(movingbox.MaxEdge.X > staticbox.MinEdge.X) &&
				(movingbox.MinEdge.Z < staticbox.MaxEdge.Z) &&
				(movingbox.MaxEdge.Z > staticbox.MinEdge.Z))
			return true;
	}

	return false;
}

static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef,
		Map *map, MapNode n, int v, int *neighbors)
{
	MapNode n2 = map->getNodeNoEx(p);
	if (nodedef->nodeboxConnects(n, n2, v))
		*neighbors |= v;
}

collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
		f32 pos_max_d, const aabb3f &box_0,
		f32 stepheight, f32 dtime,
		v3f *pos_f, v3f *speed_f,
		v3f accel_f, ActiveObject *self,
		bool collideWithObjects)
{
	static bool time_notification_done = false;
	Map *map = &env->getMap();
	//TimeTaker tt("collisionMoveSimple");
	ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);

	collisionMoveResult result;

	/*
		Calculate new velocity
	*/
	if (dtime > 0.5) {
		if (!time_notification_done) {
			time_notification_done = true;
			infostream << "collisionMoveSimple: maximum step interval exceeded,"
					" lost movement details!"<<std::endl;
		}
		dtime = 0.5;
	} else {
		time_notification_done = false;
	}
	*speed_f += accel_f * dtime;

	// If there is no speed, there are no collisions
	if (speed_f->getLength() == 0)
		return result;

	// Limit speed for avoiding hangs
	speed_f->Y = rangelim(speed_f->Y, -5000, 5000);
	speed_f->X = rangelim(speed_f->X, -5000, 5000);
	speed_f->Z = rangelim(speed_f->Z, -5000, 5000);

	/*
		Collect node boxes in movement range
	*/
	std::vector<NearbyCollisionInfo> cinfo;
	{
	//TimeTaker tt2("collisionMoveSimple collect boxes");
	ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG);

	v3f newpos_f = *pos_f + *speed_f * dtime;
	v3f minpos_f(
		MYMIN(pos_f->X, newpos_f.X),
		MYMIN(pos_f->Y, newpos_f.Y) + 0.01 * BS, // bias rounding, player often at +/-n.5
		MYMIN(pos_f->Z, newpos_f.Z)
	);
	v3f maxpos_f(
		MYMAX(pos_f->X, newpos_f.X),
		MYMAX(pos_f->Y, newpos_f.Y),
		MYMAX(pos_f->Z, newpos_f.Z)
	);
	v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1);
	v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1);

	bool any_position_valid = false;

	for(s16 x = min.X; x <= max.X; x++)
	for(s16 y = min.Y; y <= max.Y; y++)
	for(s16 z = min.Z; z <= max.Z; z++)
	{
		v3s16 p(x,y,z);

		bool is_position_valid;
		MapNode n = map->getNodeNoEx(p, &is_position_valid);

		if (is_position_valid) {
			// Object collides into walkable nodes

			any_position_valid = true;
			INodeDefManager *nodedef = gamedef->getNodeDefManager();
			const ContentFeatures &f = nodedef->get(n);
			if(f.walkable == false)
				continue;
			int n_bouncy_value = itemgroup_get(f.groups, "bouncy");

			int neighbors = 0;
			if (f.drawtype == NDT_NODEBOX && f.node_box.type == NODEBOX_CONNECTED) {
				v3s16 p2 = p;

				p2.Y++;
				getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors);

				p2 = p;
				p2.Y--;
				getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors);

				p2 = p;
				p2.Z--;
				getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors);

				p2 = p;
				p2.X--;
				getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors);

				p2 = p;
				p2.Z++;
				getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors);

				p2 = p;
				p2.X++;
				getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors);
			}
			std::vector<aabb3f> nodeboxes;
			n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors);
			for(std::vector<aabb3f>::iterator
					i = nodeboxes.begin();
					i != nodeboxes.end(); ++i)
			{
				aabb3f box = *i;
				box.MinEdge += v3f(x, y, z)*BS;
				box.MaxEdge += v3f(x, y, z)*BS;
				cinfo.push_back(NearbyCollisionInfo(false,
					false, n_bouncy_value, p, box));
			}
		} else {
			// Collide with unloaded nodes
			aabb3f box = getNodeBox(p, BS);
			cinfo.push_back(NearbyCollisionInfo(true, false, 0, p, box));
		}
	}

	// Do not move if world has not loaded yet, since custom node boxes
	// are not available for collision detection.
	if (!any_position_valid) {
		*speed_f = v3f(0, 0, 0);
		return result;
	}

	} // tt2

	if(collideWithObjects)
	{
		ScopeProfiler sp(g_profiler, "collisionMoveSimple objects avg", SPT_AVG);
		//TimeTaker tt3("collisionMoveSimple collect object boxes");

		/* add object boxes to cinfo */

		std::vector<ActiveObject*> objects;
#ifndef SERVER
		ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
		if (c_env != 0) {
			f32 distance = speed_f->getLength();
			std::vector<DistanceSortedActiveObject> clientobjects;
			c_env->getActiveObjects(*pos_f, distance * 1.5, clientobjects);
			for (size_t i=0; i < clientobjects.size(); i++) {
				if ((self == 0) || (self != clientobjects[i].obj)) {
					objects.push_back((ActiveObject*)clientobjects[i].obj);
				}
			}
		}
		else
#endif
		{
			ServerEnvironment *s_env = dynamic_cast<ServerEnvironment*>(env);
			if (s_env != NULL) {
				f32 distance = speed_f->getLength();
				std::vector<u16> s_objects;
				s_env->getObjectsInsideRadius(s_objects, *pos_f, distance * 1.5);
				for (std::vector<u16>::iterator iter = s_objects.begin(); iter != s_objects.end(); ++iter) {
					ServerActiveObject *current = s_env->getActiveObject(*iter);
					if ((self == 0) || (self != current)) {
						objects.push_back((ActiveObject*)current);
					}
				}
			}
		}

		for (std::vector<ActiveObject*>::const_iterator iter = objects.begin();
				iter != objects.end(); ++iter) {
			ActiveObject *object = *iter;

			if (object != NULL) {
				aabb3f object_collisionbox;
				if (object->getCollisionBox(&object_collisionbox) &&
						object->collideWithObjects()) {
					cinfo.push_back(NearbyCollisionInfo(false, true, 0, v3s16(), object_collisionbox));
				}
			}
		}
	} //tt3

	/*
		Collision detection
	*/

	/*
		Collision uncertainty radius
		Make it a bit larger than the maximum distance of movement
	*/
	f32 d = pos_max_d * 1.1;
	// A fairly large value in here makes moving smoother
	//f32 d = 0.15*BS;

	// This should always apply, otherwise there are glitches
	assert(d > pos_max_d);	// invariant

	int loopcount = 0;

	while(dtime > BS * 1e-10) {
		//TimeTaker tt3("collisionMoveSimple dtime loop");
        	ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);

		// Avoid infinite loop
		loopcount++;
		if (loopcount >= 100) {
			warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl;
			break;
		}

		aabb3f movingbox = box_0;
		movingbox.MinEdge += *pos_f;
		movingbox.MaxEdge += *pos_f;

		int nearest_collided = -1;
		f32 nearest_dtime = dtime;
		int nearest_boxindex = -1;

		/*
			Go through every nodebox, find nearest collision
		*/
		for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) {
			NearbyCollisionInfo box_info = cinfo[boxindex];
			// Ignore if already stepped up this nodebox.
			if (box_info.is_step_up)
				continue;

			// Find nearest collision of the two boxes (raytracing-like)
			f32 dtime_tmp;
			int collided = axisAlignedCollision(box_info.box,
					movingbox, *speed_f, d, &dtime_tmp);

			if (collided == -1 || dtime_tmp >= nearest_dtime)
				continue;

			nearest_dtime = dtime_tmp;
			nearest_collided = collided;
			nearest_boxindex = boxindex;
		}

		if (nearest_collided == -1) {
			// No collision with any collision box.
			*pos_f += *speed_f * dtime;
			dtime = 0;  // Set to 0 to avoid "infinite" loop due to small FP numbers
		} else {
			// Otherwise, a collision occurred.
			NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
			const aabb3f& cbox = nearest_info.box;
			// Check for stairs.
			bool step_up = (nearest_collided != 1) && // must not be Y direction
					(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
					(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
					(!wouldCollideWithCeiling(cinfo, movingbox,
							cbox.MaxEdge.Y - movingbox.MinEdge.Y,
							d));

			// Get bounce multiplier
			bool bouncy = (nearest_info.bouncy >= 1);
			float bounce = -(float)nearest_info.bouncy / 100.0;

			// Move to the point of collision and reduce dtime by nearest_dtime
			if (nearest_dtime < 0) {
				// Handle negative nearest_dtime (can be caused by the d allowance)
				if (!step_up) {
					if (nearest_collided == 0)
						pos_f->X += speed_f->X * nearest_dtime;
					if (nearest_collided == 1)
						pos_f->Y += speed_f->Y * nearest_dtime;
					if (nearest_collided == 2)
						pos_f->Z += speed_f->Z * nearest_dtime;
				}
			} else {
				*pos_f += *speed_f * nearest_dtime;
				dtime -= nearest_dtime;
			}

			bool is_collision = true;
			if (nearest_info.is_unloaded)
				is_collision = false;

			CollisionInfo info;
			if (nearest_info.is_object)
				info.type = COLLISION_OBJECT;
			else
				info.type = COLLISION_NODE;

			info.node_p = nearest_info.position;
			info.bouncy = bouncy;
			info.old_speed = *speed_f;

			// Set the speed component that caused the collision to zero
			if (step_up) {
				// Special case: Handle stairs
				nearest_info.is_step_up = true;
				is_collision = false;
			} else if (nearest_collided == 0) { // X
				if (fabs(speed_f->X) > BS * 3)
					speed_f->X *= bounce;
				else
					speed_f->X = 0;
				result.collides = true;
				result.collides_xz = true;
			} else if (nearest_collided == 1) { // Y
				if(fabs(speed_f->Y) > BS * 3)
					speed_f->Y *= bounce;
				else
					speed_f->Y = 0;
				result.collides = true;
			} else if (nearest_collided == 2) { // Z
				if (fabs(speed_f->Z) > BS * 3)
					speed_f->Z *= bounce;
				else
					speed_f->Z = 0;
				result.collides = true;
				result.collides_xz = true;
			}

			info.new_speed = *speed_f;
			if (info.new_speed.getDistanceFrom(info.old_speed) < 0.1 * BS)
				is_collision = false;

			if (is_collision) {
				result.collisions.push_back(info);
			}
		}
	}

	/*
		Final touches: Check if standing on ground, step up stairs.
	*/
	aabb3f box = box_0;
	box.MinEdge += *pos_f;
	box.MaxEdge += *pos_f;
	for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) {
		NearbyCollisionInfo &box_info = cinfo[boxindex];
		const aabb3f &cbox = box_info.box;

		/*
			See if the object is touching ground.

			Object touches ground if object's minimum Y is near node's
			maximum Y and object's X-Z-area overlaps with the node's
			X-Z-area.

			Use 0.15*BS so that it is easier to get on a node.
		*/
		if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X &&
				cbox.MaxEdge.Z - d > box.MinEdge.Z &&
				cbox.MinEdge.Z + d < box.MaxEdge.Z) {
			if (box_info.is_step_up) {
				pos_f->Y += cbox.MaxEdge.Y - box.MinEdge.Y;
				box = box_0;
				box.MinEdge += *pos_f;
				box.MaxEdge += *pos_f;
			}
			if (fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15 * BS) {
				result.touching_ground = true;

				if (box_info.is_object)
					result.standing_on_object = true;
				if (box_info.is_unloaded)
					result.standing_on_unloaded = true;
			}
		}
	}

	return result;
}


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef COLLISION_HEADER
#define COLLISION_HEADER

#include "irrlichttypes_bloated.h"
#include <vector>

class Map;
class IGameDef;
class Environment;
class ActiveObject;

enum CollisionType
{
	COLLISION_NODE,
	COLLISION_OBJECT,
};

struct CollisionInfo
{
	enum CollisionType type;
	v3s16 node_p; // COLLISION_NODE
	bool bouncy;
	v3f old_speed;
	v3f new_speed;

	CollisionInfo():
		type(COLLISION_NODE),
		node_p(-32768,-32768,-32768),
		bouncy(false),
		old_speed(0,0,0),
		new_speed(0,0,0)
	{}
};

struct collisionMoveResult
{
	bool touching_ground;
	bool collides;
	bool collides_xz;
	bool standing_on_unloaded;
	bool standing_on_object;
	std::vector<CollisionInfo> collisions;

	collisionMoveResult():
		touching_ground(false),
		collides(false),
		collides_xz(false),
		standing_on_unloaded(false),
		standing_on_object(false)
	{}
};

// Moves using a single iteration; speed should not exceed pos_max_d/dtime
collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef,
		f32 pos_max_d, const aabb3f &box_0,
		f32 stepheight, f32 dtime,
		v3f *pos_f, v3f *speed_f,
		v3f accel_f, ActiveObject *self=NULL,
		bool collideWithObjects=true);

// Helper function:
// Checks for collision of a moving aabbox with a static aabbox
// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
// dtime receives time until first collision, invalid if -1 is returned
int axisAlignedCollision(
		const aabb3f &staticbox, const aabb3f &movingbox,
		const v3f &speed, f32 d, f32 *dtime);

// Helper function:
// Checks if moving the movingbox up by the given distance would hit a ceiling.
bool wouldCollideWithCeiling(
		const std::vector<aabb3f> &staticboxes,
		const aabb3f &movingbox,
		f32 y_increase, f32 d);

#endif


code:
/*
	If CMake is used, includes the cmake-generated cmake_config.h.
	Otherwise use default values
*/

#ifndef CONFIG_H
#define CONFIG_H

#define STRINGIFY(x) #x
#define STR(x) STRINGIFY(x)

#if defined USE_CMAKE_CONFIG_H
	#include "cmake_config.h"
#elif defined (__ANDROID__) || defined (ANDROID)
	#define PROJECT_NAME "minetest"
	#define PROJECT_NAME_C "Minetest"
	#define STATIC_SHAREDIR ""
	#include "android_version.h"
	#ifdef NDEBUG
		#define BUILD_TYPE "Release"
	#else
		#define BUILD_TYPE "Debug"
	#endif
#else
	#ifdef NDEBUG
		#define BUILD_TYPE "Release"
	#else
		#define BUILD_TYPE "Debug"
	#endif
#endif

#define BUILD_INFO "BUILD_TYPE=" BUILD_TYPE \
		" RUN_IN_PLACE=" STR(RUN_IN_PLACE) \
		" USE_GETTEXT=" STR(USE_GETTEXT) \
		" USE_SOUND=" STR(USE_SOUND) \
		" USE_CURL=" STR(USE_CURL) \
		" USE_FREETYPE=" STR(USE_FREETYPE) \
		" USE_LUAJIT=" STR(USE_LUAJIT) \
		" STATIC_SHAREDIR=" STR(STATIC_SHAREDIR)

#endif


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CONSTANTS_HEADER
#define CONSTANTS_HEADER

/*
	All kinds of constants.

	Cross-platform compatibility crap should go in porting.h.

    Some things here are legacy crap.
*/

/*
    Connection
*/

#define PEER_ID_INEXISTENT 0
#define PEER_ID_SERVER 1

// Define for simulating the quirks of sending through internet.
// Causes the socket class to deliberately drop random packets.
// This disables unit testing of socket and connection.
#define INTERNET_SIMULATOR 0
#define INTERNET_SIMULATOR_PACKET_LOSS 10 // 10 = easy, 4 = hard

#define CONNECTION_TIMEOUT 30

#define RESEND_TIMEOUT_MIN 0.1
#define RESEND_TIMEOUT_MAX 3.0
// resend_timeout = avg_rtt * this
#define RESEND_TIMEOUT_FACTOR 4

/*
    Server
*/

// This many blocks are sent when player is building
#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 0
// Override for the previous one when distance of block is very low
#define BLOCK_SEND_DISABLE_LIMITS_MAX_D 1

/*
    Map-related things
*/

// The absolute working limit is (2^15 - viewing_range).
// I really don't want to make every algorithm to check if it's going near
// the limit or not, so this is lower.
// This is the maximum value the setting map_generation_limit can be
#define MAX_MAP_GENERATION_LIMIT (31000)

// Size of node in floating-point units
// The original idea behind this is to disallow plain casts between
// floating-point and integer positions, which potentially give wrong
// results. (negative coordinates, values between nodes, ...)
// Use floatToInt(p, BS) and intToFloat(p, BS).
#define BS (10.0)

// Dimension of a MapBlock
#define MAP_BLOCKSIZE 16
// This makes mesh updates too slow, as many meshes are updated during
// the main loop (related to TempMods and day/night)
//#define MAP_BLOCKSIZE 32

/*
    Old stuff that shouldn't be hardcoded
*/

// Size of player's main inventory
#define PLAYER_INVENTORY_SIZE (8 * 4)

// Maximum hit points of a player
#define PLAYER_MAX_HP 20

// Maximal breath of a player
#define PLAYER_MAX_BREATH 11

// Number of different files to try to save a player to if the first fails
// (because of a case-insensitive filesystem)
// TODO: Use case-insensitive player names instead of this hack.
#define PLAYER_FILE_ALTERNATE_TRIES 1000

// For screenshots a serial number is appended to the filename + datetimestamp
// if filename + datetimestamp is not unique.
// This is the maximum number of attempts to try and add a serial to the end of
// the file attempting to ensure a unique filename
#define SCREENSHOT_MAX_SERIAL_TRIES 1000

/*
    GUI related things
*/

// TODO: implement dpi-based scaling for windows and remove this hack
#if defined(_WIN32)
#define TTF_DEFAULT_FONT_SIZE (18)
#else
#define TTF_DEFAULT_FONT_SIZE (16)
#endif
#define DEFAULT_FONT_SIZE (10)

#endif


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "content_abm.h"

#include "environment.h"
#include "gamedef.h"
#include "nodedef.h"
#include "content_sao.h"
#include "settings.h"
#include "mapblock.h" // For getNodeBlockPos
#include "map.h"
#include "scripting_server.h"
#include "log.h"

void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef)
{
}


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef CONTENT_ABM_HEADER
#define CONTENT_ABM_HEADER

class ServerEnvironment;
class INodeDefManager;

/*
	Legacy ActiveBlockModifiers
*/

void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef);

#endif