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

/*
	Used for tuning constants when developing.

	Eg. if you have this constant somewhere that you just can't get right
	by changing it and recompiling all over again:
		v3f wield_position = v3f(55, -35, 65);

	Make it look like this:
		v3f wield_position = v3f(55, -35, 65);
		QUICKTUNE_AUTONAME(QVT_FLOAT, wield_position.X, 0, 100);
		QUICKTUNE_AUTONAME(QVT_FLOAT, wield_position.Y, -80, 20);
		QUICKTUNE_AUTONAME(QVT_FLOAT, wield_position.Z, 0, 100);

	Then you can modify the values at runtime, using the keys
		keymap_quicktune_prev
		keymap_quicktune_next
		keymap_quicktune_dec
		keymap_quicktune_inc

	Once you have modified the values at runtime and then quit, the game
	will print out all the modified values at the end:
		Modified quicktune values:
		wield_position.X = 60
		wield_position.Y = -30
		wield_position.Z = 65

	The QUICKTUNE macros shouldn't generally be left in committed code.
*/

#ifndef QUICKTUNE_HEADER
#define QUICKTUNE_HEADER

#include <string>
#include <map>
#include <vector>

enum QuicktuneValueType{
	QVT_NONE,
	QVT_FLOAT
};
struct QuicktuneValue
{
	QuicktuneValueType type;
	union{
		struct{
			float current;
			float min;
			float max;
		} value_QVT_FLOAT;
	};
	bool modified;

	QuicktuneValue():
		type(QVT_NONE),
		modified(false)
	{}
	std::string getString();
	void relativeAdd(float amount);
};

std::vector<std::string> getQuicktuneNames();
QuicktuneValue getQuicktuneValue(const std::string &name);
void setQuicktuneValue(const std::string &name, const QuicktuneValue &val);

void updateQuicktuneValue(const std::string &name, QuicktuneValue &val);

#ifndef NDEBUG
	#define QUICKTUNE(type_, var, min_, max_, name){\
		QuicktuneValue qv;\
		qv.type = type_;\
		qv.value_##type_.current = var;\
		qv.value_##type_.min = min_;\
		qv.value_##type_.max = max_;\
		updateQuicktuneValue(name, qv);\
		var = qv.value_##type_.current;\
	}
#else // NDEBUG
	#define QUICKTUNE(type, var, min_, max_, name){}
#endif

