aboutsummaryrefslogtreecommitdiff
path: root/src/mg_schematic.cpp
blob: 0b95fa267f7796e9f946d71b2022006be0e6885c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
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
580
581
582
583
584
585
586
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

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 "gamedef.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"

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


SchematicManager::SchematicManager(IGameDef *gamedef) :
	ObjDefManager(gamedef, OBJDEF_SCHEMATIC)
{
	m_gamedef = gamedef;
}


void SchematicManager::clear()
{
	EmergeManager *emerge = m_gamedef->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 (std::bad_cast) {
		}
	}

	ObjDefManager::clear();
}


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


Schematic::Schematic()
{
	schemdata   = NULL;
	slice_probs = NULL;
	flags       = 0;
	size        = v3s16(0, 0, 0);
}


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) {
				u32 vi = vm->m_area.index(p.X + x, y_map, p.Z + z);
				if (!vm->m_area.contains(vi))
					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;

				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(Map *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);

	vm.blitBackAll(&modified_blocks);

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

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

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

	map->dispatchEvent(&event);
}


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,
	INodeDefManager *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,
	INodeDefManager *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, INodeDefManager *ndef)
{
	std::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::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);
	}
}
/span> pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index) local vdir = vector.normalize(vector.subtract(npos2, npos)) --automatic get_on --needs to know index and path if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then --using the mapping created by the trainlogic globalstep for i, ino in ipairs(self.door_entry) do --fct is the flipstate flag from door animation above local aci = advtrains.path_get_index_by_offset(train, index, ino*fct) local ix1, ix2 = advtrains.path_get_adjacent(train, aci) -- the two wanted positions are ix1 and ix2 + (2nd-1st rotated by 90deg) -- (x z) rotated by 90deg is (-z x) (http://stackoverflow.com/a/4780141) local add = { x = (ix2.z-ix1.z)*train.door_open, y = 0, z = (ix1.x-ix2.x)*train.door_open } local pts1=vector.round(vector.add(ix1, add)) local pts2=vector.round(vector.add(ix2, add)) if minetest.get_item_group(minetest.get_node(pts1).name, "platform")>0 then local ckpts={ pts1, pts2, vector.add(pts1, {x=0, y=1, z=0}), vector.add(pts2, {x=0, y=1, z=0}), } for _,ckpos in ipairs(ckpts) do local cpp=minetest.pos_to_string(ckpos) if advtrains.playersbypts[cpp] then self:on_rightclick(advtrains.playersbypts[cpp]) end end end end end --checking for environment collisions(a 3x3 cube around the center) if not train.recently_collided_with_env then local collides=false local exh = self.extent_h or 1 local exv = self.extent_v or 2 for x=-exh,exh do for y=0,exv do for z=-exh,exh do local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z})) if (advtrains.train_collides(node)) then collides=true end end end end if collides then if self.collision_count and self.collision_count>10 then --enable collision mercy to get trains stuck in walls out of walls --actually do nothing except limiting the velocity to 1 train.velocity=math.min(train.velocity, 1) else train.recently_collided_with_env=true train.velocity=0 self.collision_count=(self.collision_count or 0)+1 end else self.collision_count=nil end end --DisCouple -- FIX: Need to do this after the yaw calculation if data.pos_in_trainparts and data.pos_in_trainparts>1 then if train.velocity==0 then if not self.discouple or not self.discouple.object:getyaw() then atprint(self.id,"trying to spawn discouple") local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span}) local object=minetest.add_entity(dcpl_pos, "advtrains:discouple") if object then local le=object:get_luaentity() le.wagon=self --box is hidden when attached, so unuseful. --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0}) self.discouple=le end end else if self.discouple and self.discouple.object:getyaw() then self.discouple.object:remove() atprint(self.id," removing discouple") end end end --FIX: use index of the wagon, not of the train. local velocity = train.velocity local acceleration = (train.acceleration or 0) local velocityvec = vector.multiply(vdir, velocity) local accelerationvec = vector.multiply(vdir, acceleration) if data.wagon_flipped then yaw=yaw+math.pi end self.updatepct_timer=(self.updatepct_timer or 0)-dtime local updatepct_timer_elapsed = self.updatepct_timer<=0 if not self.old_velocity_vector or not vector.equals(velocityvec, self.old_velocity_vector) or not self.old_acceleration_vector or not vector.equals(accelerationvec, self.old_acceleration_vector) or self.old_yaw~=yaw or updatepct_timer_elapsed then--only send update packet if something changed self.object:setpos(pos) self.object:setvelocity(velocityvec) self.object:setacceleration(accelerationvec) if #self.seats > 0 and self.old_yaw ~= yaw then if not self.player_yaw then self.player_yaw = {} end if not self.old_yaw then self.old_yaw=yaw end for _,name in pairs(data.seatp) do local p = minetest.get_player_by_name(name) if p then if not self.turning then -- save player looking direction offset self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw end -- set player looking direction using calculated offset p:set_look_horizontal((self.player_yaw[name] or 0)+yaw) end end self.turning = true elseif self.old_yaw == yaw then -- train is no longer turning self.turning = false end if self.object.set_rotation then local pitch = math.atan2(vdir.y, math.hypot(vdir.x, vdir.z)) if data.wagon_flipped then pitch = -pitch end self.object:set_rotation({x=pitch, y=yaw, z=0}) else self.object:setyaw(yaw) end self.updatepct_timer=2 if self.update_animation then self:update_animation(train.velocity, self.old_velocity) end if self.custom_on_velocity_change then self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime) end -- remove discouple object, because it will be in a wrong location if not updatepct_timer_elapsed and self.discouple then self.discouple.object:remove() end end self.old_velocity_vector=velocityvec self.old_velocity = train.velocity self.old_acceleration_vector=accelerationvec self.old_yaw=yaw atprintbm("wagon step", t) end) end function wagon:on_rightclick(clicker) return advtrains.pcall(function() if not self:ensure_init() then return end if not clicker or not clicker:is_player() then return end local data = advtrains.wagons[self.id] local pname=clicker:get_player_name() local no=self:get_seatno(pname) if no then if self.seat_groups then local poss={} local sgr=self.seats[no].group for _,access in ipairs(self.seat_groups[sgr].access_to) do if self:check_seat_group_access(pname, access) then poss[#poss+1]={name=self.seat_groups[access].name, key="sgr_"..access} end end if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then poss[#poss+1]={name=attrans("Show Inventory"), key="inv"} end if self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then poss[#poss+1]={name=attrans("Onboard Computer"), key="bordcom"} end if data.owner==pname then poss[#poss+1]={name=attrans("Wagon properties"), key="prop"} end if not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0 then poss[#poss+1]={name=attrans("Get off"), key="off"} else if clicker:get_player_control().sneak then poss[#poss+1]={name=attrans("Get off (forced)"), key="off"} else poss[#poss+1]={name=attrans("(Doors closed)"), key="dcwarn"} end end if #poss==0 then --can't do anything. elseif #poss==1 then self:seating_from_key_helper(pname, {[poss[1].key]=true}, no) else local form = "size[5,"..1+(#poss).."]" for pos,ent in ipairs(poss) do form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]" end minetest.show_formspec(pname, "advtrains_seating_"..self.id, form) end else self:get_off(no) end else --do not attach if already on a train if advtrains.player_to_train_mapping[pname] then return end if self.seat_groups then if #self.seats==0 then if self.has_inventory and self.get_inventory_formspec and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id))) end return end local doors_open = self:train().door_open~=0 or clicker:get_player_control().sneak local allow, rsn=false, "Wagon has no seats!" for _,sgr in ipairs(self.assign_to_seat_group) do allow, rsn = self:check_seat_group_access(pname, sgr) if allow then for seatid, seatdef in ipairs(self.seats) do if seatdef.group==sgr then if (not self.seat_groups[sgr].require_doors_open or doors_open) then if not data.seatp[seatid] then self:get_on(clicker, seatid) return else rsn="Wagon is full." end else rsn="Doors are closed! (try holding sneak key!)" end end end end end minetest.chat_send_player(pname, attrans("Can't get on: "..rsn)) else self:show_get_on_form(pname) end end end) end function wagon:get_on(clicker, seatno) local data = advtrains.wagons[self.id] if not data.seatp then data.seatp={}end if not self.seatpc then self.seatpc={}end--player controls in driver stands if not self.seats[seatno] then return end local oldno=self:get_seatno(clicker:get_player_name()) if oldno then atprint("get_on: clearing oldno",seatno) advtrains.player_to_train_mapping[clicker:get_player_name()]=nil advtrains.clear_driver_hud(clicker:get_player_name()) data.seatp[oldno]=nil end if data.seatp[seatno] and data.seatp[seatno]~=clicker:get_player_name() then atprint("get_on: throwing off",data.seatp[seatno],"from seat",seatno) self:get_off(seatno) end atprint("get_on: attaching",clicker:get_player_name()) data.seatp[seatno] = clicker:get_player_name() self.seatpc[seatno] = clicker:get_player_control_bits() advtrains.player_to_train_mapping[clicker:get_player_name()]=data.train_id clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0}) clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset) end function wagon:get_off_plr(pname) local no=self:get_seatno(pname) if no then self:get_off(no) end end function wagon:get_seatno(pname) local data = advtrains.wagons[self.id] for no, cont in pairs(data.seatp) do if cont==pname then return no end end return nil end function wagon:get_off(seatno) local data = advtrains.wagons[self.id] if not data.seatp[seatno] then return end local pname = data.seatp[seatno]