summaryrefslogtreecommitdiff
path: root/src/nodemetadata.cpp
blob: 308a3385432a5126ee6e4010158e24dbd4f6bdda (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
/*
Minetest-c55
Copyright (C) 2010-2011 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 General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU 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 "nodemetadata.h"
#include "utility.h"
#include "mapnode.h"
#include "exceptions.h"
#include "inventory.h"
#include <sstream>

/*
	NodeMetadata
*/

core::map<u16, NodeMetadata::Factory> NodeMetadata::m_types;

NodeMetadata::NodeMetadata()
{
}

NodeMetadata::~NodeMetadata()
{
}

NodeMetadata* NodeMetadata::deSerialize(std::istream &is)
{
	// Read id
	u8 buf[2];
	is.read((char*)buf, 2);
	s16 id = readS16(buf);
	
	// Read data
	std::string data = deSerializeString(is);
	
	// Find factory function
	core::map<u16, Factory>::Node *n;
	n = m_types.find(id);
	if(n == NULL)
	{
		// If factory is not found, just return.
		dstream<<"WARNING: NodeMetadata: No factory for typeId="
				<<id<<std::endl;
		return NULL;
	}
	
	// Try to load the metadata. If it fails, just return.
	try
	{
		std::istringstream iss(data, std::ios_base::binary);
		
		Factory f = n->getValue();
		NodeMetadata *meta = (*f)(iss);
		return meta;
	}
	catch(SerializationError &e)
	{
		dstream<<"WARNING: NodeMetadata: ignoring SerializationError"<<std::endl;
		return NULL;
	}
}

void NodeMetadata::serialize(std::ostream &os)
{
	u8 buf[2];
	writeU16(buf, typeId());
	os.write((char*)buf, 2);
	
	std::ostringstream oss(std::ios_base::binary);
	serializeBody(oss);
	os<<serializeString(oss.str());
}

void NodeMetadata::registerType(u16 id, Factory f)
{
	core::map<u16, Factory>::Node *n;
	n = m_types.find(id);
	if(n)
		return;
	m_types.insert(id, f);
}

/*
	SignNodeMetadata
*/

// Prototype
SignNodeMetadata proto_SignNodeMetadata("");

SignNodeMetadata::SignNodeMetadata(std::string text):
	m_text(text)
{
	NodeMetadata::registerType(typeId(), create);
}
u16 SignNodeMetadata::typeId() const
{
	return CONTENT_SIGN_WALL;
}
NodeMetadata* SignNodeMetadata::create(std::istream &is)
{
	std::string text = deSerializeString(is);
	return new SignNodeMetadata(text);
}
NodeMetadata* SignNodeMetadata::clone()
{
	return new SignNodeMetadata(m_text);
}
void SignNodeMetadata::serializeBody(std::ostream &os)
{
	os<<serializeString(m_text);
}
std::string SignNodeMetadata::infoText()
{
	return std::string("\"")+m_text+"\"";
}

/*
	ChestNodeMetadata
*/

// Prototype
ChestNodeMetadata proto_ChestNodeMetadata;

ChestNodeMetadata::ChestNodeMetadata()
{
	NodeMetadata::registerType(typeId(), create);
	
	m_inventory = new Inventory();
	m_inventory->addList("0", 8*4);
}
ChestNodeMetadata::~ChestNodeMetadata()
{
	delete m_inventory;
}
u16 ChestNodeMetadata::typeId() const
{
	return CONTENT_CHEST;
}
NodeMetadata* ChestNodeMetadata::create(std::istream &is)
{
	ChestNodeMetadata *d = new ChestNodeMetadata();
	d->m_inventory->deSerialize(is);
	return d;
}
NodeMetadata* ChestNodeMetadata::clone()
{
	ChestNodeMetadata *d = new ChestNodeMetadata();
	*d->m_inventory = *m_inventory;
	return d;
}
void ChestNodeMetadata::serializeBody(std::ostream &os)
{
	m_inventory->serialize(os);
}
std::string ChestNodeMetadata::infoText()
{
	return "Chest";
}
bool ChestNodeMetadata::nodeRemovalDisabled()
{
	/*
		Disable removal if chest contains something
	*/
	InventoryList *list = m_inventory->getList("0");
	if(list == NULL)
		return false;
	if(list->getUsedSlots() == 0)
		return false;
	return true;
}

/*
	FurnaceNodeMetadata
*/

// Prototype
FurnaceNodeMetadata proto_FurnaceNodeMetadata;

FurnaceNodeMetadata::FurnaceNodeMetadata()
{
	NodeMetadata::registerType(typeId(), create);
	
	m_inventory = new Inventory();
	m_inventory->addList("fuel", 1);
	m_inventory->addList("src", 1);
	m_inventory->addList("dst", 4);

	m_step_accumulator = 0;
	m_fuel_totaltime = 0;
	m_fuel_time = 0;
	m_src_totaltime = 0;
	m_src_time = 0;
}
FurnaceNodeMetadata::~FurnaceNodeMetadata()
{
	delete m_inventory;
}
u16 FurnaceNodeMetadata::typeId() const
{
	return CONTENT_FURNACE;
}
NodeMetadata* FurnaceNodeMetadata::clone()
{
	FurnaceNodeMetadata *d = new FurnaceNodeMetadata();
	*d->m_inventory = *m_inventory;
	return d;
}
NodeMetadata* FurnaceNodeMetadata::create(std::istream &is)
{
	FurnaceNodeMetadata *d = new FurnaceNodeMetadata();

	d->m_inventory->deSerialize(is);

	int temp;
	is>>temp;
	d->m_fuel_totaltime = (float)temp/10;
	is>>temp;
	d->m_fuel_time = (float)temp/10;

	return d;
}
void FurnaceNodeMetadata::serializeBody(std::ostream &os)
{
	m_inventory->serialize(os);
	os<<itos(m_fuel_totaltime*10)<<" ";
	os<<itos(m_fuel_time*10)<<" ";
}
std::string FurnaceNodeMetadata::infoText()
{
	//return "Furnace";
	if(m_fuel_time >= m_fuel_totaltime)
	{
		InventoryList *src_list = m_inventory->getList("src");
		assert(src_list);
		InventoryItem *src_item = src_list->getItem(0);

		if(src_item)
			return "Furnace is out of fuel";
		else
			return "Furnace is inactive";
	}
	else
	{
		std::string s = "Furnace is active (";
		s += itos(m_fuel_time/m_fuel_totaltime*100);
		s += "%)";
		return s;
	}
}
void FurnaceNodeMetadata::inventoryModified()
{
	dstream<<"Furnace inventory modification callback"<<std::endl;
}
bool FurnaceNodeMetadata::step(float dtime)
{
	// Update at a fixed frequency
	const float interval = 0.5;
	m_step_accumulator += dtime;
	if(m_step_accumulator < interval)
		return false;
	m_step_accumulator -= interval;
	dtime = interval;

	//dstream<<"Furnace step dtime="<<dtime<<std::endl;
	
	InventoryList *dst_list = m_inventory->getList("dst");
	assert(dst_list);

	InventoryList *src_list = m_inventory->getList("src");
	assert(src_list);
	InventoryItem *src_item = src_list->getItem(0);
	
	// Start only if there are free slots in dst, so that it can
	// accomodate any result item
	if(dst_list->getFreeSlots() > 0 && src_item && src_item->isCookable())
	{
		m_src_totaltime = 3;
	}
	else
	{
		m_src_time = 0;
		m_src_totaltime = 0;
	}

	if(m_fuel_time < m_fuel_totaltime)
	{
		//dstream<<"Furnace is active"<<std::endl;
		m_fuel_time += dtime;
		m_src_time += dtime;
		if(m_src_time >= m_src_totaltime && m_src_totaltime > 0.001
				&& src_item)
		{
			InventoryItem *cookresult = src_item->createCookResult();
			dst_list->addItem(cookresult);
			src_list->decrementMaterials(1);
			m_src_time = 0;
			m_src_totaltime = 0;
		}
		return true;
	}
	
	if(src_item == NULL || m_src_totaltime < 0.001)
	{
		return false;
	}
	
	bool changed = false;

	//dstream<<"Furnace is out of fuel"<<std::endl;

	InventoryList *fuel_list = m_inventory->getList("fuel");
	assert(fuel_list);
	InventoryItem *fuel_item = fuel_list->getItem(0);

	if(ItemSpec(ITEM_MATERIAL, CONTENT_TREE).checkItem(fuel_item))
	{
		m_fuel_totaltime = 10;
		m_fuel_time = 0;
		fuel_list->decrementMaterials(1);
		changed = true;
	}
	else if(ItemSpec(ITEM_MATERIAL, CONTENT_WOOD).checkItem(fuel_item))
	{
		m_fuel_totaltime = 5;
		m_fuel_time = 0;
		fuel_list->decrementMaterials(1);
		changed = true;
	}
	else if(ItemSpec(ITEM_CRAFT, "lump_of_coal").checkItem(fuel_item))
	{
		m_fuel_totaltime = 10;
		m_fuel_time = 0;
		fuel_list->decrementMaterials(1);
		changed = true;
	}
	else
	{
		//dstream<<"No fuel found"<<std::endl;
	}

	return changed;
}

/*
	NodeMetadatalist
*/

void NodeMetadataList::serialize(std::ostream &os)
{
	u8 buf[6];
	
	u16 version = 1;
	writeU16(buf, version);
	os.write((char*)buf, 2);

	u16 count = m_data.size();
	writeU16(buf, count);
	os.write((char*)buf, 2);

	for(core::map<v3s16, NodeMetadata*>::Iterator
			i = m_data.getIterator();
			i.atEnd()==false; i++)
	{
		v3s16 p = i.getNode()->getKey();
		NodeMetadata *data = i.getNode()->getValue();
		
		u16 p16 = p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X;
		writeU16(buf, p16);
		os.write((char*)buf, 2);

		data->serialize(os);
	}
	
}
void NodeMetadataList::deSerialize(std::istream &is)
{
	m_data.clear();

	u8 buf[6];
	
	is.read((char*)buf, 2);
	u16 version = readU16(buf);

	if(version > 1)
	{
		dstream<<__FUNCTION_NAME<<": version "<<version<<" not supported"
				<<std::endl;
		throw SerializationError("NodeMetadataList::deSerialize");
	}
	
	is.read((char*)buf, 2);
	u16 count = readU16(buf);
	
	for(u16 i=0; i<count; i++)
	{
		is.read((char*)buf, 2);
		u16 p16 = readU16(buf);

		v3s16 p(0,0,0);
		p.Z += p16 / MAP_BLOCKSIZE / MAP_BLOCKSIZE;
		p16 -= p.Z * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
		p.Y += p16 / MAP_BLOCKSIZE;
		p16 -= p.Y * MAP_BLOCKSIZE;
		p.X += p16;
		
		NodeMetadata *data = NodeMetadata::deSerialize(is);

		if(data == NULL)
			continue;
		
		if(m_data.find(p))
		{
			dstream<<"WARNING: NodeMetadataList::deSerialize(): "
					<<"already set data at position"
					<<"("<<p.X<<","<<p.Y<<","<<p.Z<<"): Ignoring."
					<<std::endl;
			delete data;
			continue;
		}

		m_data.insert(p, data);
	}
}
	
NodeMetadataList::~NodeMetadataList()
{
	for(core::map<v3s16, NodeMetadata*>::Iterator
			i = m_data.getIterator();
			i.atEnd()==false; i++)
	{
		delete i.getNode()->getValue();
	}
}

NodeMetadata* NodeMetadataList::get(v3s16 p)
{
	core::map<v3s16, NodeMetadata*>::Node *n;
	n = m_data.find(p);
	if(n == NULL)
		return NULL;
	return n->getValue();
}

void NodeMetadataList::remove(v3s16 p)
{
	NodeMetadata *olddata = get(p);
	if(olddata)
	{
		delete olddata;
		m_data.remove(p);
	}
}

void NodeMetadataList::set(v3s16 p, NodeMetadata *d)
{
	remove(p);
	m_data.insert(p, d);
}

bool NodeMetadataList::step(float dtime)
{
	bool something_changed = false;
	for(core::map<v3s16, NodeMetadata*>::Iterator
			i = m_data.getIterator();
			i.atEnd()==false; i++)
	{
		v3s16 p = i.getNode()->getKey();
		NodeMetadata *meta = i.getNode()->getValue();
		bool changed = meta->step(dtime);
		if(changed)
			something_changed = true;
		/*if(res.inventory_changed)
		{
			std::string inv_id;
			inv_id += "nodemeta:";
			inv_id += itos(p.X);
			inv_id += ",";
			inv_id += itos(p.Y);
			inv_id += ",";
			inv_id += itos(p.Z);
			InventoryContext c;
			c.current_player = NULL;
			inv_mgr->inventoryModified(&c, inv_id);
		}*/
	}
	return something_changed;
}

= advtrains.wagons[self.id] if not puncher or not puncher:is_player() then return end if data.owner and puncher:get_player_name()~=data.owner and (not minetest.check_player_privs(puncher, {train_admin = true })) then minetest.chat_send_player(puncher:get_player_name(), attrans("This wagon is owned by @1, you can't destroy it.", data.owner)); return end if self.custom_may_destroy then if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then return end end local itemstack = puncher:get_wielded_item() -- WARNING: This part of the API is guaranteed to change! DO NOT USE! if self.set_livery and itemstack:get_name() == "bike:painter" then self:set_livery(puncher, itemstack, data) return end -- check whether wagon has an inventory. Is is empty? if self.has_inventory then local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id}) if not inv then -- inventory is not initialized when wagon was never loaded - should never happen atwarn("Destroying wagon with inventory, but inventory is not found? Shouldn't happen!") return end for listname, _ in pairs(inv:get_lists()) do if not inv:is_empty(listname) then minetest.chat_send_player(puncher:get_player_name(), attrans("The wagon's inventory is not empty!")); return end end end if #(self:train().trainparts)>1 then minetest.chat_send_player(puncher:get_player_name(), attrans("Wagon needs to be decoupled from other wagons in order to destroy it.")); return end local pc=puncher:get_player_control() if not pc.sneak then minetest.chat_send_player(puncher:get_player_name(), attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon.")) return end if not self:destroy() then return end local inv = puncher:get_inventory() for _,item in ipairs(self.drops or {self.name}) do inv:add_item("main", item) end end function wagon:destroy() --some rules: -- you get only some items back -- single left-click shows warning -- shift leftclick destroys -- not when a driver is inside if self.id then local data = advtrains.wagons[self.id] if not data then atwarn("wagon:destroy(): data is not set!") return end if self.custom_on_destroy then self.custom_on_destroy(self) end for seat,_ in pairs(data.seatp) do self:get_off(seat) end if data.train_id and self:train() then advtrains.remove_train(data.train_id) advtrains.wagons[self.id]=nil if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects end end --atdebug("[wagon ", self.id, "]: destroying") self.object:remove() return true end function wagon:is_driver_stand(seat) if self.seat_groups then return (seat.driving_ctrl_access or self.seat_groups[seat.group].driving_ctrl_access) else return seat.driving_ctrl_access end end function wagon:on_step(dtime) if not self:ensure_init() then return end if advtrains.is_no_action() then self.object:remove() return end local t=os.clock() local pos = self.object:get_pos() local data = advtrains.wagons[self.id] if not pos then --atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)") return end if not data.seatp then data.seatp={} end if not self.seatpc then self.seatpc={} end local train=self:train() local is_in_loaded_area = advtrains.is_node_loaded(pos) --custom on_step function if self.custom_on_step then self:custom_on_step(dtime, data, train) end --driver control for seatno, seat in ipairs(self.seats) do local pname=data.seatp[seatno] local driver=pname and minetest.get_player_by_name(pname) local has_driverstand = pname and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) has_driverstand = has_driverstand and self:is_driver_stand(seat) if has_driverstand and driver then advtrains.update_driver_hud(driver:get_player_name(), self:train(), data.wagon_flipped) elseif driver then --only show the inside text local inside=self:train().text_inside or "" advtrains.set_trainhud(driver:get_player_name(), inside) end if driver and driver:get_player_control_bits()~=self.seatpc[seatno] then local pc=driver:get_player_control() self.seatpc[seatno]=driver:get_player_control_bits() if has_driverstand then --regular driver stand controls advtrains.on_control_change(pc, self:train(), data.wagon_flipped) --bordcom if pc.sneak and pc.jump then self:show_bordcom(data.seatp[seatno]) end --sound horn when required if self.horn_sound and pc.aux1 and not pc.sneak and not self.horn_handle then self.horn_handle = minetest.sound_play(self.horn_sound, { object = self.object, gain = 1.0, -- default max_hear_distance = 128, -- default, uses an euclidean metric loop = true, }) elseif not pc.aux1 and self.horn_handle then minetest.sound_stop(self.horn_handle) self.horn_handle = nil end else -- If on a passenger seat and doors are open, get off when W or D pressed. local pass = data.seatp[seatno] and minetest.get_player_by_name(data.seatp[seatno]) if pass and self:train().door_open~=0 then local pc=pass:get_player_control() if pc.up or pc.down then self:get_off(seatno) end end end if pc.aux1 and pc.sneak then self:get_off(seatno) end end end --check infotext local outside=train.text_outside or "" if setting_show_ids then outside = outside .. "\nT:" .. data.train_id .. " W:" .. self.id .. " O:" .. data.owner end --show off-track information in outside text instead of notifying the whole server about this if train.off_track then outside = outside .."\n!!! Train off track !!!" end if self.infotext_cache~=outside then self.object:set_properties({infotext=outside}) self.infotext_cache=outside end local fct=data.wagon_flipped and -1 or 1 --door animation if self.doors then if (self.door_anim_timer or 0)<=0 then local dstate = (train.door_open or 0) * fct if dstate ~= self.door_state then local at --meaning of the train.door_open field: -- -1: left doors (rel. to train orientation) -- 0: closed -- 1: right doors --this code produces the following behavior: -- if changed from 0 to +-1, play open anim. if changed from +-1 to 0, play close. -- if changed from +-1 to -+1, first close and set 0, then it will detect state change again and run open. if self.door_state == 0 then if self.doors.open.sound then minetest.sound_play(self.doors.open.sound, {object = self.object}) end at=self.doors.open[dstate] self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false) self.door_state = dstate else if self.doors.close.sound then minetest.sound_play(self.doors.close.sound, {object = self.object}) end at=self.doors.close[self.door_state or 1]--in case it has not been set yet self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false) self.door_state = 0 end self.door_anim_timer = at.time end else self.door_anim_timer = (self.door_anim_timer or 0) - dtime end end --for path to be available. if not, skip step if not train.path or train.no_step then self.object:set_velocity({x=0, y=0, z=0}) self.object:set_acceleration({x=0, y=0, z=0}) return end if not data.pos_in_train then return end -- Calculate new position, yaw and direction vector local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train) local 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 IGNORE_WORLD and is_in_loaded_area and 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 -- screw collision mercy train.recently_collided_with_env=true train.velocity=0 advtrains.atc.train_reset_command(train) end end --DisCouple -- FIX: Need to do this after the yaw calculation if is_in_loaded_area and 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:get_yaw() 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:get_yaw() 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 * advtrains.global_slowdown local acceleration = (train.acceleration or 0) * (advtrains.global_slowdown*advtrains.global_slowdown) local velocityvec = vector.multiply(vdir, velocity) local accelerationvec = vector.multiply(vdir, acceleration) if data.wagon_flipped then yaw=yaw+math.pi end -- this timer runs off every 2 seconds. self.updatepct_timer=(self.updatepct_timer or 0)-dtime local updatepct_timer_elapsed = self.updatepct_timer<=0 if updatepct_timer_elapsed then --restart timer self.updatepct_timer=2 -- perform checks that are not frequently needed -- unload entity if out of range (because relevant pr won't be merged in engine) -- This is a WORKAROUND! local players_in = false for sno,pname in pairs(data.seatp) do if minetest.get_player_by_name(pname) then -- Fix: If the RTT is too high, a wagon might be recognized out of range even if a player sits in it -- (client updates position not fast enough) players_in = true break end end if not players_in then if advtrains.wagon_outside_range(pos) then --atdebug("wagon",self.id,"unloading (too far away)") -- Workaround until minetest engine deletes attached sounds if self.sound_loop_handle then minetest.sound_stop(self.sound_loop_handle) end self.object:remove() end end end 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:set_pos(pos) self.object:set_velocity(velocityvec) self.object:set_acceleration(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:set_yaw(yaw) end 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 function wagon:on_rightclick(clicker) 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 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] local clicker = minetest.get_player_by_name(pname) advtrains.player_to_train_mapping[pname]=nil advtrains.clear_driver_hud(pname) data.seatp[seatno]=nil self.seatpc[seatno]=nil if clicker then atprint("get_off: detaching",clicker:get_player_name()) clicker:set_detach() clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0}) local train=self:train() --code as in step - automatic get on if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 and train.index and train.path then local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train) for i, ino in ipairs(self.door_entry) do --atdebug("using door-based",i,ino) local fct=data.wagon_flipped and -1 or 1 local aci = advtrains.path_get_index_by_offset(train, index, ino*fct) local ix1, ix2 = advtrains.path_get_adjacent(train, aci) local d = train.door_open if self.wagon_width then d = d * math.floor(self.wagon_width/2) end -- 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)*d, y = 0, z = (ix1.x-ix2.x)*d } local oadd = { x = (ix2.z-ix1.z)*(d+train.door_open), y = 1, z = (ix1.x-ix2.x)*(d+train.door_open)} local platpos=vector.round(vector.add(ix1, add)) local offpos=vector.round(vector.add(ix1, oadd)) --atdebug("platpos:", platpos, "offpos:", offpos) if minetest.get_item_group(minetest.get_node(platpos).name, "platform")>0 then minetest.after(GETOFF_TP_DELAY, function() clicker:set_pos(offpos) end) --atdebug("tp",offpos) return end --atdebug("nope") end end --if not door_entry, or paths missing, fall back to old method --atdebug("using fallback") local objpos=advtrains.round_vector_floor_y(self.object:getpos()) local yaw=self.object:getyaw() local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4) local offp --abuse helper function for _,r in ipairs({-1, 1}) do --atdebug("offset",r) local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos) offp=vector.add({x=isx and r*2 or 0, y=1, z=not isx and r*2 or 0}, objpos) --atdebug("platpos:", p, "offpos:", offp) if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then minetest.after(GETOFF_TP_DELAY, function() clicker:set_pos(offp) end) --atdebug("tp",offp) return end end --atdebug("nope") end end function wagon:show_get_on_form(pname) if not self.initialized then return end local data = advtrains.wagons[self.id] 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 form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;", "" for seatno, seattbl in ipairs(self.seats) do local addtext, colorcode="", "" if data.seatp and data.seatp[seatno] then colorcode="#FF0000" addtext=" ("..data.seatp[seatno]..")" end form=form..comma..colorcode..seattbl.name..addtext comma="," end form=form..";0,false]" if self.has_inventory and self.get_inventory_formspec then form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]" end minetest.show_formspec(pname, "advtrains_geton_"..self.id, form) end function wagon:show_wagon_properties(pname) --[[ fields: field: driving/couple whitelist button: save ]] local data = advtrains.wagons[self.id] local form="size[5,5]" form = form .. "field[0.5,1;4.5,1;whitelist;Allow these players to access your wagon:;"..minetest.formspec_escape(data.whitelist or "").."]" form = form .. "field[0.5,2;4.5,1;roadnumber;Wagon road number:;"..minetest.formspec_escape(data.roadnumber or "").."]" local fc = "" if data.fc then fc = table.concat(data.fc, "!") end form = form .. "field[0.5,3;4.5,1;fc;Freight Code:;"..fc.."]" if data.fc then if not data.fcind then data.fcind = 1 end if data.fcind > 1 then form=form.."button[0.5,3.5;1,1;fcp;prev FC]" end form=form.."label[1.5,3.5;Current FC:]" local cur = data.fc[data.fcind] or "" form=form.."label[1.5,3.75;"..minetest.formspec_escape(cur).."]" form=form.."button[3.5,3.5;1,1;fcn;next FC]" end form=form.."button_exit[0.5,4.5;4,1;save;"..attrans("Save wagon properties").."]" minetest.show_formspec(pname, "advtrains_prop_"..self.id, form) end --BordCom local function checkcouple(ent) if not ent or not ent:getyaw() then return nil end local le = ent:get_luaentity() if not le or not le.is_couple then return nil end return le end local function checklock(pname, own1, own2, wl1, wl2) return advtrains.check_driving_couple_protection(pname, own1, wl1) or advtrains.check_driving_couple_protection(pname, own2, wl2) end local function split(str, sep) local fields = {} local pattern = string.format("([^%s]+)", sep) str:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end function wagon.set_fc(data, fcstr) data.fc = split(fcstr, "!") if not data.fcind then data.fcind = 1 elseif data.fcind > #data.fc then data.fcind = #data.fc end end function wagon.prev_fc(data) if data.fcind > 1 then data.fcind = data.fcind -1 end if data.fcind == 1 and data.fcrev then data.fcrev = nil end end function wagon.next_fc(data) if not data.fc then return end if data.fcrev then wagon.prev_fc(data) return end if data.fcind < #data.fc then data.fcind = data.fcind + 1 else data.fcind = 1 end if data.fcind == #data.fc and data.fc[data.fcind] == "?" then data.fcrev = true wagon.prev_fc(data) return end end function advtrains.get_cur_fc(data) if not ( data.fc and data.fcind ) then return "" end return data.fc[data.fcind] or "" end function advtrains.step_fc(data) wagon.next_fc(data) end function wagon:show_bordcom(pname) if not self:train() then return end local train = self:train() local data = advtrains.wagons[self.id] local linhei local form = "size[11,9]label[0.5,0;AdvTrains Boardcom v0.1]" form=form.."textarea[0.5,1.5;7,1;text_outside;"..attrans("Text displayed outside on train")..";"..(minetest.formspec_escape(train.text_outside or "")).."]" form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(minetest.formspec_escape(train.text_inside or "")).."]"