#define QUICKTUNE_AUTONAME(type_, var, min_, max_)\
	QUICKTUNE(type_, var, min_, max_, #var)

#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 QVT_SHORTCUTTER_HEADER
#define QVT_SHORTCUTTER_HEADER

#include "quicktune.h"

class QuicktuneShortcutter
{
private:
	std::vector<std::string> m_names;
	u32 m_selected_i;
	std::string m_message;
public:
	bool hasMessage()
	{
		return m_message != "";
	}

	std::string getMessage()
	{
		std::string s = m_message;
		m_message = "";
		if(s != "")
			return std::string("[quicktune] ") + s;
		return "";
	}
	std::string getSelectedName()
	{
		if(m_selected_i < m_names.size())
			return m_names[m_selected_i];
		return "(nothing)";
	}
	void next()
	{
		m_names = getQuicktuneNames();
		if(m_selected_i < m_names.size()-1)
			m_selected_i++;
		else
			m_selected_i = 0;
		m_message = std::string("Selected \"")+getSelectedName()+"\"";
	}
	void prev()
	{
		m_names = getQuicktuneNames();
		if(m_selected_i > 0)
			m_selected_i--;
		else
			m_selected_i = m_names.size()-1;
		m_message = std::string("Selected \"")+getSelectedName()+"\"";
	}
	void inc()
	{
		QuicktuneValue val = getQuicktuneValue(getSelectedName());
		val.relativeAdd(0.05);
		m_message = std::string("\"")+getSelectedName()
				+"\" = "+val.getString();
		setQuicktuneValue(getSelectedName(), val);
	}
	void dec()
	{
		QuicktuneValue val = getQuicktuneValue(getSelectedName());
		val.relativeAdd(-0.05);
		m_message = std::string("\"")+getSelectedName()
				+"\" = "+val.getString();
		setQuicktuneValue(getSelectedName(), val);
	}
};

#endif

repo_name: pgimeno/minetest

license: unknown


code:
/*
Minetest
Copyright (C) 2016 juhdanad, Daniel Juhasz <juhdanad@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 "irr_v3d.h"
#include "irr_aabb3d.h"

bool boxLineCollision(const aabb3f &box, const v3f &start,
		const v3f &dir, v3f *collision_point, v3s16 *collision_normal) {
	if (box.isPointInside(start)) {
		*collision_point = start;
		collision_normal->set(0, 0, 0);
		return true;
	}
	float m = 0;

	// Test X collision
	if (dir.X != 0) {
		if (dir.X > 0)
			m = (box.MinEdge.X - start.X) / dir.X;
		else
			m = (box.MaxEdge.X - start.X) / dir.X;

		if (m >= 0 && m <= 1) {
			*collision_point = start + dir * m;
			if ((collision_point->Y >= box.MinEdge.Y)
					&& (collision_point->Y <= box.MaxEdge.Y)
					&& (collision_point->Z >= box.MinEdge.Z)
					&& (collision_point->Z <= box.MaxEdge.Z)) {
				collision_normal->set((dir.X > 0) ? -1 : 1, 0, 0);
				return true;
			}
		}
	}

	// Test Y collision
	if (dir.Y != 0) {
		if (dir.Y > 0)
			m = (box.MinEdge.Y - start.Y) / dir.Y;
		else
			m = (box.MaxEdge.Y - start.Y) / dir.Y;

		if (m >= 0 && m <= 1) {
			*collision_point = start + dir * m;
			if ((collision_point->X >= box.MinEdge.X)
					&& (collision_point->X <= box.MaxEdge.X)
					&& (collision_point->Z >= box.MinEdge.Z)
					&& (collision_point->Z <= box.MaxEdge.Z)) {
				collision_normal->set(0, (dir.Y > 0) ? -1 : 1, 0);
				return true;
			}
		}
	}

	// Test Z collision
	if (dir.Z != 0) {
		if (dir.Z > 0)
			m = (box.MinEdge.Z - start.Z) / dir.Z;
		else
			m = (box.MaxEdge.Z - start.Z) / dir.Z;

		if (m >= 0 && m <= 1) {
			*collision_point = start + dir * m;
			if ((collision_point->X >= box.MinEdge.X)
					&& (collision_point->X <= box.MaxEdge.X)
					&& (collision_point->Y >= box.MinEdge.Y)
					&& (collision_point->Y <= box.MaxEdge.Y)) {
				collision_normal->set(0, 0, (dir.Z > 0) ? -1 : 1);
				return true;
			}
		}
	}
	return false;
}


code:
/*
Minetest
Copyright (C) 2016 juhdanad, Daniel Juhasz <juhdanad@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 SRC_RAYCAST_H_
#define SRC_RAYCAST_H_

/*!
 * Checks if a line and a box intersects.
 * @param[in]  box              box to test collision
 * @param[in]  start            starting point of the line
 * @param[in]  dir              direction and length of the line
 * @param[out] collision_point  first point of the collision
 * @param[out] collision_normal normal vector at the collision, points
 * outwards of the surface. If start is in the box, zero vector.
 * @returns true if a collision point was found
 */
bool boxLineCollision(const aabb3f &box, const v3f &start, const v3f &dir,
	v3f *collision_point, v3s16 *collision_normal);

#endif /* SRC_RAYCAST_H_ */


code:
/*
Minetest
Copyright (C) 2016 MillersMan <millersman@users.noreply.github.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 "reflowscan.h"
#include "map.h"
#include "mapblock.h"
#include "nodedef.h"
#include "util/timetaker.h"
#include "util/cpp11.h"

ReflowScan::ReflowScan(Map *map, INodeDefManager *ndef) :
	m_map(map),
	m_ndef(ndef),
	m_liquid_queue(nullptr)
{
}

void ReflowScan::scan(MapBlock *block, UniqueQueue<v3s16> *liquid_queue)
{
	m_block_pos = block->getPos();
	m_rel_block_pos = block->getPosRelative();
	m_liquid_queue = liquid_queue;

	// Prepare the lookup which is a 3x3x3 array of the blocks surrounding the
	// scanned block. Blocks are only added to the lookup if they are really
	// needed. The lookup is indexed manually to use the same index in a
	// bit-array (of uint32 type) which stores for unloaded blocks that they
	// were already fetched from Map but were actually nullptr.
	memset(m_lookup, 0, sizeof(m_lookup));
	int block_idx = 1 + (1 * 9) + (1 * 3);
	m_lookup[block_idx] = block;
	m_lookup_state_bitset = 1 << block_idx;

	// Scan the columns in the block
	for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
	for (s16 x = 0; x < MAP_BLOCKSIZE; x++) {
		scanColumn(x, z);
	}

	// Scan neighbouring columns from the nearby blocks as they might contain
	// liquid nodes that weren't allowed to flow to prevent gaps.
	for (s16 i = 0; i < MAP_BLOCKSIZE; i++) {
		scanColumn(i, -1);
		scanColumn(i, MAP_BLOCKSIZE);
		scanColumn(-1, i);
		scanColumn(MAP_BLOCKSIZE, i);
	}
}

inline MapBlock *ReflowScan::lookupBlock(int x, int y, int z)
{
	// Gets the block that contains (x,y,z) relativ to the scanned block.
	// This uses a lookup as there might be many lookups into the same
	// neighbouring block which makes fetches from Map costly.
	int bx = (MAP_BLOCKSIZE + x) / MAP_BLOCKSIZE;
	int by = (MAP_BLOCKSIZE + y) / MAP_BLOCKSIZE;
	int bz = (MAP_BLOCKSIZE + z) / MAP_BLOCKSIZE;
	int idx = (bx + (by * 9) + (bz * 3));
	MapBlock *result = m_lookup[idx];
	if (!result && (m_lookup_state_bitset & (1 << idx)) == 0) {
		// The block wasn't requested yet so fetch it from Map and store it
		// in the lookup
		v3s16 pos = m_block_pos + v3s16(bx - 1, by - 1, bz - 1);
		m_lookup[idx] = result = m_map->getBlockNoCreateNoEx(pos);
		m_lookup_state_bitset |= (1 << idx);
	}
	return result;
}

inline bool ReflowScan::isLiquidFlowableTo(int x, int y, int z)
{
	// Tests whether (x,y,z) is a node to which liquid might flow.
	bool valid_position;
	MapBlock *block = lookupBlock(x, y, z);
	if (block) {
		int dx = (MAP_BLOCKSIZE + x) % MAP_BLOCKSIZE;
		int dy = (MAP_BLOCKSIZE + y) % MAP_BLOCKSIZE;
		int dz = (MAP_BLOCKSIZE + z) % MAP_BLOCKSIZE;
		MapNode node = block->getNodeNoCheck(dx, dy, dz, &valid_position);
		if (node.getContent() != CONTENT_IGNORE) {
			const ContentFeatures &f = m_ndef->get(node);
			// NOTE: No need to check for flowing nodes with lower liquid level
			// as they should only occur on top of other columns where they
			// will be added to the queue themselves.
			return f.floodable;
		}
	}
	return false;
}

inline bool ReflowScan::isLiquidHorizontallyFlowable(int x, int y, int z)
{
	// Check if the (x,y,z) might spread to one of the horizontally
	// neighbouring nodes
	return isLiquidFlowableTo(x - 1, y, z) ||
		isLiquidFlowableTo(x + 1, y, z) ||
		isLiquidFlowableTo(x, y, z - 1) ||
		isLiquidFlowableTo(x, y, z + 1);
}

void ReflowScan::scanColumn(int x, int z)
{
	bool valid_position;

	// Is the column inside a loaded block?
	MapBlock *block = lookupBlock(x, 0, z);
	if (!block)
		return;

	MapBlock *above = lookupBlock(x, MAP_BLOCKSIZE, z);
	int dx = (MAP_BLOCKSIZE + x) % MAP_BLOCKSIZE;
	int dz = (MAP_BLOCKSIZE + z) % MAP_BLOCKSIZE;

	// Get the state from the node above the scanned block
	bool was_ignore, was_liquid;
	if (above) {
		MapNode node = above->getNodeNoCheck(dx, 0, dz, &valid_position);
		was_ignore = node.getContent() == CONTENT_IGNORE;
		was_liquid = m_ndef->get(node).isLiquid();
	} else {
		was_ignore = true;
		was_liquid = false;
	}
	bool was_checked = false;
	bool was_pushed = false;

	// Scan through the whole block
	for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) {
		MapNode node = block->getNodeNoCheck(dx, y, dz, &valid_position);
		const ContentFeatures &f = m_ndef->get(node);
		bool is_ignore = node.getContent() == CONTENT_IGNORE;
		bool is_liquid = f.isLiquid();

		if (is_ignore || was_ignore || is_liquid == was_liquid) {
			// Neither topmost node of liquid column nor topmost node below column
			was_checked = false;
			was_pushed = false;
		} else if (is_liquid) {
			// This is the topmost node in the column
			bool is_pushed = false;
			if (f.liquid_type == LIQUID_FLOWING ||
					isLiquidHorizontallyFlowable(x, y, z)) {
				m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y, z));
				is_pushed = true;
			}
			// Remember waschecked and waspushed to avoid repeated
			// checks/pushes in case the column consists of only this node
			was_checked = true;
			was_pushed = is_pushed;
		} else {
			// This is the topmost node below a liquid column
			if (!was_pushed && (f.floodable ||
					(!was_checked && isLiquidHorizontallyFlowable(x, y + 1, z)))) {
				// Activate the lowest node in the column which is one
				// node above this one
				m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, y + 1, z));
			}
		}

		was_liquid = is_liquid;
		was_ignore = is_ignore;
	}

	// Check the node below the current block
	MapBlock *below = lookupBlock(x, -1, z);
	if (below) {
		MapNode node = below->getNodeNoCheck(dx, MAP_BLOCKSIZE - 1, dz, &valid_position);
		const ContentFeatures &f = m_ndef->get(node);
		bool is_ignore = node.getContent() == CONTENT_IGNORE;
		bool is_liquid = f.isLiquid();

		if (is_ignore || was_ignore || is_liquid == was_liquid) {
			// Neither topmost node of liquid column nor topmost node below column
		} else if (is_liquid) {
			// This is the topmost node in the column and might want to flow away
			if (f.liquid_type == LIQUID_FLOWING ||
					isLiquidHorizontallyFlowable(x, -1, z)) {
				m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, -1, z));
			}
		} else {
			// This is the topmost node below a liquid column
			if (!was_pushed && (f.floodable ||
					(!was_checked && isLiquidHorizontallyFlowable(x, 0, z)))) {
				// Activate the lowest node in the column which is one
				// node above this one
				m_liquid_queue->push_back(m_rel_block_pos + v3s16(x, 0, z));
			}
		}
	}
}


code:
/*
Minetest
Copyright (C) 2016 MillersMan <millersman@users.noreply.github.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 REFLOWSCAN_H
#define REFLOWSCAN_H

#include "util/container.h"
#include "irrlichttypes_bloated.h"

class INodeDefManager;
class Map;
class MapBlock;

class ReflowScan {
public:
	ReflowScan(Map *map, INodeDefManager *ndef);
	void scan(MapBlock *block, UniqueQueue<v3s16> *liquid_queue);

private:
	MapBlock *lookupBlock(int x, int y, int z);
	bool isLiquidFlowableTo(int x, int y, int z);
	bool isLiquidHorizontallyFlowable(int x, int y, int z);
	void scanColumn(int x, int z);

private:
	Map *m_map;
	INodeDefManager *m_ndef;
	v3s16 m_block_pos, m_rel_block_pos;
	UniqueQueue<v3s16> *m_liquid_queue;
	MapBlock *m_lookup[3 * 3 * 3];
	u32 m_lookup_state_bitset;
};

#endif // REFLOWSCAN_H


code:
/*
Minetest
Copyright (C) 2010-2016 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2014-2016 nerzhul, Loic Blot <loic.blot@unix-experience.fr>

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 "remoteplayer.h"
#include <json/json.h>
#include "content_sao.h"
#include "filesys.h"
#include "gamedef.h"
#include "porting.h"  // strlcpy
#include "server.h"
#include "settings.h"

/*
	RemotePlayer
*/
// static config cache for remoteplayer
bool RemotePlayer::m_setting_cache_loaded = false;
float RemotePlayer::m_setting_chat_message_limit_per_10sec = 0.0f;
u16 RemotePlayer::m_setting_chat_message_limit_trigger_kick = 0;

RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
	Player(name, idef),
	protocol_version(0),
	m_sao(NULL),
	m_dirty(false),
	m_last_chat_message_sent(time(NULL)),
	m_chat_message_allowance(5.0f),
	m_message_rate_overhead(0),
	hud_hotbar_image(""),
	hud_hotbar_selected_image("")
{
	if (!RemotePlayer::m_setting_cache_loaded) {
		RemotePlayer::m_setting_chat_message_limit_per_10sec =
			g_settings->getFloat("chat_message_limit_per_10sec");
		RemotePlayer::m_setting_chat_message_limit_trigger_kick =
			g_settings->getU16("chat_message_limit_trigger_kick");
		RemotePlayer::m_setting_cache_loaded = true;
	}
	movement_acceleration_default   = g_settings->getFloat("movement_acceleration_default")   * BS;
	movement_acceleration_air       = g_settings->getFloat("movement_acceleration_air")       * BS;
	movement_acceleration_fast      = g_settings->getFloat("movement_acceleration_fast")      * BS;
	movement_speed_walk             = g_settings->getFloat("movement_speed_walk")             * BS;
	movement_speed_crouch           = g_settings->getFloat("movement_speed_crouch")           * BS;
	movement_speed_fast             = g_settings->getFloat("movement_speed_fast")             * BS;
	movement_speed_climb            = g_settings->getFloat("movement_speed_climb")            * BS;
	movement_speed_jump             = g_settings->getFloat("movement_speed_jump")             * BS;
	movement_liquid_fluidity        = g_settings->getFloat("movement_liquid_fluidity")        * BS;
	movement_liquid_fluidity_smooth = g_settings->getFloat("movement_liquid_fluidity_smooth") * BS;
	movement_liquid_sink            = g_settings->getFloat("movement_liquid_sink")            * BS;
	movement_gravity                = g_settings->getFloat("movement_gravity")                * BS;

	// copy defaults
	m_cloud_params.density = 0.4f;
	m_cloud_params.color_bright = video::SColor(229, 240, 240, 255);
	m_cloud_params.color_ambient = video::SColor(255, 0, 0, 0);
	m_cloud_params.height = 120.0f;
	m_cloud_params.thickness = 16.0f;
	m_cloud_params.speed = v2f(0.0f, -2.0f);
}

void RemotePlayer::serializeExtraAttributes(std::string &output)
{
	assert(m_sao);
	Json::Value json_root;
	const PlayerAttributes &attrs = m_sao->getExtendedAttributes();
	for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) {
		json_root[(*it).first] = (*it).second;
	}

	Json::FastWriter writer;
	output = writer.write(json_root);
	m_sao->setExtendedAttributeModified(false);
}

