aboutsummaryrefslogtreecommitdiff
path: root/assets/manual.html.LyXconv/manual.html
blob: fe1c13201cc5c69b743d57c7aea639950387fc5c (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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"  
  "http://www.w3.org/TR/html4/loose.dtd">  
<html > 
<head><title></title> 
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> 
<meta name="generator" content="TeX4ht (http://www.tug.org/tex4ht/)"> 
<meta name="originator" content="TeX4ht (http://www.tug.org/tex4ht/)"> 
<!-- html --> 
<meta name="src" content="manual.tex"> 
<link rel="stylesheet" type="text/css" href="manual.css"> 
</head><body 
>
<!--l. 15--><p class="noindent" ><span 
class="ecsx-1728">Advanced</span>
<span 
class="ecsx-1728">Trains</span>
<!--l. 17--><p class="indent" >   This mod aims to provide realistic, good-looking and functional trains by introducing a revolutionary rail placement system. It
features several wagons that can be coupled together.
<!--l. 21--><p class="indent" >   This mod is not finished. If you miss features, suggest them, but do not denounce this mod just because they are not yet implemented.
They will be.
<span 
class="ecsx-1200">Placing</span>
<span 
class="ecsx-1200">Rails</span>
<!--l. 27--><p class="noindent" >Minetest&#8217;s in-house rail system features rails that turn at an angle of 90 degrees &#8211; totally impractical for the use with realistic trains. So
we have our own rails. Remember: Carts can&#8217;t drive on the rails provided by this mod, as do trains not drive on minetest&#8217;s default rails
because of their different track widths.
<!--l. 33--><p class="indent" >   First, craft some rails.
<!--l. 35--><p class="indent" >   <img 
src="0_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-43-29.png" alt="PIC"  
>
<!--l. 37--><p class="indent" >   Now, place one at any position and another one right next to it: you have made your first railway track!
<!--l. 40--><p class="indent" >   To learn how to make turns have a look at the following examples. A rail node has been placed only at the red-marked
places.
<!--l. 43--><p class="indent" >   <img 
src="1_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-04-12.png" alt="PIC"  
><img 
src="2_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-04-57.png" alt="PIC"  
>
<!--l. 45--><p class="indent" >   <img 
src="3_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-05-51.png" alt="PIC"  
><img 
src="4_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-07-13.png" alt="PIC"  
>
<!--l. 47--><p class="indent" >   As shown in the illustrations above, the 30-degree angled rails use a knight&#8217;s move (2 ahead, 1 aside) for placement. For the rails to
look realistic, I encourage you not to build turns that are too narrow. IMO the angles you can build with this are still way to narrow, but
this is the best compromise I can find.
<!--l. 53--><p class="indent" >
<!--l. 53--><p class="noindent" ><span 
class="ecsx-1200">Switches</span>
<!--l. 55--><p class="indent" >   To create switches we need the trackworker tool. ATM it looks like a Doctor Who Sonic Screwdriver. Aside from turning rails into
switches, it is also capable of rotating everything (rails, bumpers, signals) in this mod. Due to internal mechanics, nothing can be rotated
using the default screwdriver.
<!--l. 61--><p class="indent" >   <img 
src="5_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-56-34.png" alt="PIC"  
>
<!--l. 63--><p class="indent" >   Place some rails. Then left-click 1-2 times on one of these rails, until you see a switch. Use right-click to rotate it how you need it. You
can change the switch direction by right-clicking the switch or by powering it with mesecons.
<!--l. 68--><p class="indent" >   Unfortunately tracks that are placed next to switches don&#8217;t always automatically connect to them. You need to correct manually
using the Trackworker. One day I will implement proper handling for these. When you are finished it could look like
this:
<!--l. 73--><p class="indent" >   <img 
src="6_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-08-02.png" alt="PIC"  
>
<!--l. 75--><p class="indent" >
<!--l. 75--><p class="noindent" ><span 
class="ecsx-1200">Rail</span>
<span 
class="ecsx-1200">crosses</span>
<!--l. 77--><p class="indent" >   There are no real cross-rail nodes. However you can create crossing rails by being creative and using the knight&#8217;s move or by placing
opposing 45-degree rails.
<!--l. 81--><p class="indent" >   <img 
src="7_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-09-01.png" alt="PIC"  
><img 
src="8_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-10-15.png" alt="PIC"  
>
<!--l. 83--><p class="indent" >
<!--l. 83--><p class="noindent" ><span 
class="ecsx-1200">Height</span>
<span 
class="ecsx-1200">differences</span>
<!--l. 85--><p class="indent" >   To master height differences you can craft slope nodes:
<!--l. 87--><p class="indent" >   <img 
src="9_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-45-38.png" alt="PIC"  
>
<!--l. 89--><p class="indent" >   To place them, you have to prepare the base, then stand in the right direction and point to the slope start point, then place it. A slope
will be constructed in the direction you are facing (45 degree steps) leaned against the next solid node. The right number of slopes is
subtracted from the item stack if you are in survival.
<!--l. 95--><p class="indent" >
<!--l. 95--><p class="noindent" ><span 
class="ecsx-1200">Bumpers,</span>
<span 
class="ecsx-1200">platforms,</span>
<span 
class="ecsx-1200">signals</span>
<span 
class="ecsx-1200">and</span>
<span 
class="ecsx-1200">detector</span>
<span 
class="ecsx-1200">rails</span>
<!--l. 97--><p class="indent" >   <img 
src="10_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-48-54.png" alt="PIC"  
>
<!--l. 99--><p class="indent" >   Bumpers are objects that are usually placed at the end of a track to prevent trains rolling off it. After placed, they can be rotated
using the Trackworker.
<!--l. 103--><p class="indent" >   <img 
src="11_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-50-27.png" alt="PIC"  
><img 
src="12_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-51-02.png" alt="PIC"  
>
<!--l. 105--><p class="indent" >   These are a regular analog signal and an electric signal. Like everything, you can rotate them using the Trackworker.
Right-click or power with mesecons to signal trains that they can pass or have to stop. The signals do not have any effect on
trains, they can only signal the driver. A more advanced signalling system (with distant signals/signal combinations) is
planned.
<!--l. 112--><p class="indent" >   <img 
src="13_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-58-39.png" alt="PIC"  
><img 
src="14_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-58-20.png" alt="PIC"  
>
<!--l. 114--><p class="indent" >   These are some platform nodes. I suggest using the left one, it&#8217;s only half height and looks better. These nodes also have a sandstone
variant, craft with sandstone bricks
<!--l. 118--><p class="indent" >   <img 
src="15_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2017-03-09_11-33-09.png" alt="PIC"  
>
<!--l. 120--><p class="indent" >   These detector rails turn adjacent mesecons on when a train is standing/driving over them.
<!--l. 123--><p class="indent" >   Notice: Detector rails and bumpers currently aren&#8217;t aligned to the regular tracks. This will be fixed soon. Meanwhile, you need to
rotate them manually.
<!--l. 127--><p class="indent" >
<!--l. 127--><p class="noindent" ><span 
class="ecsx-1200">Trains</span>
<!--l. 129--><p class="indent" >   There are some wagons included in this modpack, however community members (namely mbb and Andrey) have made some more wagons
that can be downloaded and enabled separately. Visit the forum topic (<a 
href="https://forum.minetest.net/viewtopic.php?f=11&t=14726" class="url" ><span 
class="ectt-1000">https://forum.minetest.net/viewtopic.php?f=11&amp;t=14726</span></a>)
to download them.
<!--l. 134--><p class="indent" >   To see what&#8217;s included, look up in a craft guide or consult the creative mode inventory.
<!--l. 137--><p class="indent" >   To place wagons simply craft and click a track. To remove a wagon, punch it. Only the person who placed the wagon can do this. In
survival if you destroy trains you get only some of your steel back, so you will be asked to confirm if you really want to destroy a
wagon.
<!--l. 142--><p class="indent" >
<!--l. 142--><p class="noindent" ><span 
class="ecsx-1200">Driving</span>
<span 
class="ecsx-1200">trains</span>
<!--l. 144--><p class="indent" >   Right-click any wagon to get on. This will attach you to the wagon and register you as passenger. Depending on how the wagon is set
up, you are either in a passenger seat or inside a driver stand. Right-clicking again will show your possibilities on what you can do in/with
the wagon.
<!--l. 150--><p class="indent" >   Example:
<!--l. 152--><p class="indent" >   <img 
src="16_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2017-03-09_11-42-49.png" alt="PIC"  
>
<!--l. 154--><p class="indent" >   When entering a subway wagon, you are formally inside the passenger area. You can see this by the fact that there&#8217;s no head-up
display. Right-clicking brings up this form.
<!--l. 158--><p class="indent" >   The first button will make you move to the Driver stand, so you can drive the train.
<!--l. 161--><p class="indent" >   The second button should say &#8220;Wagon properties&#8221; and appears only for the wagon owner. See &#8220;Wagon Properties&#8221;.
<!--l. 164--><p class="indent" >   The last button tells that the doors are closed, so you can&#8217;t get off at this time. If the doors are open or the wagon has no doors, this
button says &#8220;Get off&#8221;.
<!--l. 168--><p class="indent" >   It is always possible to bypass closed doors and get off by holding the Sneak key and right-clicking the wagon or by
holding Sneak and Use at the same time. Remember that this may result in your death when the train is travelling
fast.
<!--l. 173--><p class="indent" >   The Japanese train and the Subway train support automatic getting on by just walking into the wagon. As soon as you stand on a
platform and walk towards a door, you will automatically get on the wagon. On these, pressing W or S while inside the Passenger Area
will also make you get off.
<!--l. 179--><p class="indent" >
<!--l. 179--><p class="noindent" ><span 
class="ecsx-1200">Train</span>
<span 
class="ecsx-1200">controls</span>
<!--l. 181--><p class="indent" >   If you are inside a driver stand you are presented with a head-up display:
<!--l. 184--><p class="indent" >   The upper bar shows your current speed and the lower bar shows what speed you ordered the train to hold. Assuming you have the
default controls (WASD, Shift for sneak, Space for jump), the following key bindings apply:
     <ul class="itemize1">
     <li class="itemize">W - faster
     </li>
     <li class="itemize">S - slower / change direction
     </li>
     <li class="itemize">A / D &#8211; open/close doors
     </li>
     <li class="itemize">Space: brake (shown by =B=, target speed will be decreased automatically)
     </li>
     <li class="itemize">Sneak+S: set speed to 0 (train rolls out, brake to stop!)
     </li>
     <li class="itemize">Sneak+W: Set full speed
     </li>
     <li class="itemize">Sneak+A: Set speed to 4 (~40km/h)
     </li>
     <li class="itemize">Sneak+D: Set speed to 8 (~100km/h)
     </li>
     <li class="itemize">Sneak+Space: toggle brake (the brake will not release when releasing the keys, shown by =^B=)</li></ul>
<!--l. 201--><p class="indent" >
<!--l. 201--><p class="noindent" ><span 
class="ecsx-1200">Coupling</span>
<span 
class="ecsx-1200">wagons</span>
<!--l. 203--><p class="indent" >   You just learned how to drive an engine. Now place a wagon anywhere and drive your engine slowly towards that wagon. As soon as
they collided your engine will stop. Now get off and right-click the green icon that appeared between the engine and the train. You have
coupled the wagon to the engine.
<!--l. 209--><p class="indent" >   <img 
src="17_home_moritz_Home_Projekte_Minetest_minetest____ssets_manual_img_screenshot_20161203_231622.png" alt="PIC"  
>
<!--l. 211--><p class="indent" >   To discouple a wagon, punch the red icon between the wagons you want to discouple while the train is standing.
<!--l. 214--><p class="indent" >
<!--l. 214--><p class="noindent" ><span 
class="ecsx-1200">Automatic</span>
<span 
class="ecsx-1200">Train</span>
<span 
class="ecsx-1200">Control</span>
<span 
class="ecsx-1200">(ATC)</span>
<!--l. 216--><p class="indent" >   ATC rails allow you to automate train operation. There are two types of ATC rails:
<!--l. 219--><p class="indent" >
<!--l. 219--><p class="noindent" ><span 
class="ecsx-1000">Regular</span>
<span 
class="ecsx-1000">ATC</span>
<!--l. 221--><p class="indent" >   The ATC rail does not have a crafting recipe. When placed, you can set a command and it will be sent to any train driving over the
controller.
<!--l. 224--><p class="indent" >   Only the static mode is implemented, changing the mode has no effect.
<!--l. 226--><p class="indent" >   For a detailed explanation how ATC commands work and their syntax see atc_command.txt
<!--l. 229--><p class="indent" >   Note: to rotate ATC rails, you need to bypass the formspec that is set for the node. To do this, hold Sneak when right-clicking the rail
with the trackworker tool.
<!--l. 233--><p class="indent" >
<!--l. 233--><p class="noindent" ><span 
class="ecsx-1000">LUA</span>
<span 
class="ecsx-1000">ATC</span>
<!--l. 235--><p class="indent" >   The LUA ATC suite is part of the mod advtrains_luaautomation. The LUA ATC components are quite similar to Mesecons
Luacontrollers and allow to create all kinds of automation systems. This tool is not intended for beginners or regular players, but for
server admins who wish to create a heavily automated subway system.
<!--l. 241--><p class="indent" >   More information on those can be found inside the mod directory of advtrains_luaautomation.  
</body></html> 



on consume has_inventory=false, static_save=false, } function wagon:train() local data = advtrains.wagons[self.id] return advtrains.trains[data.train_id] end function wagon:on_activate(sd_uid, dtime_s) if sd_uid~="" then --destroy when loaded from static block. self.object:remove() return end self.object:set_armor_groups({immortal=1}) end local function invcallback(id, pname, rtallow, rtfail) local data = advtrains.wagons[id] if data and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then return rtallow end return rtfail end function wagon:set_id(wid) self.id = wid self.initialized = true local data = advtrains.wagons[self.id] advtrains.wagon_objects[self.id] = self.object --atdebug("Created wagon entity:",self.name," w_id",wid," t_id",data.train_id) if self.has_inventory then --to be used later local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.id}) -- create inventory, if not yet created if not inv then inv=minetest.create_detached_inventory("advtrains_wgn_"..self.id, { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) return invcallback(wid, player:get_player_name(), count, 0) end, allow_put = function(inv, listname, index, stack, player) return invcallback(wid, player:get_player_name(), stack:get_count(), 0) end, allow_take = function(inv, listname, index, stack, player) return invcallback(wid, player:get_player_name(), stack:get_count(), 0) end }) if data.ser_inv then advtrains.deserialize_inventory(data.ser_inv, inv) end if self.inventory_list_sizes then for lst, siz in pairs(self.inventory_list_sizes) do inv:set_size(lst, siz) end end end end self.door_anim_timer=0 self.door_state=0 minetest.after(0.2, function() self:reattach_all() end) if self.custom_on_activate then self:custom_on_activate() end end function wagon:get_staticdata() return "STATIC" end function wagon:ensure_init() -- Note: A wagon entity won't exist when there's no train, because the train is -- the thing that actually creates the entity -- Train not being set just means that this will happen as soon as the train calls update_trainpart_properties. if self.initialized and self.id then local data = advtrains.wagons[self.id] if data and data.train_id and self:train() then if self.noninitticks then self.noninitticks=nil end return true end end if not self.noninitticks then atwarn("wagon",self.id,"uninitialized init=",self.initialized) self.noninitticks=0 end self.noninitticks=self.noninitticks+1 if self.noninitticks>20 then atwarn("wagon",self.id,"uninitialized, removing") self:destroy() else self.object:setvelocity({x=0,y=0,z=0}) end return false end function wagon:train() local data = advtrains.wagons[self.id] return advtrains.trains[data.train_id] end -- Remove the wagon function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction) return advtrains.pcall(function() if not self:ensure_init() then return end local data = 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: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 self.custom_may_destroy then if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then return end 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) 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:on_step(dtime) return advtrains.pcall(function() if not self:ensure_init() then return end local t=os.clock() local pos = self.object:getpos() 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() --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) if self.seat_groups then has_driverstand = has_driverstand and (seat.driving_ctrl_access or self.seat_groups[seat.group].driving_ctrl_access) else has_driverstand = has_driverstand and (seat.driving_ctrl_access) end 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:setvelocity({x=0, y=0, z=0}) self.object:setacceleration({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 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] 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) -- 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 oadd = { x = (ix2.z-ix1.z)*train.door_open*2, y = 1, z = (ix1.x-ix2.x)*train.door_open*2} 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:setpos(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:setpos(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,1;whitelist;Allow these players to drive your wagon:;"..(data.whitelist or "").."]" --seat groups access lists were here form=form.."button_exit[0.5,3;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 function wagon:show_bordcom(pname) if not self:train() then return end local train = self:train() local data = advtrains.wagons[self.id] 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")..";"..(train.text_outside or "").."]" form=form.."textarea[0.5,3;7,1;text_inside;"..attrans("Text displayed inside train")..";"..(train.text_inside or "").."]" form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(train.line or "").."]" form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(train.routingcode or "").."]" --row 5 : train overview and autocoupling if train.velocity==0 then form=form.."label[0.5,4.5;Train overview /coupling control:]" linhei=5 local pre_own, pre_wl, owns_any = nil, nil, minetest.check_player_privs(pname, "train_admin") for i, tpid in ipairs(train.trainparts) do local ent = advtrains.wagons[tpid] if ent then local ename = ent.type form = form .. "item_image["..i..","..linhei..";1,1;"..ename.."]" if i~=1 then if checklock(pname, ent.owner, pre_own, ent.whitelist, pre_wl) then form = form .. "image_button["..(i-0.5)..","..(linhei+1)..";1,1;advtrains_discouple.png;dcpl_"..i..";]" end end if i == data.pos_in_trainparts then form = form .. "box["..(i-0.1)..","..(linhei-0.1)..";1,1;green]" end pre_own = ent.owner pre_wl = ent.whitelist owns_any = owns_any or (not ent.owner or ent.owner==pname) end end if train.movedir==1 then form = form .. "label["..(#train.trainparts+1)..","..(linhei)..";-->]" else form = form .. "label[0.5,"..(linhei)..";<--]" end --check cpl_eid_front and _back of train local couple_front = checkcouple(train.cpl_front) local couple_back = checkcouple(train.cpl_back) if couple_front then form = form .. "image_button[0.5,"..(linhei+1)..";1,1;advtrains_couple.png;cpl_f;]" end if couple_back then form = form .. "image_button["..(#train.trainparts+0.5)..","..(linhei+1)..";1,1;advtrains_couple.png;cpl_b;]" end else form=form.."label[0.5,4.5;Train overview / coupling control is only shown when the train stands.]" end form = form .. "button[0.5,8;3,1;save;Save]" -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect -- from inside the train if advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then local i=1 while train.lzb.oncoming[i] do local oci = train.lzb.oncoming[i] if oci.udata and oci.udata.signal_pos then if advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) then form = form .. "button[4.5,8;5,1;ilrs;Remote Routesetting]" break end end i=i+1 end end minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form) end function wagon:handle_bordcom_fields(pname, formname, fields) local data = advtrains.wagons[self.id] local seatno=self:get_seatno(pname) if not seatno or not self.seat_groups[self.seats[seatno].group].driving_ctrl_access or not advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then return end local train = self:train() if not train then return end if fields.text_outside then if fields.text_outside~="" then train.text_outside=fields.text_outside else train.text_outside=nil end end if fields.text_inside then if fields.text_inside~="" then train.text_inside=fields.text_inside else train.text_inside=nil end end if fields.line then if fields.line~="" then if fields.line ~= train.line then train.line=fields.line minetest.after(0, advtrains.invalidate_path, train.id) end else train.line=nil end end if fields.routingcode then if fields.routingcode~="" then if fields.routingcode ~= train.routingcode then train.routingcode=fields.routingcode minetest.after(0, advtrains.invalidate_path, train.id) end else train.routingcode=nil end end for i, tpid in ipairs(train.trainparts) do if fields["dcpl_"..i] then advtrains.safe_decouple_wagon(tpid, pname) end end --check cpl_eid_front and _back of train local couple_front = checkcouple(train.cpl_front) local couple_back = checkcouple(train.cpl_back) if fields.cpl_f and couple_front then couple_front:on_rightclick(pname) end if fields.cpl_b and couple_back then couple_back:on_rightclick(pname) end -- Interlocking functionality: If the interlocking module is loaded, you can set the signal aspect -- from inside the train if fields.ilrs and advtrains.interlocking and train.lzb and #train.lzb.oncoming > 0 then local i=1 while train.lzb.oncoming[i] do local oci = train.lzb.oncoming[i] if oci.udata and oci.udata.signal_pos then local sigd = advtrains.interlocking.db.get_sigd_for_signal(oci.udata.signal_pos) if sigd then advtrains.interlocking.show_signalling_form(sigd, pname) return end end i=i+1 end end if not fields.quit then self:show_bordcom(pname) end end minetest.register_on_player_receive_fields(function(player, formname, fields) return advtrains.pcall(function() local uid=string.match(formname, "^advtrains_geton_(.+)$") if uid then for _,wagon in pairs(minetest.luaentities) do if wagon.is_wagon and wagon.initialized and wagon.id==uid then local data = advtrains.wagons[wagon.id] if fields.inv then if wagon.has_inventory and wagon.get_inventory_formspec then minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name(), make_inv_name(uid))) end elseif fields.seat then local val=minetest.explode_textlist_event(fields.seat) if val and val.type~="INV" and not data.seatp[player:get_player_name()] then --get on wagon:get_on(player, val.index) --will work with the new close_formspec functionality. close exactly this formspec. minetest.show_formspec(player:get_player_name(), formname, "") end end end end end uid=string.match(formname, "^advtrains_seating_(.+)$") if uid then for _,wagon in pairs(minetest.luaentities) do if wagon.is_wagon and wagon.initialized and wagon.id==uid then local pname=player:get_player_name() local no=wagon:get_seatno(pname) if no then if wagon.seat_groups then wagon:seating_from_key_helper(pname, fields, no) end end end end end uid=string.match(formname, "^advtrains_prop_(.+)$") if uid then local pname=player:get_player_name() local data = advtrains.wagons[uid] if pname~=data.owner and not minetest.check_player_privs(pname, {train_admin = true}) then return true end if fields.save or not fields.quit then if fields.whitelist then data.whitelist = fields.whitelist end end end uid=string.match(formname, "^advtrains_bordcom_(.+)$") if uid then for _,wagon in pairs(minetest.luaentities) do if wagon.is_wagon and wagon.initialized and wagon.id==uid then wagon:handle_bordcom_fields(player:get_player_name(), formname, fields) end end end end) end) function wagon:seating_from_key_helper(pname, fields, no) local data = advtrains.wagons[self.id] local sgr=self.seats[no].group for _,access in ipairs(self.seat_groups[sgr].access_to) do if fields["sgr_"..access] and self:check_seat_group_access(pname, access) then for seatid, seatdef in ipairs(self.seats) do if seatdef.group==access and not data.seatp[seatid] then self:get_on(minetest.get_player_by_name(pname), seatid) return end end end end if fields.inv and self.has_inventory and self.get_inventory_formspec then minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..self.id, self:get_inventory_formspec(player:get_player_name(), make_inv_name(self.id))) end if fields.prop and data.owner==pname then self:show_wagon_properties(pname) end if fields.bordcom and self.seat_groups[sgr].driving_ctrl_access and advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist) then self:show_bordcom(pname) end if fields.dcwarn then minetest.chat_send_player(pname, attrans("Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!")) end if fields.off then self:get_off(no) end end function wagon:check_seat_group_access(pname, sgr) local data = advtrains.wagons[self.id] if self.seat_groups[sgr].driving_ctrl_access and not (advtrains.check_driving_couple_protection(pname, data.owner, data.whitelist)) then return false, "Not allowed to access a driver stand!" end if self.seat_groups[sgr].driving_ctrl_access then advtrains.log("Drive", pname, self.object:getpos(), self:train().text_outside) end return true end function wagon:reattach_all() local data = advtrains.wagons[self.id] if not data.seatp then data.seatp={} end for seatno, pname in pairs(data.seatp) do local p=minetest.get_player_by_name(pname) if p then self:get_on(p ,seatno) end end end local function check_twagon_owner(train, b_first, pname) local wtp = b_first and 1 or #train.trainparts local wid = train.trainparts[wtp] local wdata = advtrains.wagons[wid] if wdata then return advtrains.check_driving_couple_protection(pname, wdata.owner, wdata.whitelist) end return false end function advtrains.safe_couple_trains(id1, id2, t1f, t2f, pname, try_run) if not minetest.check_player_privs(pname, "train_operator") then minetest.chat_send_player(pname, "Missing train_operator privilege") return false end local train1=advtrains.trains[id1] local train2=advtrains.trains[id2] if not advtrains.train_ensure_init(id1, train1) or not advtrains.train_ensure_init(id2, train2) then return false end local wck_t1 = check_twagon_owner(train1, t1f, pname) local wck_t2 = check_twagon_owner(train2, t2f, pname) if wck_t1 or wck_t2 then if try_run then return true end if t1f then if t2f then advtrains.invert_train(id1) advtrains.do_connect_trains(id1, id2) else advtrains.do_connect_trains(id2, id1) end else if t2f then advtrains.do_connect_trains(id1, id2) else advtrains.invert_train(id2) advtrains.do_connect_trains(id1, id2) end end return true else minetest.chat_send_player(pname, "You must be authorized for at least one wagon.") return false end end function advtrains.safe_decouple_wagon(w_id, pname, try_run) if not minetest.check_player_privs(pname, "train_operator") then minetest.chat_send_player(pname, "Missing train_operator privilege") return false end local data = advtrains.wagons[w_id] local dpt = data.pos_in_trainparts if not dpt or dpt <= 1 then return false end local train = advtrains.trains[data.train_id] local owid = train.trainparts[dpt-1] local owdata = advtrains.wagons[owid] if not owdata then return end if not checklock(pname, data.owner, owdata.owner, data.whitelist, owdata.whitelist) then minetest.chat_send_player(pname, "Not allowed to do this.") return false end if try_run then return true end advtrains.log("Discouple", pname, train.last_pos, train.text_outside) advtrains.split_train_at_wagon(w_id) return true end function advtrains.get_wagon_prototype(data) local wt = data.type if not wt then -- LEGACY: Field was called "entity_name" in previous versions wt = data.entity_name data.type = data.entity_name data.entity_name = nil end if not wt or not advtrains.wagon_prototypes[wt] then atwarn("Unable to load wagon type",wt,", using placeholder") wt="advtrains:wagon_placeholder" end return wt, advtrains.wagon_prototypes[wt] end function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative) local sysname = sysname_p if not string.match(sysname, ":") then sysname = "advtrains:"..sysname_p end setmetatable(prototype, {__index=wagon}) minetest.register_entity(":"..sysname,prototype) advtrains.wagon_prototypes[sysname] = prototype minetest.register_craftitem(":"..sysname, { description = desc, inventory_image = inv_img, wield_image = inv_img, stack_max = 1, groups = { not_in_creative_inventory = nincreative and 1 or 0}, on_place = function(itemstack, placer, pointed_thing) return advtrains.pcall(function() if not pointed_thing.type == "node" then return end local pname = placer:get_player_name() local node=minetest.get_node_or_nil(pointed_thing.under) if not node then atprint("[advtrains]Ignore at placer position") return itemstack end local nodename=node.name if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then atprint("no track here, not placing.") return itemstack end if not minetest.check_player_privs(placer, {train_operator = true }) then minetest.chat_send_player(pname, "You don't have the train_operator privilege.") return itemstack end if not minetest.check_player_privs(placer, {train_admin = true }) and minetest.is_protected(pointed_thing.under, placer:get_player_name()) then return itemstack end local tconns=advtrains.get_track_connections(node.name, node.param2) local yaw = placer:get_look_horizontal() local plconnid = advtrains.yawToClosestConn(yaw, tconns) local prevpos = advtrains.get_adjacent_rail(pointed_thing.under, tconns, plconnid, prototype.drives_on) if not prevpos then minetest.chat_send_player(pname, "The track you are trying to place the wagon on is not long enough!") return end local wid = advtrains.create_wagon(sysname, pname) local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid}) if not advtrains.is_creative(pname) then itemstack:take_item() end return itemstack end) end, }) end -- Placeholder wagon. Will be spawned whenever a mod is missing advtrains.register_wagon("advtrains:wagon_placeholder", { visual="sprite", textures = {"advtrains_wagon_placeholder.png"}, collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3}, visual_size = {x=0.7, y=0.7}, initial_sprite_basepos = {x=0, y=0}, drives_on = advtrains.all_tracktypes, max_speed = 5, seats = { }, seat_groups = { }, assign_to_seat_group = {}, wagon_span=1, drops={}, }, "Wagon placeholder", "advtrains_wagon_placeholder.png", true)