aboutsummaryrefslogtreecommitdiff
path: root/builtin/game
Commit message (Expand)AuthorAge
...
* Add player inventory callbacksSmallJoker2018-04-02
* ObjectRef: Add add_velocity() (#3208)you2018-03-31
* core.rotate_node: Do not trigger after_place_node (#6900)SmallJoker2018-03-29
* Optional alpha channel support for entitiesstujones112018-03-29
* Add reasons to on_dieplayer and on_hpchangeAndrew Ward2018-03-28
* is_area_protected: Rename from intersects_protection (#7073)SmallJoker2018-02-25
* /shutdown can't do countdown when using reconnect and/or shutdown message (#7...dopik2018-02-19
* Fix "Ignoring CONTENT_IGNORE redefinition" warning (#4393)you2018-02-18
* Add `on_auth_fail` callback (#7039)red-0012018-02-15
* Item entity: Delete in 'ignore' nodesparamat2018-02-13
* Falling.lua: Delete falling node entities on contact with 'ignore'paramat2018-02-13
* Add minetest.is_player (#7013)you2018-02-05
* Item entity: Prevent motion in ignore nodesparamat2018-02-05
* Add kill chat command (#6992)Wuzzy2018-02-04
* Make chat command + privilege help slightly more accurate (#6964)Wuzzy2018-01-29
* Intersects_protection(): Move from Minetest Game to builtin (#6952)Paramat2018-01-23
* Add callback to preserve node metadata as item metadataashtrayoz2017-12-14
* Builtin: Fix handle_node_drops crash with nil diggerSmallJoker2017-12-12
* Auth handler: Player deletion & Iterator (#6741)sfan52017-12-06
* Make core.auth_table private and structure builtin/auth.luasfan52017-12-01
* Clearobjects: Send progress messages to terminal using actionstreamparamat2017-11-24
* Sounds: Add falling node soundsAuke Kok2017-11-14
* Make use of safe file writing in auth handler (fixes #6576)sfan52017-11-08
* Item entities: Enable item collision detection for sudden movementDTA72017-11-06
* Fix default item callbacks to work with nil users (#5819)raymoo2017-10-28
* Do not grant all privs to the admin - changes game behavior (#6460)lhofhansl2017-10-07
* Implement mod communication channels (#6351)Loïc Blot2017-09-26
* Customizeable max breath for players (#6411)SmallJoker2017-09-15
* Item drop: Tune to land exactly 2 nodes away with level view (#6410)Paramat2017-09-13
* Allow the join/leave message to be overridden by mods.red-0012017-09-11
* Fix dropped item look (#6370)you2017-09-11
* Fix crash on revocation of removed privilegerubenwardy2017-09-08
* Prevent /spawnentity from spawning unknown entity (#6388)Wuzzy2017-09-08
* Implement minetest.register_can_bypass_userlimit (#6369)Loïc Blot2017-09-04
* Remove nodeupdate and nodeupdate_singleRui2017-08-30
* Zoom: Move enabling zoom to a new player object propertyparamat2017-08-30
* Fix mistake when calling on_priv_grant/revoke, and document them (#6341)rubenwardy2017-08-29
* Lua API: Add function to deregister single biomes (#5445)zeuner2017-08-27
* Add on_grant and on_revoke callbacks (#4713)rubenwardy2017-08-26
* Check item_drop amount clientside (#6242)rubenwardy2017-08-26
* Builtin item: Tidy up (#4370)SmallJoker2017-08-24
* Respect object property hp_max field for players (#6287)SmallJoker2017-08-23
* Make dropped items colorableDániel Juhász2017-08-14
* Add slippery group for nodes (players/items slide)Wuzzy2017-08-13
* Abort if static_spawnpoint is an invalid setting instead of just giving an er...Hybrid Dog2017-08-12
* Create a filesystem abstraction layer for CSM and only allow accessing files ...red-0012017-06-30
* Helper methods for hardware colorization (#5870)Dániel Juhász2017-06-24
* Automatic item and node colorization (#5640)Dániel Juhász2017-06-20
* Improve chatcommand params consistency (#5985)Ezhh2017-06-15
* [CSM] Add function to get player privileges (#5933)red-0012017-06-07
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579
/*
Minetest
Copyright (C) 2014-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
Copyright (C) 2015-2018 paramat

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 <fstream>
#include <typeinfo>
#include "mg_schematic.h"
#include "server.h"
#include "mapgen.h"
#include "emerge.h"
#include "map.h"
#include "mapblock.h"
#include "log.h"
#include "util/numeric.h"
#include "util/serialize.h"
#include "serialization.h"
#include "filesys.h"
#include "voxelalgorithms.h"

///////////////////////////////////////////////////////////////////////////////


SchematicManager::SchematicManager(Server *server) :
	ObjDefManager(server, OBJDEF_SCHEMATIC),
	m_server(server)
{
}


void SchematicManager::clear()
{
	EmergeManager *emerge = m_server->getEmergeManager();

	// Remove all dangling references in Decorations
	DecorationManager *decomgr = emerge->decomgr;
	for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
		Decoration *deco = (Decoration *)decomgr->getRaw(i);

		try {
			DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco);
			if (dschem)
				dschem->schematic = NULL;
		} catch (const std::bad_cast &) {
		}
	}

	ObjDefManager::clear();
}


///////////////////////////////////////////////////////////////////////////////


Schematic::Schematic()
= default;


Schematic::~Schematic()
{
	delete []schemdata;
	delete []slice_probs;
}


void Schematic::resolveNodeNames()
{
	getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR);

	size_t bufsize = size.X * size.Y * size.Z;
	for (size_t i = 0; i != bufsize; i++) {
		content_t c_original = schemdata[i].getContent();
		content_t c_new = c_nodes[c_original];
		schemdata[i].setContent(c_new);
	}
}


void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place)
{
	sanity_check(m_ndef != NULL);

	int xstride = 1;
	int ystride = size.X;
	int zstride = size.X * size.Y;

	s16 sx = size.X;
	s16 sy = size.Y;
	s16 sz = size.Z;

	int i_start, i_step_x, i_step_z;
	switch (rot) {
		case ROTATE_90:
			i_start  = sx - 1;
			i_step_x = zstride;
			i_step_z = -xstride;
			SWAP(s16, sx, sz);
			break;
		case ROTATE_180:
			i_start  = zstride * (sz - 1) + sx - 1;
			i_step_x = -xstride;
			i_step_z = -zstride;
			break;
		case ROTATE_270:
			i_start  = zstride * (sz - 1);
			i_step_x = -zstride;
			i_step_z = xstride;
			SWAP(s16, sx, sz);
			break;
		default:
			i_start  = 0;
			i_step_x = xstride;
			i_step_z = zstride;
	}

	s16 y_map = p.Y;
	for (s16 y = 0; y != sy; y++) {
		if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) &&
			(slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
			continue;

		for (s16 z = 0; z != sz; z++) {
			u32 i = z * i_step_z + y * ystride + i_start;
			for (s16 x = 0; x != sx; x++, i += i_step_x) {
				v3s16 pos(p.X + x, y_map, p.Z + z);
				if (!vm->m_area.contains(pos))
					continue;

				if (schemdata[i].getContent() == CONTENT_IGNORE)
					continue;

				u8 placement_prob     = schemdata[i].param1 & MTSCHEM_PROB_MASK;
				bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;

				if (placement_prob == MTSCHEM_PROB_NEVER)
					continue;

				u32 vi = vm->m_area.index(pos);
				if (!force_place && !force_place_node) {
					content_t c = vm->m_data[vi].getContent();
					if (c != CONTENT_AIR && c != CONTENT_IGNORE)
						continue;
				}

				if ((placement_prob != MTSCHEM_PROB_ALWAYS) &&
					(placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
					continue;

				vm->m_data[vi] = schemdata[i];
				vm->m_data[vi].param1 = 0;

				if (rot)
					vm->m_data[vi].rotateAlongYAxis(m_ndef, rot);
			}
		}
		y_map++;
	}
}


bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
	Rotation rot, bool force_place)
{
	assert(vm != NULL);
	assert(schemdata != NULL);
	sanity_check(m_ndef != NULL);

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

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

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

	blitToVManip(vm, p, rot, force_place);

	return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1, 1, 1)));
}

void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
	Rotation rot, bool force_place)
{
	std::map<v3s16, MapBlock *> lighting_modified_blocks;
	std::map<v3s16, MapBlock *> modified_blocks;
	std::map<v3s16, MapBlock *>::iterator it;

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

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

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

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

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

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

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

	voxalgo::blit_back_with_light(map, &vm, &modified_blocks);

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

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

	map->dispatchEvent(event);
}


bool Schematic::deserializeFromMts(std::istream *is,
	std::vector<std::string> *names)
{
	std::istream &ss = *is;
	content_t cignore = CONTENT_IGNORE;
	bool have_cignore = false;

	//// Read signature
	u32 signature = readU32(ss);
	if (signature != MTSCHEM_FILE_SIGNATURE) {
		errorstream << __FUNCTION__ << ": invalid schematic "
			"file" << std::endl;
		return false;
	}

	//// Read version
	u16 version = readU16(ss);
	if (version > MTSCHEM_FILE_VER_HIGHEST_READ) {
		errorstream << __FUNCTION__ << ": unsupported schematic "
			"file version" << std::endl;
		return false;
	}

	//// Read size
	size = readV3S16(ss);

	//// Read Y-slice probability values
	delete []slice_probs;
	slice_probs = new u8[size.Y];
	for (int y = 0; y != size.Y; y++)
		slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD;

	//// Read node names
	u16 nidmapcount = readU16(ss);
	for (int i = 0; i != nidmapcount; i++) {
		std::string name = deSerializeString(ss);

		// Instances of "ignore" from v1 are converted to air (and instances
		// are fixed to have MTSCHEM_PROB_NEVER later on).
		if (name == "ignore") {
			name = "air";
			cignore = i;
			have_cignore = true;
		}

		names->push_back(name);
	}

	//// Read node data
	size_t nodecount = size.X * size.Y * size.Z;

	delete []schemdata;
	schemdata = new MapNode[nodecount];

	MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata,
		nodecount, 2, 2, true);

	// Fix probability values for nodes that were ignore; removed in v2
	if (version < 2) {
		for (size_t i = 0; i != nodecount; i++) {
			if (schemdata[i].param1 == 0)
				schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD;
			if (have_cignore && schemdata[i].getContent() == cignore)
				schemdata[i].param1 = MTSCHEM_PROB_NEVER;
		}
	}

	// Fix probability values for probability range truncation introduced in v4
	if (version < 4) {
		for (s16 y = 0; y != size.Y; y++)
			slice_probs[y] >>= 1;
		for (size_t i = 0; i != nodecount; i++)
			schemdata[i].param1 >>= 1;
	}

	return true;
}


bool Schematic::serializeToMts(std::ostream *os,
	const std::vector<std::string> &names)
{
	std::ostream &ss = *os;

	writeU32(ss, MTSCHEM_FILE_SIGNATURE);         // signature
	writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version
	writeV3S16(ss, size);                         // schematic size

	for (int y = 0; y != size.Y; y++)             // Y slice probabilities
		writeU8(ss, slice_probs[y]);

	writeU16(ss, names.size()); // name count
	for (size_t i = 0; i != names.size(); i++)
		ss << serializeString(names[i]); // node names

	// compressed bulk node data
	MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE,
		schemdata, size.X * size.Y * size.Z, 2, 2, true);

	return true;
}


bool Schematic::serializeToLua(std::ostream *os,
	const std::vector<std::string> &names, bool use_comments, u32 indent_spaces)
{
	std::ostream &ss = *os;

	std::string indent("\t");
	if (indent_spaces > 0)
		indent.assign(indent_spaces, ' ');

	//// Write header
	{
		ss << "schematic = {" << std::endl;
		ss << indent << "size = "
			<< "{x=" << size.X
			<< ", y=" << size.Y
			<< ", z=" << size.Z
			<< "}," << std::endl;
	}

	//// Write y-slice probabilities
	{
		ss << indent << "yslice_prob = {" << std::endl;

		for (u16 y = 0; y != size.Y; y++) {
			u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK;

			ss << indent << indent << "{"
				<< "ypos=" << y
				<< ", prob=" << (u16)probability * 2
				<< "}," << std::endl;
		}

		ss << indent << "}," << std::endl;
	}

	//// Write node data
	{
		ss << indent << "data = {" << std::endl;

		u32 i = 0;
		for (u16 z = 0; z != size.Z; z++)
		for (u16 y = 0; y != size.Y; y++) {
			if (use_comments) {
				ss << std::endl
					<< indent << indent
					<< "-- z=" << z
					<< ", y=" << y << std::endl;
			}

			for (u16 x = 0; x != size.X; x++, i++) {
				u8 probability   = schemdata[i].param1 & MTSCHEM_PROB_MASK;
				bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;

				ss << indent << indent << "{"
					<< "name=\"" << names[schemdata[i].getContent()]
					<< "\", prob=" << (u16)probability * 2
					<< ", param2=" << (u16)schemdata[i].param2;

				if (force_place)
					ss << ", force_place=true";

				ss << "}," << std::endl;
			}
		}

		ss << indent << "}," << std::endl;
	}

	ss << "}" << std::endl;

	return true;
}


bool Schematic::loadSchematicFromFile(const std::string &filename,
	const NodeDefManager *ndef, StringMap *replace_names)
{
	std::ifstream is(filename.c_str(), std::ios_base::binary);
	if (!is.good()) {
		errorstream << __FUNCTION__ << ": unable to open file '"
			<< filename << "'" << std::endl;
		return false;
	}

	size_t origsize = m_nodenames.size();
	if (!deserializeFromMts(&is, &m_nodenames))
		return false;

	m_nnlistsizes.push_back(m_nodenames.size() - origsize);

	name = filename;

	if (replace_names) {
		for (size_t i = origsize; i < m_nodenames.size(); i++) {
			std::string &node_name = m_nodenames[i];
			StringMap::iterator it = replace_names->find(node_name);
			if (it != replace_names->end())
				node_name = it->second;
		}
	}

	if (ndef)
		ndef->pendNodeResolve(this);

	return true;
}


bool Schematic::saveSchematicToFile(const std::string &filename,
	const NodeDefManager *ndef)
{
	MapNode *orig_schemdata = schemdata;
	std::vector<std::string> ndef_nodenames;
	std::vector<std::string> *names;

	if (m_resolve_done && ndef == NULL)
		ndef = m_ndef;

	if (ndef) {
		names = &ndef_nodenames;

		u32 volume = size.X * size.Y * size.Z;
		schemdata = new MapNode[volume];
		for (u32 i = 0; i != volume; i++)
			schemdata[i] = orig_schemdata[i];

		generate_nodelist_and_update_ids(schemdata, volume, names, ndef);
	} else { // otherwise, use the names we have on hand in the list
		names = &m_nodenames;
	}

	std::ostringstream os(std::ios_base::binary);
	bool status = serializeToMts(&os, *names);

	if (ndef) {
		delete []schemdata;
		schemdata = orig_schemdata;
	}

	if (!status)
		return false;

	return fs::safeWriteToFile(filename, os.str());
}


bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2)
{
	MMVManip *vm = new MMVManip(map);

	v3s16 bp1 = getNodeBlockPos(p1);
	v3s16 bp2 = getNodeBlockPos(p2);
	vm->initialEmerge(bp1, bp2);

	size = p2 - p1 + 1;

	slice_probs = new u8[size.Y];
	for (s16 y = 0; y != size.Y; y++)
		slice_probs[y] = MTSCHEM_PROB_ALWAYS;

	schemdata = new MapNode[size.X * size.Y * size.Z];

	u32 i = 0;
	for (s16 z = p1.Z; z <= p2.Z; z++)
	for (s16 y = p1.Y; y <= p2.Y; y++) {
		u32 vi = vm->m_area.index(p1.X, y, z);
		for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) {
			schemdata[i] = vm->m_data[vi];
			schemdata[i].param1 = MTSCHEM_PROB_ALWAYS;
		}
	}

	delete vm;
	return true;
}


void Schematic::applyProbabilities(v3s16 p0,
	std::vector<std::pair<v3s16, u8> > *plist,
	std::vector<std::pair<s16, u8> > *splist)
{
	for (size_t i = 0; i != plist->size(); i++) {
		v3s16 p = (*plist)[i].first - p0;
		int index = p.Z * (size.Y * size.X) + p.Y * size.X + p.X;
		if (index < size.Z * size.Y * size.X) {
			u8 prob = (*plist)[i].second;
			schemdata[index].param1 = prob;

			// trim unnecessary node names from schematic
			if (prob == MTSCHEM_PROB_NEVER)
				schemdata[index].setContent(CONTENT_AIR);
		}
	}

	for (size_t i = 0; i != splist->size(); i++) {
		s16 y = (*splist)[i].first - p0.Y;
		slice_probs[y] = (*splist)[i].second;
	}
}


void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
	std::vector<std::string> *usednodes, const NodeDefManager *ndef)
{
	std::unordered_map<content_t, content_t> nodeidmap;
	content_t numids = 0;

	for (size_t i = 0; i != nodecount; i++) {
		content_t id;
		content_t c = nodes[i].getContent();

		std::unordered_map<content_t, content_t>::const_iterator it = nodeidmap.find(c);
		if (it == nodeidmap.end()) {
			id = numids;
			numids++;

			usednodes->push_back(ndef->get(c).name);
			nodeidmap.insert(std::make_pair(c, id));
		} else {
			id = it->second;
		}
		nodes[i].setContent(id);
	}
}