void RemotePlayer::deSerialize(std::istream &is, const std::string &playername,
		PlayerSAO *sao)
{
	Settings args;

	if (!args.parseConfigLines(is, "PlayerArgsEnd")) {
		throw SerializationError("PlayerArgsEnd of player " + playername + " not found!");
	}

	m_dirty = true;
	//args.getS32("version"); // Version field value not used
	const std::string &name = args.get("name");
	strlcpy(m_name, name.c_str(), PLAYERNAME_SIZE);

	if (sao) {
		try {
			sao->setHPRaw(args.getS32("hp"));
		} catch(SettingNotFoundException &e) {
			sao->setHPRaw(PLAYER_MAX_HP);
		}

		try {
			sao->setBasePosition(args.getV3F("position"));
		} catch (SettingNotFoundException &e) {}

		try {
			sao->setPitch(args.getFloat("pitch"));
		} catch (SettingNotFoundException &e) {}
		try {
			sao->setYaw(args.getFloat("yaw"));
		} catch (SettingNotFoundException &e) {}

		try {
			sao->setBreath(args.getS32("breath"), false);
		} catch (SettingNotFoundException &e) {}

		try {
			const std::string &extended_attributes = args.get("extended_attributes");
			Json::Reader reader;
			Json::Value attr_root;
			reader.parse(extended_attributes, attr_root);

			const Json::Value::Members attr_list = attr_root.getMemberNames();
			for (Json::Value::Members::const_iterator it = attr_list.begin();
					it != attr_list.end(); ++it) {
				Json::Value attr_value = attr_root[*it];
				sao->setExtendedAttribute(*it, attr_value.asString());
			}
		} catch (SettingNotFoundException &e) {}
	}

	inventory.deSerialize(is);

	if (inventory.getList("craftpreview") == NULL) {
		// Convert players without craftpreview
		inventory.addList("craftpreview", 1);

		bool craftresult_is_preview = true;
		if(args.exists("craftresult_is_preview"))
			craftresult_is_preview = args.getBool("craftresult_is_preview");
		if(craftresult_is_preview)
		{
			// Clear craftresult
			inventory.getList("craftresult")->changeItem(0, ItemStack());
		}
	}
}

void RemotePlayer::serialize(std::ostream &os)
{
	// Utilize a Settings object for storing values
	Settings args;
	args.setS32("version", 1);
	args.set("name", m_name);

	// This should not happen
	assert(m_sao);
	args.setS32("hp", m_sao->getHP());
	args.setV3F("position", m_sao->getBasePosition());
	args.setFloat("pitch", m_sao->getPitch());
	args.setFloat("yaw", m_sao->getYaw());
	args.setS32("breath", m_sao->getBreath());

	std::string extended_attrs = "";
	serializeExtraAttributes(extended_attrs);
	args.set("extended_attributes", extended_attrs);

	args.writeLines(os);

	os<<"PlayerArgsEnd\n";

	inventory.serialize(os);
}

const RemotePlayerChatResult RemotePlayer::canSendChatMessage()
{
	// Rate limit messages
	u32 now = time(NULL);
	float time_passed = now - m_last_chat_message_sent;
	m_last_chat_message_sent = now;

	// If this feature is disabled
	if (m_setting_chat_message_limit_per_10sec <= 0.0) {
		return RPLAYER_CHATRESULT_OK;
	}

	m_chat_message_allowance += time_passed * (m_setting_chat_message_limit_per_10sec / 8.0f);
	if (m_chat_message_allowance > m_setting_chat_message_limit_per_10sec) {
		m_chat_message_allowance = m_setting_chat_message_limit_per_10sec;
	}

	if (m_chat_message_allowance < 1.0f) {
		infostream << "Player " << m_name
				<< " chat limited due to excessive message amount." << std::endl;

		// Kick player if flooding is too intensive
		m_message_rate_overhead++;
		if (m_message_rate_overhead > RemotePlayer::m_setting_chat_message_limit_trigger_kick) {
			return RPLAYER_CHATRESULT_KICK;
		}

		return RPLAYER_CHATRESULT_FLOODING;
	}

	// Reinit message overhead
	if (m_message_rate_overhead > 0) {
		m_message_rate_overhead = 0;
	}

	m_chat_message_allowance -= 1.0f;
	return RPLAYER_CHATRESULT_OK;
}


code:
/*
Minetest
Copyright (C) 2010-2016 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2014-2016 nerzhul, Loic Blot <loic.blot@unix-experience.fr>

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 REMOTEPLAYER_HEADER
#define REMOTEPLAYER_HEADER

#include "player.h"
#include "cloudparams.h"

class PlayerSAO;

enum RemotePlayerChatResult
{
	RPLAYER_CHATRESULT_OK,
	RPLAYER_CHATRESULT_FLOODING,
	RPLAYER_CHATRESULT_KICK,
};

/*
	Player on the server
*/
class RemotePlayer : public Player
{
	friend class PlayerDatabaseFiles;

public:
	RemotePlayer(const char *name, IItemDefManager *idef);
	virtual ~RemotePlayer() {}

	void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao);

	PlayerSAO *getPlayerSAO() { return m_sao; }
	void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; }

	const RemotePlayerChatResult canSendChatMessage();

	void setHotbarItemcount(s32 hotbar_itemcount)
	{
		hud_hotbar_itemcount = hotbar_itemcount;
	}

	s32 getHotbarItemcount() const { return hud_hotbar_itemcount; }

	void overrideDayNightRatio(bool do_override, float ratio)
	{
		m_day_night_ratio_do_override = do_override;
		m_day_night_ratio = ratio;
	}

	void getDayNightRatio(bool *do_override, float *ratio)
	{
		*do_override = m_day_night_ratio_do_override;
		*ratio = m_day_night_ratio;
	}

	void setHotbarImage(const std::string &name) { hud_hotbar_image = name; }

	std::string getHotbarImage() const { return hud_hotbar_image; }

	void setHotbarSelectedImage(const std::string &name)
	{
		hud_hotbar_selected_image = name;
	}

	const std::string &getHotbarSelectedImage() const
	{
		return hud_hotbar_selected_image;
	}

	void setSky(const video::SColor &bgcolor, const std::string &type,
			const std::vector<std::string> &params, bool &clouds)
	{
		m_sky_bgcolor = bgcolor;
		m_sky_type = type;
		m_sky_params = params;
		m_sky_clouds = clouds;
	}

	void getSky(video::SColor *bgcolor, std::string *type,
			std::vector<std::string> *params, bool *clouds)
	{
		*bgcolor = m_sky_bgcolor;
		*type = m_sky_type;
		*params = m_sky_params;
		*clouds = m_sky_clouds;
	}

	void setCloudParams(const CloudParams &cloud_params)
	{
		m_cloud_params = cloud_params;
	}

	const CloudParams &getCloudParams() const { return m_cloud_params; }

	bool checkModified() const { return m_dirty || inventory.checkModified(); }

	void setModified(const bool x)
	{
		m_dirty = x;
		if (!x)
			inventory.setModified(x);
	}

	void setLocalAnimations(v2s32 frames[4], float frame_speed)
	{
		for (int i = 0; i < 4; i++)
			local_animations[i] = frames[i];
		local_animation_speed = frame_speed;
	}

	void getLocalAnimations(v2s32 *frames, float *frame_speed)
	{
		for (int i = 0; i < 4; i++)
			frames[i] = local_animations[i];
		*frame_speed = local_animation_speed;
	}

	void setDirty(bool dirty) { m_dirty = true; }

	u16 protocol_version;

private:
	/*
		serialize() writes a bunch of text that can contain
		any characters except a '\0', and such an ending that
		deSerialize stops reading exactly at the right point.
	*/
	void serialize(std::ostream &os);
	void serializeExtraAttributes(std::string &output);

	PlayerSAO *m_sao;
	bool m_dirty;

	static bool m_setting_cache_loaded;
	static float m_setting_chat_message_limit_per_10sec;
	static u16 m_setting_chat_message_limit_trigger_kick;

	u32 m_last_chat_message_sent;
	float m_chat_message_allowance;
	u16 m_message_rate_overhead;

	bool m_day_night_ratio_do_override = false;
	float m_day_night_ratio;
	std::string hud_hotbar_image;
	std::string hud_hotbar_selected_image;

	std::string m_sky_type;
	video::SColor m_sky_bgcolor;
	std::vector<std::string> m_sky_params;
	bool m_sky_clouds;

	CloudParams m_cloud_params;
};

#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 "rollback.h"
#include <fstream>
#include <list>
#include <sstream>
#include "log.h"
#include "mapnode.h"
#include "gamedef.h"
#include "nodedef.h"
#include "util/serialize.h"
#include "util/string.h"
#include "util/numeric.h"
#include "inventorymanager.h" // deserializing InventoryLocations
#include "sqlite3.h"
#include "filesys.h"

#define POINTS_PER_NODE (16.0)

#define SQLRES(f, good) \
	if ((f) != (good)) {\
		throw FileNotGoodException(std::string("RollbackManager: " \
			"SQLite3 error (" __FILE__ ":" TOSTRING(__LINE__) \
			"): ") + sqlite3_errmsg(db)); \
	}
#define SQLOK(f) SQLRES(f, SQLITE_OK)

#define SQLOK_ERRSTREAM(s, m)                             \
	if ((s) != SQLITE_OK) {                               \
		errorstream << "RollbackManager: " << (m) << ": " \
			<< sqlite3_errmsg(db) << std::endl;           \
	}

#define FINALIZE_STATEMENT(statement) \
	SQLOK_ERRSTREAM(sqlite3_finalize(statement), "Failed to finalize " #statement)

class ItemStackRow : public ItemStack {
public:
	ItemStackRow & operator = (const ItemStack & other)
	{
		*static_cast<ItemStack *>(this) = other;
		return *this;
	}

	int id;
};

struct ActionRow {
	int          id;
	int          actor;
	time_t       timestamp;
	int          type;
	std::string  location, list;
	int          index, add;
	ItemStackRow stack;
	int          nodeMeta;
	int          x, y, z;
	int          oldNode;
	int          oldParam1, oldParam2;
	std::string  oldMeta;
	int          newNode;
	int          newParam1, newParam2;
	std::string  newMeta;
	int          guessed;
};

struct Entity {
	int         id;
	std::string name;
};

RollbackManager::RollbackManager(const std::string & world_path,
		IGameDef * gamedef_) :
	gamedef(gamedef_),
	current_actor_is_guess(false)
{
	verbosestream << "RollbackManager::RollbackManager(" << world_path
		<< ")" << std::endl;

	std::string txt_filename = world_path + DIR_DELIM "rollback.txt";
	std::string migrating_flag = txt_filename + ".migrating";
	database_path = world_path + DIR_DELIM "rollback.sqlite";

	bool created = initDatabase();

	if (fs::PathExists(txt_filename) && (created ||
			fs::PathExists(migrating_flag))) {
		std::ofstream of(migrating_flag.c_str());
		of.close();
		migrate(txt_filename);
		fs::DeleteSingleFileOrEmptyDirectory(migrating_flag);
	}
}

RollbackManager::~RollbackManager()
{
	flush();

	FINALIZE_STATEMENT(stmt_insert);
	FINALIZE_STATEMENT(stmt_replace);
	FINALIZE_STATEMENT(stmt_select);
	FINALIZE_STATEMENT(stmt_select_range);
	FINALIZE_STATEMENT(stmt_select_withActor);
	FINALIZE_STATEMENT(stmt_knownActor_select);
	FINALIZE_STATEMENT(stmt_knownActor_insert);
	FINALIZE_STATEMENT(stmt_knownNode_select);
	FINALIZE_STATEMENT(stmt_knownNode_insert);

	SQLOK_ERRSTREAM(sqlite3_close(db), "Could not close db");
}

void RollbackManager::registerNewActor(const int id, const std::string &name)
{
	Entity actor = {id, name};
	knownActors.push_back(actor);
}

void RollbackManager::registerNewNode(const int id, const std::string &name)
{
	Entity node = {id, name};
	knownNodes.push_back(node);
}

int RollbackManager::getActorId(const std::string &name)
{
	for (std::vector<Entity>::const_iterator iter = knownActors.begin();
			iter != knownActors.end(); ++iter) {
		if (iter->name == name) {
			return iter->id;
		}
	}

	SQLOK(sqlite3_bind_text(stmt_knownActor_insert, 1, name.c_str(), name.size(), NULL));
	SQLRES(sqlite3_step(stmt_knownActor_insert), SQLITE_DONE);
	SQLOK(sqlite3_reset(stmt_knownActor_insert));

	int id = sqlite3_last_insert_rowid(db);
	registerNewActor(id, name);

	return id;
}

int RollbackManager::getNodeId(const std::string &name)
{
	for (std::vector<Entity>::const_iterator iter = knownNodes.begin();
			iter != knownNodes.end(); ++iter) {
		if (iter->name == name) {
			return iter->id;
		}
	}

	SQLOK(sqlite3_bind_text(stmt_knownNode_insert, 1, name.c_str(), name.size(), NULL));
	SQLRES(sqlite3_step(stmt_knownNode_insert), SQLITE_DONE);
	SQLOK(sqlite3_reset(stmt_knownNode_insert));

	int id = sqlite3_last_insert_rowid(db);
	registerNewNode(id, name);

	return id;
}

const char * RollbackManager::getActorName(const int id)
{
	for (std::vector<Entity>::const_iterator iter = knownActors.begin();
			iter != knownActors.end(); ++iter) {
		if (iter->id == id) {
			return iter->name.c_str();
		}
	}

	return "";
}

const char * RollbackManager::getNodeName(const int id)
{
	for (std::vector<Entity>::const_iterator iter = knownNodes.begin();
			iter != knownNodes.end(); ++iter) {
		if (iter->id == id) {
			return iter->name.c_str();
		}
	}

	return "";
}

bool RollbackManager::createTables()
{
	SQLOK(sqlite3_exec(db,
		"CREATE TABLE IF NOT EXISTS `actor` (\n"
		"	`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
		"	`name` TEXT NOT NULL\n"
		");\n"
		"CREATE TABLE IF NOT EXISTS `node` (\n"
		"	`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
		"	`name` TEXT NOT NULL\n"
		");\n"
		"CREATE TABLE IF NOT EXISTS `action` (\n"
		"	`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n"
		"	`actor` INTEGER NOT NULL,\n"
		"	`timestamp` TIMESTAMP NOT NULL,\n"
		"	`type` INTEGER NOT NULL,\n"
		"	`list` TEXT,\n"
		"	`index` INTEGER,\n"
		"	`add` INTEGER,\n"
		"	`stackNode` INTEGER,\n"
		"	`stackQuantity` INTEGER,\n"
		"	`nodeMeta` INTEGER,\n"
		"	`x` INT,\n"
		"	`y` INT,\n"
		"	`z` INT,\n"
		"	`oldNode` INTEGER,\n"
		"	`oldParam1` INTEGER,\n"
		"	`oldParam2` INTEGER,\n"
		"	`oldMeta` TEXT,\n"
		"	`newNode` INTEGER,\n"
		"	`newParam1` INTEGER,\n"
		"	`newParam2` INTEGER,\n"
		"	`newMeta` TEXT,\n"
		"	`guessedActor` INTEGER,\n"
		"	FOREIGN KEY (`actor`) REFERENCES `actor`(`id`),\n"
		"	FOREIGN KEY (`stackNode`) REFERENCES `node`(`id`),\n"
		"	FOREIGN KEY (`oldNode`)   REFERENCES `node`(`id`),\n"
		"	FOREIGN KEY (`newNode`)   REFERENCES `node`(`id`)\n"
		");\n"
		"CREATE INDEX IF NOT EXISTS `actionIndex` ON `action`(`x`,`y`,`z`,`timestamp`,`actor`);\n",
		NULL, NULL, NULL));
	verbosestream << "SQL Rollback: SQLite3 database structure was created" << std::endl;

	return true;
}

bool RollbackManager::initDatabase()
{
	verbosestream << "RollbackManager: Database connection setup" << std::endl;

	bool needs_create = !fs::PathExists(database_path);
	SQLOK(sqlite3_open_v2(database_path.c_str(), &db,
			SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));

	if (needs_create) {
		createTables();
	}

	SQLOK(sqlite3_prepare_v2(db,
		"INSERT INTO `action` (\n"
		"	`actor`, `timestamp`, `type`,\n"
		"	`list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n"
		"	`x`, `y`, `z`,\n"
		"	`oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
		"	`newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
		"	`guessedActor`\n"
		") VALUES (\n"
		"	?, ?, ?,\n"
		"	?, ?, ?, ?, ?, ?,\n"
		"	?, ?, ?,\n"
		"	?, ?, ?, ?,\n"
		"	?, ?, ?, ?,\n"
		"	?"
		");",
		-1, &stmt_insert, NULL));

	SQLOK(sqlite3_prepare_v2(db,
		"REPLACE INTO `action` (\n"
		"	`actor`, `timestamp`, `type`,\n"
		"	`list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n"
		"	`x`, `y`, `z`,\n"
		"	`oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
		"	`newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
		"	`guessedActor`, `id`\n"
		") VALUES (\n"
		"	?, ?, ?,\n"
		"	?, ?, ?, ?, ?, ?,\n"
		"	?, ?, ?,\n"
		"	?, ?, ?, ?,\n"
		"	?, ?, ?, ?,\n"
		"	?, ?\n"
		");",
		-1, &stmt_replace, NULL));

	SQLOK(sqlite3_prepare_v2(db,
		"SELECT\n"
		"	`actor`, `timestamp`, `type`,\n"
		"	`list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
		"	`x`, `y`, `z`,\n"
		"	`oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
		"	`newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
		"	`guessedActor`\n"
		" FROM `action`\n"
		" WHERE `timestamp` >= ?\n"
		" ORDER BY `timestamp` DESC, `id` DESC",
		-1, &stmt_select, NULL));

	SQLOK(sqlite3_prepare_v2(db,
		"SELECT\n"
		"	`actor`, `timestamp`, `type`,\n"
		"	`list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
		"	`x`, `y`, `z`,\n"
		"	`oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
		"	`newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
		"	`guessedActor`\n"
		"FROM `action`\n"
		"WHERE `timestamp` >= ?\n"
		"	AND `x` IS NOT NULL\n"
		"	AND `y` IS NOT NULL\n"
		"	AND `z` IS NOT NULL\n"
		"	AND `x` BETWEEN ? AND ?\n"
		"	AND `y` BETWEEN ? AND ?\n"
		"	AND `z` BETWEEN ? AND ?\n"
		"ORDER BY `timestamp` DESC, `id` DESC\n"
		"LIMIT 0,?",
		-1, &stmt_select_range, NULL));

	SQLOK(sqlite3_prepare_v2(db,
		"SELECT\n"
		"	`actor`, `timestamp`, `type`,\n"
		"	`list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
		"	`x`, `y`, `z`,\n"
		"	`oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
		"	`newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
		"	`guessedActor`\n"
		"FROM `action`\n"
		"WHERE `timestamp` >= ?\n"
		"	AND `actor` = ?\n"
		"ORDER BY `timestamp` DESC, `id` DESC\n",
		-1, &stmt_select_withActor, NULL));

	SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `actor`",
			-1, &stmt_knownActor_select, NULL));

	SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `actor` (`name`) VALUES (?)",
			-1, &stmt_knownActor_insert, NULL));

	SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `node`",
			-1, &stmt_knownNode_select, NULL));

	SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `node` (`name`) VALUES (?)",
			-1, &stmt_knownNode_insert, NULL));

	verbosestream << "SQL prepared statements setup correctly" << std::endl;

	while (sqlite3_step(stmt_knownActor_select) == SQLITE_ROW) {
		registerNewActor(
		        sqlite3_column_int(stmt_knownActor_select, 0),
		        reinterpret_cast<const char *>(sqlite3_column_text(stmt_knownActor_select, 1))
		);
	}
	SQLOK(sqlite3_reset(stmt_knownActor_select));

	while (sqlite3_step(stmt_knownNode_select) == SQLITE_ROW) {
		registerNewNode(
		        sqlite3_column_int(stmt_knownNode_select, 0),
		        reinterpret_cast<const char *>(sqlite3_column_text(stmt_knownNode_select, 1))
		);
	}
	SQLOK(sqlite3_reset(stmt_knownNode_select));

	return needs_create;
}

bool RollbackManager::registerRow(const ActionRow & row)
{
	sqlite3_stmt * stmt_do = (row.id) ? stmt_replace : stmt_insert;

	bool nodeMeta = false;

	SQLOK(sqlite3_bind_int  (stmt_do, 1, row.actor));
	SQLOK(sqlite3_bind_int64(stmt_do, 2, row.timestamp));
	SQLOK(sqlite3_bind_int  (stmt_do, 3, row.type));

	if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
		const std::string & loc = row.location;
		nodeMeta = (loc.substr(0, 9) == "nodemeta:");

		SQLOK(sqlite3_bind_text(stmt_do, 4, row.list.c_str(), row.list.size(), NULL));
		SQLOK(sqlite3_bind_int (stmt_do, 5, row.index));
		SQLOK(sqlite3_bind_int (stmt_do, 6, row.add));
		SQLOK(sqlite3_bind_int (stmt_do, 7, row.stack.id));
		SQLOK(sqlite3_bind_int (stmt_do, 8, row.stack.count));
		SQLOK(sqlite3_bind_int (stmt_do, 9, (int) nodeMeta));

		if (nodeMeta) {
			std::string::size_type p1, p2;
			p1 = loc.find(':') + 1;
			p2 = loc.find(',');
			std::string x = loc.substr(p1, p2 - p1);
			p1 = p2 + 1;
			p2 = loc.find(',', p1);
			std::string y = loc.substr(p1, p2 - p1);
			std::string z = loc.substr(p2 + 1);
			SQLOK(sqlite3_bind_int(stmt_do, 10, atoi(x.c_str())));
			SQLOK(sqlite3_bind_int(stmt_do, 11, atoi(y.c_str())));
			SQLOK(sqlite3_bind_int(stmt_do, 12, atoi(z.c_str())));
		}
	} else {
		SQLOK(sqlite3_bind_null(stmt_do, 4));
		SQLOK(sqlite3_bind_null(stmt_do, 5));
		SQLOK(sqlite3_bind_null(stmt_do, 6));
		SQLOK(sqlite3_bind_null(stmt_do, 7));
		SQLOK(sqlite3_bind_null(stmt_do, 8));
		SQLOK(sqlite3_bind_null(stmt_do, 9));
	}

	if (row.type == RollbackAction::TYPE_SET_NODE) {
		SQLOK(sqlite3_bind_int (stmt_do, 10, row.x));
		SQLOK(sqlite3_bind_int (stmt_do, 11, row.y));
		SQLOK(sqlite3_bind_int (stmt_do, 12, row.z));
		SQLOK(sqlite3_bind_int (stmt_do, 13, row.oldNode));
		SQLOK(sqlite3_bind_int (stmt_do, 14, row.oldParam1));
		SQLOK(sqlite3_bind_int (stmt_do, 15, row.oldParam2));
		SQLOK(sqlite3_bind_text(stmt_do, 16, row.oldMeta.c_str(), row.oldMeta.size(), NULL));
		SQLOK(sqlite3_bind_int (stmt_do, 17, row.newNode));
		SQLOK(sqlite3_bind_int (stmt_do, 18, row.newParam1));
		SQLOK(sqlite3_bind_int (stmt_do, 19, row.newParam2));
		SQLOK(sqlite3_bind_text(stmt_do, 20, row.newMeta.c_str(), row.newMeta.size(), NULL));
		SQLOK(sqlite3_bind_int (stmt_do, 21, row.guessed ? 1 : 0));
	} else {
		if (!nodeMeta) {
			SQLOK(sqlite3_bind_null(stmt_do, 10));
			SQLOK(sqlite3_bind_null(stmt_do, 11));
			SQLOK(sqlite3_bind_null(stmt_do, 12));
		}
		SQLOK(sqlite3_bind_null(stmt_do, 13));
		SQLOK(sqlite3_bind_null(stmt_do, 14));
		SQLOK(sqlite3_bind_null(stmt_do, 15));
		SQLOK(sqlite3_bind_null(stmt_do, 16));
		SQLOK(sqlite3_bind_null(stmt_do, 17));
		SQLOK(sqlite3_bind_null(stmt_do, 18));
		SQLOK(sqlite3_bind_null(stmt_do, 19));
		SQLOK(sqlite3_bind_null(stmt_do, 20));
		SQLOK(sqlite3_bind_null(stmt_do, 21));
	}

	if (row.id) {
		SQLOK(sqlite3_bind_int(stmt_do, 22, row.id));
	}

	int written = sqlite3_step(stmt_do);

	SQLOK(sqlite3_reset(stmt_do));

	return written == SQLITE_DONE;
}

const std::list<ActionRow> RollbackManager::actionRowsFromSelect(sqlite3_stmt* stmt)
{
	std::list<ActionRow> rows;
	const unsigned char * text;
	size_t size;

	while (sqlite3_step(stmt) == SQLITE_ROW) {
		ActionRow row;

		row.actor     = sqlite3_column_int  (stmt, 0);
		row.timestamp = sqlite3_column_int64(stmt, 1);
		row.type      = sqlite3_column_int  (stmt, 2);

		if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
			text = sqlite3_column_text (stmt, 3);
			size = sqlite3_column_bytes(stmt, 3);
			row.list        = std::string(reinterpret_cast<const char*>(text), size);
			row.index       = sqlite3_column_int(stmt, 4);
			row.add         = sqlite3_column_int(stmt, 5);
			row.stack.id    = sqlite3_column_int(stmt, 6);
			row.stack.count = sqlite3_column_int(stmt, 7);
			row.nodeMeta    = sqlite3_column_int(stmt, 8);
		}

		if (row.type == RollbackAction::TYPE_SET_NODE || row.nodeMeta) {
			row.x = sqlite3_column_int(stmt,  9);
			row.y = sqlite3_column_int(stmt, 10);
			row.z = sqlite3_column_int(stmt, 11);
		}

		if (row.type == RollbackAction::TYPE_SET_NODE) {
			row.oldNode   = sqlite3_column_int(stmt, 12);
			row.oldParam1 = sqlite3_column_int(stmt, 13);
			row.oldParam2 = sqlite3_column_int(stmt, 14);
			text = sqlite3_column_text (stmt, 15);
			size = sqlite3_column_bytes(stmt, 15);
			row.oldMeta   = std::string(reinterpret_cast<const char*>(text), size);
			row.newNode   = sqlite3_column_int(stmt, 16);
			row.newParam1 = sqlite3_column_int(stmt, 17);
			row.newParam2 = sqlite3_column_int(stmt, 18);
			text = sqlite3_column_text(stmt, 19);
			size = sqlite3_column_bytes(stmt, 19);
			row.newMeta   = std::string(reinterpret_cast<const char*>(text), size);
			row.guessed   = sqlite3_column_int(stmt, 20);
		}

		if (row.nodeMeta) {
			row.location = "nodemeta:";
			row.location += itos(row.x);
			row.location += ',';
			row.location += itos(row.y);
			row.location += ',';
			row.location += itos(row.z);
		} else {
			row.location = getActorName(row.actor);
		}

		rows.push_back(row);
	}

	SQLOK(sqlite3_reset(stmt));

	return rows;
}

ActionRow RollbackManager::actionRowFromRollbackAction(const RollbackAction & action)
{
	ActionRow row;

	row.id        = 0;
	row.actor     = getActorId(action.actor);
	row.timestamp = action.unix_time;
	row.type      = action.type;

	if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
		row.location = action.inventory_location;
		row.list     = action.inventory_list;
		row.index    = action.inventory_index;
		row.add      = action.inventory_add;
		row.stack    = action.inventory_stack;
		row.stack.id = getNodeId(row.stack.name);
	} else {
		row.x         = action.p.X;
		row.y         = action.p.Y;
		row.z         = action.p.Z;
		row.oldNode   = getNodeId(action.n_old.name);
		row.oldParam1 = action.n_old.param1;
		row.oldParam2 = action.n_old.param2;
		row.oldMeta   = action.n_old.meta;
		row.newNode   = getNodeId(action.n_new.name);
		row.newParam1 = action.n_new.param1;
		row.newParam2 = action.n_new.param2;
		row.newMeta   = action.n_new.meta;
		row.guessed   = action.actor_is_guess;
	}

	return row;
}

const std::list<RollbackAction> RollbackManager::rollbackActionsFromActionRows(
		const std::list<ActionRow> & rows)
{
	std::list<RollbackAction> actions;

	for (std::list<ActionRow>::const_iterator it = rows.begin();
			it != rows.end(); ++it) {
		RollbackAction action;
		action.actor     = (it->actor) ? getActorName(it->actor) : "";
		action.unix_time = it->timestamp;
		action.type      = static_cast<RollbackAction::Type>(it->type);

		switch (action.type) {
		case RollbackAction::TYPE_MODIFY_INVENTORY_STACK:
			action.inventory_location = it->location.c_str();
			action.inventory_list     = it->list;
			action.inventory_index    = it->index;
			action.inventory_add      = it->add;
			action.inventory_stack    = it->stack;
			if (action.inventory_stack.name.empty()) {
				action.inventory_stack.name = getNodeName(it->stack.id);
			}
			break;

		case RollbackAction::TYPE_SET_NODE:
			action.p            = v3s16(it->x, it->y, it->z);
			action.n_old.name   = getNodeName(it->oldNode);
			action.n_old.param1 = it->oldParam1;
			action.n_old.param2 = it->oldParam2;
			action.n_old.meta   = it->oldMeta;
			action.n_new.name   = getNodeName(it->newNode);
			action.n_new.param1 = it->newParam1;
			action.n_new.param2 = it->newParam2;
			action.n_new.meta   = it->newMeta;
			break;

		default:
			throw ("W.T.F.");
			break;
		}

		actions.push_back(action);
	}

	return actions;
}

const std::list<ActionRow> RollbackManager::getRowsSince(time_t firstTime, const std::string & actor)
{
	sqlite3_stmt *stmt_stmt = actor.empty() ? stmt_select : stmt_select_withActor;
	sqlite3_bind_int64(stmt_stmt, 1, firstTime);

	if (!actor.empty()) {
		sqlite3_bind_int(stmt_stmt, 2, getActorId(actor));
	}

	const std::list<ActionRow> & rows = actionRowsFromSelect(stmt_stmt);
	sqlite3_reset(stmt_stmt);

	return rows;
}

const std::list<ActionRow> RollbackManager::getRowsSince_range(
		time_t start_time, v3s16 p, int range, int limit)
{

	sqlite3_bind_int64(stmt_select_range, 1, start_time);
	sqlite3_bind_int  (stmt_select_range, 2, static_cast<int>(p.X - range));
	sqlite3_bind_int  (stmt_select_range, 3, static_cast<int>(p.X + range));
	sqlite3_bind_int  (stmt_select_range, 4, static_cast<int>(p.Y - range));
	sqlite3_bind_int  (stmt_select_range, 5, static_cast<int>(p.Y + range));
	sqlite3_bind_int  (stmt_select_range, 6, static_cast<int>(p.Z - range));
	sqlite3_bind_int  (stmt_select_range, 7, static_cast<int>(p.Z + range));
	sqlite3_bind_int  (stmt_select_range, 8, limit);

	const std::list<ActionRow> & rows = actionRowsFromSelect(stmt_select_range);
	sqlite3_reset(stmt_select_range);

	return rows;
}

const std::list<RollbackAction> RollbackManager::getActionsSince_range(
		time_t start_time, v3s16 p, int range, int limit)
{
	return rollbackActionsFromActionRows(getRowsSince_range(start_time, p, range, limit));
}

const std::list<RollbackAction> RollbackManager::getActionsSince(
		time_t start_time, const std::string & actor)
{
	return rollbackActionsFromActionRows(getRowsSince(start_time, actor));
}

void RollbackManager::migrate(const std::string & file_path)
{
	std::cout << "Migrating from rollback.txt to rollback.sqlite." << std::endl;

	std::ifstream fh(file_path.c_str(), std::ios::in | std::ios::ate);
	if (!fh.good()) {
		throw FileNotGoodException("Unable to open rollback.txt");
	}

	std::streampos file_size = fh.tellg();

	if (file_size < 10) {
		errorstream << "Empty rollback log." << std::endl;
		return;
	}

	fh.seekg(0);

	sqlite3_stmt *stmt_begin;
	sqlite3_stmt *stmt_commit;
	SQLOK(sqlite3_prepare_v2(db, "BEGIN", -1, &stmt_begin, NULL));
	SQLOK(sqlite3_prepare_v2(db, "COMMIT", -1, &stmt_commit, NULL));

	std::string bit;
	int i = 0;
	time_t start = time(0);
	time_t t = start;
	SQLRES(sqlite3_step(stmt_begin), SQLITE_DONE);
	sqlite3_reset(stmt_begin);
	do {
		ActionRow row;
		row.id = 0;

		// Get the timestamp
		std::getline(fh, bit, ' ');
		bit = trim(bit);
		if (!atoi(bit.c_str())) {
			std::getline(fh, bit);
			continue;
		}
		row.timestamp = atoi(bit.c_str());

		// Get the actor
		row.actor = getActorId(deSerializeJsonString(fh));

		// Get the action type
		std::getline(fh, bit, '[');
		std::getline(fh, bit, ' ');

		if (bit == "modify_inventory_stack") {
			row.type = RollbackAction::TYPE_MODIFY_INVENTORY_STACK;
			row.location = trim(deSerializeJsonString(fh));
			std::getline(fh, bit, ' ');
			row.list     = trim(deSerializeJsonString(fh));
			std::getline(fh, bit, ' ');
			std::getline(fh, bit, ' ');
			row.index    = atoi(trim(bit).c_str());
			std::getline(fh, bit, ' ');
			row.add      = (int)(trim(bit) == "add");
			row.stack.deSerialize(deSerializeJsonString(fh));
			row.stack.id = getNodeId(row.stack.name);
			std::getline(fh, bit);
		} else if (bit == "set_node") {
			row.type = RollbackAction::TYPE_SET_NODE;
			std::getline(fh, bit, '(');
			std::getline(fh, bit, ',');
			row.x       = atoi(trim(bit).c_str());
			std::getline(fh, bit, ',');
			row.y       = atoi(trim(bit).c_str());
			std::getline(fh, bit, ')');
			row.z       = atoi(trim(bit).c_str());
			std::getline(fh, bit, ' ');
			row.oldNode = getNodeId(trim(deSerializeJsonString(fh)));
			std::getline(fh, bit, ' ');
			std::getline(fh, bit, ' ');
			row.oldParam1 = atoi(trim(bit).c_str());
			std::getline(fh, bit, ' ');
			row.oldParam2 = atoi(trim(bit).c_str());
			row.oldMeta   = trim(deSerializeJsonString(fh));
			std::getline(fh, bit, ' ');
			row.newNode   = getNodeId(trim(deSerializeJsonString(fh)));
			std::getline(fh, bit, ' ');
			std::getline(fh, bit, ' ');
			row.newParam1 = atoi(trim(bit).c_str());
			std::getline(fh, bit, ' ');
			row.newParam2 = atoi(trim(bit).c_str());
			row.newMeta   = trim(deSerializeJsonString(fh));
			std::getline(fh, bit, ' ');
			std::getline(fh, bit, ' ');
			std::getline(fh, bit);
			row.guessed = (int)(trim(bit) == "actor_is_guess");
		} else {
			errorstream << "Unrecognized rollback action type \""
				<< bit << "\"!" << std::endl;
			continue;
		}

		registerRow(row);
		++i;

		if (time(0) - t >= 1) {
			SQLRES(sqlite3_step(stmt_commit), SQLITE_DONE);
			sqlite3_reset(stmt_commit);
			t = time(0);
			std::cout
				<< " Done: " << static_cast<int>((static_cast<float>(fh.tellg()) / static_cast<float>(file_size)) * 100) << "%"
				<< " Speed: " << i / (t - start) << "/second     \r" << std::flush;
			SQLRES(sqlite3_step(stmt_begin), SQLITE_DONE);
			sqlite3_reset(stmt_begin);
		}
	} while (fh.good());
	SQLRES(sqlite3_step(stmt_commit), SQLITE_DONE);
	sqlite3_reset(stmt_commit);

	SQLOK(sqlite3_finalize(stmt_begin));
	SQLOK(sqlite3_finalize(stmt_commit));

	std::cout
		<< " Done: 100%                                  " << std::endl
		<< "Now you can delete the old rollback.txt file." << std::endl;
}

// Get nearness factor for subject's action for this action
// Return value: 0 = impossible, >0 = factor
float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p,
		time_t suspect_t, v3s16 action_p, time_t action_t)
{
	// Suspect cannot cause things in the past
	if (action_t < suspect_t) {
		return 0;        // 0 = cannot be
	}
	// Start from 100
	int f = 100;
	// Distance (1 node = -x points)
	f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1));
	// Time (1 second = -x points)
	f -= 1 * (action_t - suspect_t);
	// If is a guess, halve the points
	if (is_guess) {
		f *= 0.5;
	}
	// Limit to 0
	if (f < 0) {
		f = 0;
	}
	return f;
}

void RollbackManager::reportAction(const RollbackAction &action_)
{
	// Ignore if not important
	if (!action_.isImportant(gamedef)) {
		return;
	}

	RollbackAction action = action_;
	action.unix_time = time(0);

	// Figure out actor
	action.actor = current_actor;
	action.actor_is_guess = current_actor_is_guess;

	if (action.actor.empty()) { // If actor is not known, find out suspect or cancel
		v3s16 p;
		if (!action.getPosition(&p)) {
			return;
		}

		action.actor = getSuspect(p, 83, 1);
		if (action.actor.empty()) {
			return;
		}

		action.actor_is_guess = true;
	}

	addAction(action);
}

std::string RollbackManager::getActor()
{
	return current_actor;
}

bool RollbackManager::isActorGuess()
{
	return current_actor_is_guess;
}

void RollbackManager::setActor(const std::string & actor, bool is_guess)
{
	current_actor = actor;
	current_actor_is_guess = is_guess;
}

std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut,
		float min_nearness)
{
	if (current_actor != "") {
		return current_actor;
	}
	int cur_time = time(0);
	time_t first_time = cur_time - (100 - min_nearness);
	RollbackAction likely_suspect;
	float likely_suspect_nearness = 0;
	for (std::list<RollbackAction>::const_reverse_iterator
	     i = action_latest_buffer.rbegin();
	     i != action_latest_buffer.rend(); ++i) {
		if (i->unix_time < first_time) {
			break;
		}
		if (i->actor == "") {
			continue;
		}
		// Find position of suspect or continue
		v3s16 suspect_p;
		if (!i->getPosition(&suspect_p)) {
			continue;
		}
		float f = getSuspectNearness(i->actor_is_guess, suspect_p,
					     i->unix_time, p, cur_time);
		if (f >= min_nearness && f > likely_suspect_nearness) {
			likely_suspect_nearness = f;
			likely_suspect = *i;
			if (likely_suspect_nearness >= nearness_shortcut) {
				break;
			}
		}
	}
	// No likely suspect was found
	if (likely_suspect_nearness == 0) {
		return "";
	}
	// Likely suspect was found
	return likely_suspect.actor;
}

void RollbackManager::flush()
{
	sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);

	std::list<RollbackAction>::const_iterator iter;

	for (iter  = action_todisk_buffer.begin();
			iter != action_todisk_buffer.end();
			++iter) {
		if (iter->actor == "") {
			continue;
		}

		registerRow(actionRowFromRollbackAction(*iter));
	}

	sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
	action_todisk_buffer.clear();
}

void RollbackManager::addAction(const RollbackAction & action)
{
	action_todisk_buffer.push_back(action);
	action_latest_buffer.push_back(action);

	// Flush to disk sometimes
	if (action_todisk_buffer.size() >= 500) {
		flush();
	}
}

std::list<RollbackAction> RollbackManager::getEntriesSince(time_t first_time)
{
	flush();
	return getActionsSince(first_time);
}

std::list<RollbackAction> RollbackManager::getNodeActors(v3s16 pos, int range,
		time_t seconds, int limit)
{
	flush();
	time_t cur_time = time(0);
	time_t first_time = cur_time - seconds;

	return getActionsSince_range(first_time, pos, range, limit);
}

std::list<RollbackAction> RollbackManager::getRevertActions(
		const std::string &actor_filter,
		time_t seconds)
{
	time_t cur_time = time(0);
	time_t first_time = cur_time - seconds;

	flush();

	return getActionsSince(first_time, actor_filter);
}


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 ROLLBACK_HEADER
#define ROLLBACK_HEADER

#include <string>
#include "irr_v3d.h"
#include "rollback_interface.h"
#include <list>
#include <vector>
#include "sqlite3.h"

class IGameDef;

struct ActionRow;
struct Entity;

class RollbackManager: public IRollbackManager
{
public:
	RollbackManager(const std::string & world_path, IGameDef * gamedef);
	~RollbackManager();

	void reportAction(const RollbackAction & action_);
	std::string getActor();
	bool isActorGuess();
	void setActor(const std::string & actor, bool is_guess);
	std::string getSuspect(v3s16 p, float nearness_shortcut,
			float min_nearness);
	void flush();

	void addAction(const RollbackAction & action);
	std::list<RollbackAction> getEntriesSince(time_t first_time);
	std::list<RollbackAction> getNodeActors(v3s16 pos, int range,
			time_t seconds, int limit);
	std::list<RollbackAction> getRevertActions(
			const std::string & actor_filter, time_t seconds);

private:
	void registerNewActor(const int id, const std::string & name);
	void registerNewNode(const int id, const std::string & name);
	int getActorId(const std::string & name);
	int getNodeId(const std::string & name);
	const char * getActorName(const int id);
	const char * getNodeName(const int id);
	bool createTables();
	bool initDatabase();
	bool registerRow(const ActionRow & row);
	const std::list<ActionRow> actionRowsFromSelect(sqlite3_stmt * stmt);
	ActionRow actionRowFromRollbackAction(const RollbackAction & action);
	const std::list<RollbackAction> rollbackActionsFromActionRows(
			const std::list<ActionRow> & rows);
	const std::list<ActionRow> getRowsSince(time_t firstTime,
			const std::string & actor);
	const std::list<ActionRow> getRowsSince_range(time_t firstTime, v3s16 p,
			int range, int limit);
	const std::list<RollbackAction> getActionsSince_range(time_t firstTime, v3s16 p,
			int range, int limit);
	const std::list<RollbackAction> getActionsSince(time_t firstTime,
			const std::string & actor = "");
	void migrate(const std::string & filepath);
	static float getSuspectNearness(bool is_guess, v3s16 suspect_p,
		time_t suspect_t, v3s16 action_p, time_t action_t);

	IGameDef * gamedef;

	std::string current_actor;
	bool current_actor_is_guess;

	std::list<RollbackAction> action_todisk_buffer;
	std::list<RollbackAction> action_latest_buffer;

	std::string database_path;
	sqlite3 * db;
	sqlite3_stmt * stmt_insert;
	sqlite3_stmt * stmt_replace;
	sqlite3_stmt * stmt_select;
	sqlite3_stmt * stmt_select_range;
	sqlite3_stmt * stmt_select_withActor;
	sqlite3_stmt * stmt_knownActor_select;
	sqlite3_stmt * stmt_knownActor_insert;
	sqlite3_stmt * stmt_knownNode_select;
	sqlite3_stmt * stmt_knownNode_insert;

	std::vector<Entity> knownActors;
	std::vector<Entity> knownNodes;
};

#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 "rollback_interface.h"
#include <sstream>
#include "util/serialize.h"
#include "util/string.h"
#include "util/numeric.h"
#include "util/basic_macros.h"
#include "map.h"
#include "gamedef.h"
#include "nodedef.h"
#include "nodemetadata.h"
#include "exceptions.h"
#include "log.h"
#include "inventorymanager.h"
#include "inventory.h"
#include "mapblock.h"

RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
{
	INodeDefManager *ndef = gamedef->ndef();
	MapNode n = map->getNodeNoEx(p);
	name = ndef->get(n).name;
	param1 = n.param1;
	param2 = n.param2;
	NodeMetadata *metap = map->getNodeMetadata(p);
	if (metap) {
		std::ostringstream os(std::ios::binary);
		metap->serialize(os, 1); // FIXME: version bump??
		meta = os.str();
	}
}

std::string RollbackAction::toString() const
{
	std::ostringstream os(std::ios::binary);
	switch (type) {
	case TYPE_SET_NODE:
		os << "set_node " << PP(p);
		os << ": (" << serializeJsonString(n_old.name);
		os << ", " << itos(n_old.param1);
		os << ", " << itos(n_old.param2);
		os << ", " << serializeJsonString(n_old.meta);
		os << ") -> (" << serializeJsonString(n_new.name);
		os << ", " << itos(n_new.param1);
		os << ", " << itos(n_new.param2);
		os << ", " << serializeJsonString(n_new.meta);
		os << ')';
	case TYPE_MODIFY_INVENTORY_STACK:
		os << "modify_inventory_stack (";
		os << serializeJsonString(inventory_location);
		os << ", " << serializeJsonString(inventory_list);
		os << ", " << inventory_index;
		os << ", " << (inventory_add ? "add" : "remove");
		os << ", " << serializeJsonString(inventory_stack.getItemString());
		os << ')';
	default:
		return "<unknown action>";
	}
	return os.str();
}

bool RollbackAction::isImportant(IGameDef *gamedef) const
{
	if (type != TYPE_SET_NODE)
		return true;
	// If names differ, action is always important
	if(n_old.name != n_new.name)
		return true;
	// If metadata differs, action is always important
	if(n_old.meta != n_new.meta)
		return true;
	INodeDefManager *ndef = gamedef->ndef();
	// Both are of the same name, so a single definition is needed
	const ContentFeatures &def = ndef->get(n_old.name);
	// If the type is flowing liquid, action is not important
	if (def.liquid_type == LIQUID_FLOWING)
		return false;
	// Otherwise action is important
	return true;
}

bool RollbackAction::getPosition(v3s16 *dst) const
{
	switch (type) {
	case TYPE_SET_NODE:
		if (dst) *dst = p;
		return true;
	case TYPE_MODIFY_INVENTORY_STACK: {
		InventoryLocation loc;
		loc.deSerialize(inventory_location);
		if (loc.type != InventoryLocation::NODEMETA) {
			return false;
		}
		if (dst) *dst = loc.p;
		return true; }
	default:
		return false;
	}
}

bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const
{
	try {
		switch (type) {
		case TYPE_NOTHING:
			return true;
		case TYPE_SET_NODE: {
			INodeDefManager *ndef = gamedef->ndef();
			// Make sure position is loaded from disk
			map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
			// Check current node
			MapNode current_node = map->getNodeNoEx(p);
			std::string current_name = ndef->get(current_node).name;
			// If current node not the new node, it's bad
			if (current_name != n_new.name) {
				return false;
			}
			// Create rollback node
			MapNode n(ndef, n_old.name, n_old.param1, n_old.param2);
			// Set rollback node
			try {
				if (!map->addNodeWithEvent(p, n)) {
					infostream << "RollbackAction::applyRevert(): "
						<< "AddNodeWithEvent failed at "
						<< PP(p) << " for " << n_old.name
						<< std::endl;
					return false;
				}
				if (n_old.meta.empty()) {
					map->removeNodeMetadata(p);
				} else {
					NodeMetadata *meta = map->getNodeMetadata(p);
					if (!meta) {
						meta = new NodeMetadata(gamedef->idef());
						if (!map->setNodeMetadata(p, meta)) {
							delete meta;
							infostream << "RollbackAction::applyRevert(): "
								<< "setNodeMetadata failed at "
								<< PP(p) << " for " << n_old.name
								<< std::endl;
							return false;
						}
					}
					std::istringstream is(n_old.meta, std::ios::binary);
					meta->deSerialize(is, 1); // FIXME: version bump??
				}
				// Inform other things that the meta data has changed
				v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE);
				MapEditEvent event;
				event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
				event.p = blockpos;
				map->dispatchEvent(&event);
				// Set the block to be saved
				MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
				if (block) {
					block->raiseModified(MOD_STATE_WRITE_NEEDED,
						MOD_REASON_REPORT_META_CHANGE);
				}
			} catch (InvalidPositionException &e) {
				infostream << "RollbackAction::applyRevert(): "
					<< "InvalidPositionException: " << e.what()
					<< std::endl;
				return false;
			}
			// Success
			return true; }
		case TYPE_MODIFY_INVENTORY_STACK: {
			InventoryLocation loc;
			loc.deSerialize(inventory_location);
			Inventory *inv = imgr->getInventory(loc);
			if (!inv) {
				infostream << "RollbackAction::applyRevert(): Could not get "
					"inventory at " << inventory_location << std::endl;
				return false;
			}
			InventoryList *list = inv->getList(inventory_list);
			if (!list) {
				infostream << "RollbackAction::applyRevert(): Could not get "
					"inventory list \"" << inventory_list << "\" in "
					<< inventory_location << std::endl;
				return false;
			}
			if (list->getSize() <= inventory_index) {
				infostream << "RollbackAction::applyRevert(): List index "
					<< inventory_index << " too large in "
					<< "inventory list \"" << inventory_list << "\" in "
					<< inventory_location << std::endl;
				return false;
			}

			// If item was added, take away item, otherwise add removed item
			if (inventory_add) {
				// Silently ignore different current item
				if (list->getItem(inventory_index).name !=
						gamedef->idef()->getAlias(inventory_stack.name))
					return false;
				list->takeItem(inventory_index, inventory_stack.count);
			} else {
				list->addItem(inventory_index, inventory_stack);
			}
			// Inventory was modified; send to clients
			imgr->setInventoryModified(loc);
			return true; }
		default:
			errorstream << "RollbackAction::applyRevert(): type not handled"
				<< std::endl;
			return false;
		}
	} catch(SerializationError &e) {
		errorstream << "RollbackAction::applyRevert(): n_old.name=" << n_old.name
				<< ", SerializationError: " << e.what() << std::endl;
	}
	return false;
}


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 ROLLBACK_INTERFACE_HEADER
#define ROLLBACK_INTERFACE_HEADER

#include "irr_v3d.h"
#include <string>
#include <iostream>
#include <list>
#include "exceptions.h"
#include "inventory.h"

class Map;
class IGameDef;
struct MapNode;
class InventoryManager;

struct RollbackNode
{
	std::string name;
	int param1;
	int param2;
	std::string meta;

	bool operator == (const RollbackNode &other)
	{
		return (name == other.name && param1 == other.param1 &&
				param2 == other.param2 && meta == other.meta);
	}
	bool operator != (const RollbackNode &other) { return !(*this == other); }

	RollbackNode():
		param1(0),
		param2(0)
	{}

	RollbackNode(Map *map, v3s16 p, IGameDef *gamedef);
};

struct RollbackAction
{
	enum Type{
		TYPE_NOTHING,
		TYPE_SET_NODE,
		TYPE_MODIFY_INVENTORY_STACK,
	} type;

	time_t unix_time;
	std::string actor;
	bool actor_is_guess;

	v3s16 p;
	RollbackNode n_old;
	RollbackNode n_new;

	std::string inventory_location;
	std::string inventory_list;
	u32 inventory_index;
	bool inventory_add;
	ItemStack inventory_stack;

	RollbackAction():
		type(TYPE_NOTHING),
		unix_time(0),
		actor_is_guess(false)
	{}

	void setSetNode(v3s16 p_, const RollbackNode &n_old_,
			const RollbackNode &n_new_)
	{
		type = TYPE_SET_NODE;
		p = p_;
		n_old = n_old_;
		n_new = n_new_;
	}

	void setModifyInventoryStack(const std::string &inventory_location_,
			const std::string &inventory_list_, int index_,
			bool add_, const ItemStack &inventory_stack_)
	{
		type = TYPE_MODIFY_INVENTORY_STACK;
		inventory_location = inventory_location_;
		inventory_list = inventory_list_;
		inventory_index = index_;
		inventory_add = add_;
		inventory_stack = inventory_stack_;
	}

	// String should not contain newlines or nulls
	std::string toString() const;

	// Eg. flowing water level changes are not important
	bool isImportant(IGameDef *gamedef) const;

	bool getPosition(v3s16 *dst) const;

	bool applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const;
};

class IRollbackManager
{
public:
	virtual void reportAction(const RollbackAction &action) = 0;
	virtual std::string getActor() = 0;
	virtual bool isActorGuess() = 0;
	virtual void setActor(const std::string &actor, bool is_guess) = 0;
	virtual std::string getSuspect(v3s16 p, float nearness_shortcut,
	                               float min_nearness) = 0;

	virtual ~IRollbackManager() {};
	virtual void flush() = 0;
	// Get all actors that did something to position p, but not further than
	// <seconds> in history
	virtual std::list<RollbackAction> getNodeActors(v3s16 pos, int range,
	                time_t seconds, int limit) = 0;
	// Get actions to revert <seconds> of history made by <actor>
	virtual std::list<RollbackAction> getRevertActions(const std::string &actor,
	                time_t seconds) = 0;
};

class RollbackScopeActor
{
public:
	RollbackScopeActor(IRollbackManager * rollback_,
			const std::string & actor, bool is_guess = false) :
		rollback(rollback_)
	{
		if (rollback) {
			old_actor = rollback->getActor();
			old_actor_guess = rollback->isActorGuess();
			rollback->setActor(actor, is_guess);
		}
	}
	~RollbackScopeActor()
	{
		if (rollback) {
			rollback->setActor(old_actor, old_actor_guess);
		}
	}

private:
	IRollbackManager * rollback;
	std::string old_actor;
	bool old_actor_guess;
};

#endif


code:
add_subdirectory(common)
add_subdirectory(cpp_api)
add_subdirectory(lua_api)

# Used by server and client
set(common_SCRIPT_SRCS
	${CMAKE_CURRENT_SOURCE_DIR}/scripting_server.cpp
	${common_SCRIPT_COMMON_SRCS}
	${common_SCRIPT_CPP_API_SRCS}
	${common_SCRIPT_LUA_API_SRCS}
	PARENT_SCOPE)

# Used by client only
set(client_SCRIPT_SRCS
	${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/scripting_client.cpp
	${client_SCRIPT_COMMON_SRCS}
	${client_SCRIPT_CPP_API_SRCS}
	${client_SCRIPT_LUA_API_SRCS}
	PARENT_SCOPE)


code:
set(common_SCRIPT_COMMON_SRCS
	${CMAKE_CURRENT_SOURCE_DIR}/c_content.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/c_types.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp
	PARENT_SCOPE)

set(client_SCRIPT_COMMON_SRCS
	PARENT_SCOPE)