aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/api_doc.txt192
-rw-r--r--advtrains/atc.lua375
-rw-r--r--advtrains/copytool.lua183
-rw-r--r--advtrains/couple.lua182
-rw-r--r--advtrains/craft_items.lua23
-rw-r--r--advtrains/crafting.lua102
-rw-r--r--advtrains/debugitems.lua53
-rw-r--r--advtrains/debugringbuffer.lua47
-rw-r--r--advtrains/description.txt8
-rw-r--r--advtrains/helpers.lua472
-rw-r--r--advtrains/init.lua737
-rw-r--r--advtrains/locale/advtrains.de.tr72
-rw-r--r--advtrains/locale/advtrains.zh_CN.tr107
-rw-r--r--advtrains/log.lua17
-rw-r--r--advtrains/lzb.lua276
-rw-r--r--advtrains/misc_nodes.lua123
-rw-r--r--advtrains/mod.conf7
-rw-r--r--advtrains/models/advtrains_across.obj537
-rw-r--r--advtrains/models/advtrains_dtrack_bumper_st.b3dbin0 -> 32927 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_bumper_st_30.b3dbin0 -> 48546 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_bumper_st_45.b3dbin0 -> 32926 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_bumper_st_60.b3dbin0 -> 48546 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_cr.b3dbin0 -> 28040 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_cr_30.b3dbin0 -> 28043 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_cr_45.b3dbin0 -> 34483 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_cr_60.b3dbin0 -> 34483 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_st.b3dbin0 -> 10712 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_st_30.b3dbin0 -> 20043 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_st_45.b3dbin0 -> 12235 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_st_60.b3dbin0 -> 20043 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlcr.b3dbin0 -> 36203 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlcr_30.b3dbin0 -> 39958 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlcr_45.b3dbin0 -> 41302 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlcr_60.b3dbin0 -> 46270 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlst.b3dbin0 -> 36203 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlst_30.b3dbin0 -> 39958 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlst_45.b3dbin0 -> 41302 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swlst_60.b3dbin0 -> 46270 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrcr.b3dbin0 -> 36203 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrcr_30.b3dbin0 -> 46270 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrcr_45.b3dbin0 -> 41302 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrcr_60.b3dbin0 -> 39958 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrst.b3dbin0 -> 36203 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrst_30.b3dbin0 -> 46270 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrst_45.b3dbin0 -> 41302 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_swrst_60.b3dbin0 -> 39958 bytes
-rw-r--r--advtrains/models/advtrains_dtrack_vst1.obj348
-rw-r--r--advtrains/models/advtrains_dtrack_vst1_45.obj434
-rw-r--r--advtrains/models/advtrains_dtrack_vst2.obj372
-rw-r--r--advtrains/models/advtrains_dtrack_vst2_45.obj462
-rw-r--r--advtrains/models/advtrains_dtrack_vst31.obj348
-rw-r--r--advtrains/models/advtrains_dtrack_vst32.obj372
-rw-r--r--advtrains/models/advtrains_dtrack_vst33.obj388
-rw-r--r--advtrains/models/advtrains_modernwagon.b3dbin0 -> 172028 bytes
-rw-r--r--advtrains/models/advtrains_platform_diag.b3dbin0 -> 1970 bytes
-rw-r--r--advtrains/models/advtrains_platform_diag_low.b3dbin0 -> 1982 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_off.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_off_30.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_off_45.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_off_60.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_on.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_on_30.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_on_45.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_retrosignal_on_60.b3dbin0 -> 13093 bytes
-rw-r--r--advtrains/models/advtrains_signal.b3dbin0 -> 61544 bytes
-rw-r--r--advtrains/models/advtrains_signal_30.b3dbin0 -> 61544 bytes
-rw-r--r--advtrains/models/advtrains_signal_45.b3dbin0 -> 61544 bytes
-rw-r--r--advtrains/models/advtrains_signal_60.b3dbin0 -> 61544 bytes
-rw-r--r--advtrains/models/advtrains_signal_wall_l.b3dbin0 -> 40514 bytes
-rw-r--r--advtrains/models/advtrains_signal_wall_r.b3dbin0 -> 40514 bytes
-rw-r--r--advtrains/models/advtrains_signal_wall_t.b3dbin0 -> 40514 bytes
-rw-r--r--advtrains/models/advtrains_track_cr.b3dbin0 -> 8023 bytes
-rw-r--r--advtrains/models/advtrains_track_st.b3dbin0 -> 15831 bytes
-rw-r--r--advtrains/models/advtrains_track_st_45.b3dbin0 -> 8935 bytes
-rw-r--r--advtrains/models/trackplane.b3dbin0 -> 262 bytes
-rw-r--r--advtrains/nodedb.lua391
-rw-r--r--advtrains/occupation.lua206
-rw-r--r--advtrains/p_mesecon_iface.lua58
-rw-r--r--advtrains/passive.lua121
-rw-r--r--advtrains/path.lua419
-rw-r--r--advtrains/protection.lua197
-rw-r--r--advtrains/settingtypes.txt58
-rw-r--r--advtrains/signals.lua348
-rw-r--r--advtrains/sounds/advtrains_crossing_bell.oggbin0 -> 47722 bytes
-rwxr-xr-xadvtrains/textures/advtrains_across.pngbin0 -> 302 bytes
-rwxr-xr-xadvtrains/textures/advtrains_across_anim.pngbin0 -> 524 bytes
-rwxr-xr-xadvtrains/textures/advtrains_boiler.pngbin0 -> 413 bytes
-rwxr-xr-xadvtrains/textures/advtrains_chimney.pngbin0 -> 309 bytes
-rw-r--r--advtrains/textures/advtrains_copytool.pngbin0 -> 779 bytes
-rwxr-xr-xadvtrains/textures/advtrains_couple.pngbin0 -> 339 bytes
-rw-r--r--advtrains/textures/advtrains_cpl_lock.pngbin0 -> 209 bytes
-rw-r--r--advtrains/textures/advtrains_cpl_unlock.pngbin0 -> 213 bytes
-rwxr-xr-xadvtrains/textures/advtrains_discouple.pngbin0 -> 293 bytes
-rwxr-xr-xadvtrains/textures/advtrains_driver_cab.pngbin0 -> 352 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_atc_placer.pngbin0 -> 1259 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_bumper_placer.pngbin0 -> 2213 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_detector_placer.pngbin0 -> 1253 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_load_placer.pngbin0 -> 1248 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_placer.pngbin0 -> 1097 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_rail.pngbin0 -> 4582 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared.pngbin0 -> 7141 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared_atc.pngbin0 -> 7215 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared_detector_off.pngbin0 -> 7180 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared_detector_on.pngbin0 -> 7181 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared_load.pngbin0 -> 7339 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_shared_unload.pngbin0 -> 7338 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_slopeplacer.pngbin0 -> 2415 bytes
-rwxr-xr-xadvtrains/textures/advtrains_dtrack_unload_placer.pngbin0 -> 1260 bytes
-rw-r--r--advtrains/textures/advtrains_hud_arrow.pngbin0 -> 91 bytes
-rw-r--r--advtrains/textures/advtrains_hud_atc.pngbin0 -> 528 bytes
-rw-r--r--advtrains/textures/advtrains_hud_bg.pngbin0 -> 67 bytes
-rw-r--r--advtrains/textures/advtrains_hud_lzb.pngbin0 -> 398 bytes
-rw-r--r--advtrains/textures/advtrains_hud_shunt.pngbin0 -> 476 bytes
-rwxr-xr-xadvtrains/textures/advtrains_platform.pngbin0 -> 193 bytes
-rw-r--r--advtrains/textures/advtrains_platform_diag.pngbin0 -> 93 bytes
-rwxr-xr-xadvtrains/textures/advtrains_retrosignal.pngbin0 -> 8496 bytes
-rwxr-xr-xadvtrains/textures/advtrains_retrosignal_inv.pngbin0 -> 2242 bytes
-rwxr-xr-xadvtrains/textures/advtrains_signal_inv.pngbin0 -> 856 bytes
-rwxr-xr-xadvtrains/textures/advtrains_signal_off.pngbin0 -> 5882 bytes
-rwxr-xr-xadvtrains/textures/advtrains_signal_on.pngbin0 -> 5884 bytes
-rwxr-xr-xadvtrains/textures/advtrains_signal_wall_off.pngbin0 -> 3056 bytes
-rwxr-xr-xadvtrains/textures/advtrains_signal_wall_on.pngbin0 -> 3043 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_cr.pngbin0 -> 33370 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_cr_45.pngbin0 -> 33938 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_placer.pngbin0 -> 32349 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_st.pngbin0 -> 20405 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_st_45.pngbin0 -> 39977 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swlcr.pngbin0 -> 33378 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swlcr_45.pngbin0 -> 45772 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swlst.pngbin0 -> 32321 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swlst_45.pngbin0 -> 46408 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swrcr.pngbin0 -> 33670 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swrcr_45.pngbin0 -> 46865 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swrst.pngbin0 -> 32654 bytes
-rwxr-xr-xadvtrains/textures/advtrains_track_swrst_45.pngbin0 -> 47636 bytes
-rwxr-xr-xadvtrains/textures/advtrains_trackworker.pngbin0 -> 328 bytes
-rw-r--r--advtrains/textures/advtrains_wagon_placeholder.pngbin0 -> 723 bytes
-rwxr-xr-xadvtrains/textures/advtrains_wheel.pngbin0 -> 582 bytes
-rwxr-xr-xadvtrains/textures/drwho_screwdriver.pngbin0 -> 328 bytes
-rw-r--r--advtrains/trackdb_legacy.lua27
-rw-r--r--advtrains/trackplacer.lua432
-rw-r--r--advtrains/tracks.lua751
-rw-r--r--advtrains/trainhud.lua332
-rw-r--r--advtrains/trainlogic.lua1389
-rw-r--r--advtrains/wagons.lua1449
145 files changed, 12415 insertions, 0 deletions
diff --git a/advtrains/api_doc.txt b/advtrains/api_doc.txt
new file mode 100644
index 0000000..8ac4986
--- /dev/null
+++ b/advtrains/api_doc.txt
@@ -0,0 +1,192 @@
+Advanced Trains [advtrains] API documentation
+--------
+To use the API, mods must depend on 'advtrains'.
+All boolean values in definition tables default to 'false' and can be omitted.
+### Wagons
+Wagons are registered using the function
+
+advtrains.register_wagon(name, prototype, description, inventory_image)
+- 'name' is the internal name of the wagon. It should follow the mod naming convention, however, this is not enforced.
+ For compatibility reasons, if a mod name is omitted, the wagon will be registered in the advtrains: namespace.
+ Example: A wagon with name="engine_tgv" will be registered as "advtrains:engine_tgv".
+ !IMPORTANT! You must not append a ":" at the start of the name, even if you want to bypass the mod naming convention check. This is because internally the register_wagon function
+ appends a ":" automatically.
+- 'prototype' is the lua entity prototype. The regular definition keys for luaentites apply. Additional required and optional properties see below. DO NOT define 'on_step', 'on_activate', 'on_punch', 'on_rightclick' and 'get_staticdata' since these will be overridden. Use 'custom_*' instead.
+- 'description' is the description of the inventory item that is used to place the wagon.
+- 'inventory_image' is the inventory image of said item.
+
+# Wagon prototype properties
+{
+ ... all regular luaentity properties (mesh, textures, collisionbox a.s.o)...
+ drives_on = {default=true},
+ ^- used to define the tracktypes (see below) that wagon can drive on. The tracktype identifiers are given as keys, similar to privileges)
+ max_speed = 10,
+ ^- optional, default 10: defines the maximum speed this wagon can drive. The maximum speed of a train is determined by the wagon with the lowest max_speed value.
+ seats = {
+ ^- contains zero or more seat definitions. A seat is a place where a player can be attached when getting on a wagon.
+ {
+ name="Left front window",
+ ^- display name of this seat
+ attach_offset={x=0, y=10, z=0},
+ ^- this value is passed to 'set_attach'
+ view_offset={x=0, y=6, z=0},
+ ^- player:set_eye_offset is called with this parameter.
+ group="default"
+ ^- optional. Defines the seat group. See 'seat_groups' below
+ -!- Note: driving_ctrl_access field has moved to seat group definition,
+ -!- but is still partwise supported here. If you don't use seat groups yet,
+ -!- you really should change NOW!
+ },
+ },
+ seat_groups = {
+ ^- If defined, activates advanced seating behavior. See "seating behavior".
+ default = {
+ name = "Seats"
+ ^- name of this seat group, to be shown in get-on menu.
+ access_to = {"foo", "bar"}
+ ^- List of seat groups you can access from this seat using the menu when sitting inside the train.
+ require_doors_open = true
+ ^- Only allow getting on and off if doors are open.
+ driving_ctrl_access=false,
+ ^- If the seat is a driver stand, and players sitting here should get access to the train's driving control.
+ }
+ }
+ assign_to_seat_group = {"default"},
+ ^- optional, like seat_groups. When player right_clicks the wagon, player will be assigned to the first free seat group in the list.
+
+ doors={
+ ^- optional. If defined, defines door animation frames. Opposite door has to be closed during animation period.
+ ^- Remember, advtrains can't handle doors on both sides opened simultaneously.
+ open={
+ [-1]={frames={x=0, y=20}, time=1}, -- open left doors
+ [1]={frames={x=40, y=60}, time=1} -- open right doors
+ sound = <simpleSoundSpec>
+ ^- The sound file of the doors opening. If none is specified, nothing is played.
+ },
+ close={
+ [-1]={frames={x=20, y=40}, time=1}, -- close left doors
+ [1]={frames={x=60, y=80}, time=1} -- close right doors
+ sound = <simpleSoundSpec>
+ ^- The sound file of the doors closing. If none is specified, nothing is played.
+ }
+ },
+ door_entry={ 1.5, -1.5 }
+ ^- optional. If defined, defines the locations of the doors on the model as distance from the object center on the path.
+ ^- Getting on by walking in then takes effect.
+ ^- Positive values mean front, negative ones back. Resulting position is automatically shifted to the right side.
+
+ wagon_span=2,
+ ^- How far this wagon extends from its base position. Is the half of the wagon length.
+ ^- Used to determine in which distance the other wagons have to be positioned. Will require tweaking.
+ extent_h = 1,
+ ^- Determines the collision box extent in x/z direction. Defaults to 1 (=3x3)
+ ^- The actual bounding box size is (extent_h*2)+1, so 0 means 1x1, 1 means 3x3 and 2 means 5x5
+ extent_v = 2,
+ ^- Determines the collision box extent in y direction. Defaults to 2 (=3).
+ ^- The actual bounding box size is extent_v+1, so 0 means 1, 1 means 2, 2 means 3 a.s.o.
+ horn_sound = <simpleSoundSpec>,
+ ^- The sound file of the horn. If none is specified, this wagon can't sound a horn. The specified sound file will be looped.
+
+ drops = {"default:steelblock 3"}
+ ^- List of itemstrings what to drop when the wagon is destroyed
+
+ has_inventory = false
+ ^- If this wagon has an inventory. The inventory is saved with the wagon.
+ ^- the following settings are ignored if not.
+ inventory_list_sizes = {
+ box=8*6,
+ },
+ ^- List of assignments of type list_name=size.
+ ^- For every entry, an inventory list is created with the specified size.
+ get_inventory_formspec = function(self, player_name, inventory_name)
+ return "<a formspec>"
+ end,
+ ^- Function that should return the formspec to be displayed when <player> requests to open the wagon's inventory.
+ ^- advtrains.standard_inventory_formspec can be used for ordinary wagons with inventories to show
+ ^- both the inventory grid and a 'Wagon properties' button.
+ ^- Use "list["..inventory_name..";<list_name>;<X>,<Y>;<W>,<H>;<Start>]" to display a wagon's inventory list.
+
+ custom_on_step = function(self, dtime) end
+ ^- optional: Execute custom code on every step
+ custom_on_activate = function(self, dtime_s) end
+ ^- optional: Execute custom code on activate. Staticdata does not need to be saved and restored since all properties written in 'self' are preserved over unloads.
+ custom_on_velocity_change = function(self, velocity, old_velocity) end
+ ^- optional: Function that is called whenever the train's velocity changes or every 2 seconds. Used to call 'self.object:update_animation()' if needed.
+ ^- for compatibility reasons the name 'update_animation' for this function is still supported.
+
+}
+
+# Notes on wagons
+
+- Every wagon has the field 'id' which assigns each wagon a random id.
+- Properties written in the Lua Entity (self) are discarded when the wagon entity is unloaded. At the moment there is no way to store data inside a wagon persistently.
+- Assuming Z Axis as the axis parallel to the tracks and Y Axis as the one pointing into the sky, wagon models should be dimensioned in a way that:
+ - their origin is centered in X and Z direction
+ - their origin lies 0.5 units above the bottom of the model
+ - the overall extent in X and Y direction is <=3 units
+- wagon_span is then the distance between the model origin and the Z axis extent.
+
+# Seating behavior
+If the advanced seating behavior is active, clicking on a wagon will immediately get you on that wagon depending on the entries in assign_to_seat_group.
+If all seat groups are full, if the doors are closed or if you are not authorized to enter this seat group(e.g. driver stands), will show a warning.
+On a train, right-clicking the wagon will make you get off the train unless:
+- the doors are closed and it requires open doors.
+- you have access to a seat group specified in access_to (you may enter it and it's not full)
+- you are the owner and can access the wagon preferences
+In case there's no possibility, does nothing.
+In case there are multiple possibilities, will show a form.
+
+If you can't enter or leave a train because the doors are closed, holding the Sneak key while right-clicking bypasses the "doors have to be open" enforcement.
+
+### Tracks
+Most modders will be satisfied with the built-in tracks. If cog railways, maglev trains and mine trains are added, it is necessary to understand the definition of tracks. Although the tracks API is there, explaining it would require more effort than me creating the wanted definitions myself. Contact me if you need to register your own rails using my registration functions.
+
+However, it is still possible to register single rails by understanding the node properties of rails.
+minetest.register_node(nodename, {
+ ... usual node definition ...
+ groups = {
+ advtrains_track = 1,
+ advtrains_track_<tracktype>=1
+ ^- these groups tell that the node is a track
+ not_blocking_trains=1,
+ ^- this group tells that the node should not block trains although it's walkable.
+ },
+
+ at_rail_y = 0,
+ ^- Height of this rail node (the y position of a wagon that stands centered on this rail)
+ at_conns = {
+ [1] = { c=0..15, y=0..1 },
+ [2] = { c=0..15, y=0..1 },
+ ( [3] = { c=0..15, y=0..1 }, )
+ ( [4] = { c=0..15, y=0..1 }, )
+ }
+ ^- Connections of this rail. There can be up to 4 connections.
+ 2 connections are a normal rail, 3 connections a turnout (1->2 and 2/3->1) and 4 connections a crossing (1<>2 and 3<>4)
+ c is the direction of the connection (0-16) and y is the height of the connection (rail will only connect when this matches)
+
+ can_dig=function(pos)
+ return not advtrains.get_train_at_pos(pos)
+ end,
+ after_dig_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ after_place_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ ^- the code in these 3 default minetest API functions is required for advtrains to work, however you can add your own code
+
+ advtrains = {
+ on_train_enter=function(pos, train_id, train, index) end
+ ^- called when a train enters the rail
+ on_train_leave=function(pos, train_id, train, index) end
+ ^- called when a train leaves the rail
+
+ -- The following function is only in effect when interlocking is enabled:
+ on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
+ ^- called when a train is approaching this position, called exactly once for every path recalculation (which can happen at any time)
+ ^- This is called so that if the train would start braking now, it would come to halt about(wide approx) 5 nodes before the rail.
+ ^- has_entered: when true, the train is already standing on this node with its front tip, and the enter callback has already been called.
+ Possibly, some actions need not to be taken in this case. Only set if it's the very first node the train is standing on.
+ ^- lzbdata should be ignored and nothing should be assigned to it
+ }
+})
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
new file mode 100644
index 0000000..4f8b937
--- /dev/null
+++ b/advtrains/atc.lua
@@ -0,0 +1,375 @@
+--atc.lua
+--registers and controls the ATC system
+
+local atc={}
+
+local eval_conditional
+
+-- ATC persistence table. advtrains.atc is created by init.lua when it loads the save file.
+atc.controllers = {}
+function atc.load_data(data)
+ local temp = data and data.controllers or {}
+ --transcode atc controller data to node hashes: table access times for numbers are far less than for strings
+ for pts, data in pairs(temp) do
+ if type(pts)=="number" then
+ pts=minetest.pos_to_string(minetest.get_position_from_hash(pts))
+ end
+ atc.controllers[pts] = data
+ end
+end
+function atc.save_data()
+ return {controllers = atc.controllers}
+end
+--contents: {command="...", arrowconn=0-15 where arrow points}
+
+--general
+function atc.train_set_command(train, command, arrow)
+ atc.train_reset_command(train, true)
+ train.atc_delay = 0
+ train.atc_arrow = arrow
+ train.atc_command = command
+end
+
+function atc.send_command(pos, par_tid)
+ local pts=minetest.pos_to_string(pos)
+ if atc.controllers[pts] then
+ --atprint("Called send_command at "..pts)
+ local train_id = par_tid or advtrains.get_train_at_pos(pos)
+ if train_id then
+ if advtrains.trains[train_id] then
+ --atprint("send_command inside if: "..sid(train_id))
+ if atc.controllers[pts].arrowconn then
+ atlog("ATC controller at",pts,": This controller had an arrowconn of", atc.controllers[pts].arrowconn, "set. Since this field is now deprecated, it was removed.")
+ atc.controllers[pts].arrowconn = nil
+ end
+
+ local train = advtrains.trains[train_id]
+ local index = advtrains.path_lookup(train, pos)
+
+ local iconnid = 1
+ if index then
+ iconnid = train.path_cn[index]
+ else
+ atwarn("ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!")
+ end
+
+ local command = atc.controllers[pts].command
+ command = eval_conditional(command, iconnid==1, train.velocity)
+ if not command then command="" end
+ command=string.match(command, "^%s*(.*)$")
+
+ if command == "" then
+ atprint("Sending ATC Command to", train_id, ": Not modifying, conditional evaluated empty.")
+ return true
+ end
+
+ atc.train_set_command(train, command, iconnid==1)
+ atprint("Sending ATC Command to", train_id, ":", command, "iconnid=",iconnid)
+ return true
+
+ else
+ atwarn("ATC rail at", pos, ": Sending command failed: The train",train_id,"does not exist. This seems to be a bug.")
+ end
+ else
+ atwarn("ATC rail at", pos, ": Sending command failed: There's no train at this position. This seems to be a bug.")
+ -- huch
+ --local train = advtrains.trains[train_id_temp_debug]
+ --atlog("Train speed is",train.velocity,", have moved",train.dist_moved_this_step,", lever",train.lever)
+ --advtrains.path_print(train, atlog)
+ -- TODO track again when ATC bugs occur...
+
+ end
+ else
+ atwarn("ATC rail at", pos, ": Sending command failed: Entry for controller not found.")
+ atwarn("ATC rail at", pos, ": Please visit controller and click 'Save'")
+ end
+ return false
+end
+
+-- Resets any ATC commands the train is currently executing, including the target speed (tarvelocity) it is instructed to hold
+-- if keep_tarvel is set, does not clear the tarvelocity
+function atc.train_reset_command(train, keep_tarvel)
+ train.atc_command=nil
+ train.atc_delay=nil
+ train.atc_brake_target=nil
+ train.atc_wait_finish=nil
+ train.atc_arrow=nil
+ if not keep_tarvel then
+ train.tarvelocity=nil
+ end
+end
+
+--nodes
+local idxtrans={static=1, mesecon=2, digiline=3}
+local apn_func=function(pos)
+ -- FIX for long-persisting ndb bug: there's no node in parameter 2 of this function!
+ local meta=minetest.get_meta(pos)
+ if meta then
+ meta:set_string("infotext", attrans("ATC controller, unconfigured."))
+ meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
+ end
+end
+
+advtrains.atc_function = function(def, preset, suffix, rotation)
+ return {
+ after_place_node=apn_func,
+ after_dig_node=function(pos)
+ advtrains.invalidate_all_paths(pos)
+ advtrains.ndb.clear(pos)
+ local pts=minetest.pos_to_string(pos)
+ atc.controllers[pts]=nil
+ end,
+ on_receive_fields = function(pos, formname, fields, player)
+ if advtrains.is_protected(pos, player:get_player_name()) then
+ minetest.record_protection_violation(pos, player:get_player_name())
+ return
+ end
+ local meta=minetest.get_meta(pos)
+ if meta then
+ if not fields.save then
+ --[[--maybe only the dropdown changed
+ if fields.mode then
+ meta:set_string("mode", idxtrans[fields.mode])
+ if fields.mode=="digiline" then
+ meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
+ else
+ meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", fields.mode, meta:get_string("command")) )
+ end
+ meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
+ end]]--
+ return
+ end
+ --meta:set_string("mode", idxtrans[fields.mode])
+ meta:set_string("command", fields.command)
+ --meta:set_string("command_on", fields.command_on)
+ meta:set_string("channel", fields.channel)
+ --if fields.mode=="digiline" then
+ -- meta:set_string("infotext", attrans("ATC controller, mode @1\nChannel: @2", fields.mode, meta:get_string("command")) )
+ --else
+ meta:set_string("infotext", attrans("ATC controller, mode @1\nCommand: @2", "-", meta:get_string("command")) )
+ --end
+ meta:set_string("formspec", atc.get_atc_controller_formspec(pos, meta))
+
+ local pts=minetest.pos_to_string(pos)
+ local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ atc.controllers[pts]={command=fields.command}
+ if #advtrains.occ.get_trains_at(pos) > 0 then
+ atc.send_command(pos)
+ end
+ end
+ end,
+ advtrains = {
+ on_train_enter = function(pos, train_id)
+ atc.send_command(pos, train_id)
+ end,
+ },
+ }
+end
+
+function atc.get_atc_controller_formspec(pos, meta)
+ local mode=tonumber(meta:get_string("mode")) or 1
+ local command=meta:get_string("command")
+ local command_on=meta:get_string("command_on")
+ local channel=meta:get_string("channel")
+ local formspec="size[8,6]"
+ -- "dropdown[0,0;3;mode;static,mesecon,digiline;"..mode.."]"
+ if mode<3 then
+ formspec=formspec.."field[0.5,1.5;7,1;command;"..attrans("Command")..";"..minetest.formspec_escape(command).."]"
+ if tonumber(mode)==2 then
+ formspec=formspec.."field[0.5,3;7,1;command_on;"..attrans("Command (on)")..";"..minetest.formspec_escape(command_on).."]"
+ end
+ else
+ formspec=formspec.."field[0.5,1.5;7,1;channel;"..attrans("Digiline channel")..";"..minetest.formspec_escape(channel).."]"
+ end
+ return formspec.."button_exit[0.5,4.5;7,1;save;"..attrans("Save").."]"
+end
+
+--from trainlogic.lua train step
+local matchptn={
+ ["SM"]=function(id, train)
+ train.tarvelocity=train.max_speed
+ return 2
+ end,
+ ["S([0-9]+)"]=function(id, train, match)
+ train.tarvelocity=tonumber(match)
+ return #match+1
+ end,
+ ["B([0-9]+)"]=function(id, train, match)
+ if train.velocity>tonumber(match) then
+ train.atc_brake_target=tonumber(match)
+ if not train.tarvelocity or train.tarvelocity>train.atc_brake_target then
+ train.tarvelocity=train.atc_brake_target
+ end
+ end
+ return #match+1
+ end,
+ ["BB"]=function(id, train)
+ train.atc_brake_target = -1
+ train.tarvelocity = 0
+ return 2
+ end,
+ ["W"]=function(id, train)
+ train.atc_wait_finish=true
+ return 1
+ end,
+ ["D([0-9]+)"]=function(id, train, match)
+ train.atc_delay=tonumber(match)
+ return #match+1
+ end,
+ ["R"]=function(id, train)
+ if train.velocity<=0 then
+ advtrains.invert_train(id)
+ advtrains.train_ensure_init(id, train)
+ -- no one minds if this failed... this shouldn't even be called without train being initialized...
+ else
+ atwarn(sid(id), attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
+ end
+ return 1
+ end,
+ ["O([LRC])"]=function(id, train, match)
+ local tt={L=-1, R=1, C=0}
+ local arr=train.atc_arrow and 1 or -1
+ train.door_open = tt[match]*arr
+ return 2
+ end,
+ ["K"] = function(id, train)
+ if train.door_open == 0 then
+ atwarn(sid(id), attrans("ATC Kick command warning: Doors closed"))
+ return 1
+ end
+ if train.velocity > 0 then
+ atwarn(sid(id), attrans("ATC Kick command warning: Train moving"))
+ return 1
+ end
+ local tp = train.trainparts
+ for i=1,#tp do
+ local data = advtrains.wagons[tp[i]]
+ local obj = advtrains.wagon_objects[tp[i]]
+ if data and obj then
+ local ent = obj:get_luaentity()
+ if ent then
+ for seatno,seat in pairs(ent.seats) do
+ if data.seatp[seatno] and not ent:is_driver_stand(seat) then
+ ent:get_off(seatno)
+ end
+ end
+ end
+ end
+ end
+ return 1
+ end,
+ ["A([01])"]=function(id, train, match)
+ if not advtrains.interlocking then return 2 end
+ advtrains.interlocking.ars_set_disable(train, match=="0")
+ return 2
+ end,
+}
+
+eval_conditional = function(command, arrow, speed)
+ --conditional statement?
+ local is_cond, cond_applies, compare
+ local cond, rest=string.match(command, "^I([%+%-])(.+)$")
+ if cond then
+ is_cond=true
+ if cond=="+" then
+ cond_applies=arrow
+ end
+ if cond=="-" then
+ cond_applies=not arrow
+ end
+ else
+ cond, compare, rest=string.match(command, "^I([<>]=?)([0-9]+)(.+)$")
+ if cond and compare then
+ is_cond=true
+ if cond=="<" then
+ cond_applies=speed<tonumber(compare)
+ end
+ if cond==">" then
+ cond_applies=speed>tonumber(compare)
+ end
+ if cond=="<=" then
+ cond_applies=speed<=tonumber(compare)
+ end
+ if cond==">=" then
+ cond_applies=speed>=tonumber(compare)
+ end
+ end
+ end
+ if is_cond then
+ atprint("Evaluating if statement: "..command)
+ atprint("Cond: "..(cond or "nil"))
+ atprint("Applies: "..(cond_applies and "true" or "false"))
+ atprint("Rest: "..rest)
+ --find end of conditional statement
+ local nest, pos, elsepos=0, 1
+ while nest>=0 do
+ if pos>#rest then
+ atwarn(sid(id), attrans("ATC command syntax error: I statement not closed: @1",command))
+ return ""
+ end
+ local char=string.sub(rest, pos, pos)
+ if char=="I" then
+ nest=nest+1
+ end
+ if char==";" then
+ nest=nest-1
+ end
+ if nest==0 and char=="E" then
+ elsepos=pos+0
+ end
+ pos=pos+1
+ end
+ if not elsepos then elsepos=pos-1 end
+ if cond_applies then
+ command=string.sub(rest, 1, elsepos-1)..string.sub(rest, pos)
+ else
+ command=string.sub(rest, elsepos+1, pos-2)..string.sub(rest, pos)
+ end
+ atprint("Result: "..command)
+ end
+ return command
+end
+
+function atc.execute_atc_command(id, train)
+ --strip whitespaces
+ local command=string.match(train.atc_command, "^%s*(.*)$")
+
+
+ if string.match(command, "^%s*$") then
+ train.atc_command=nil
+ return
+ end
+
+ train.atc_command = eval_conditional(command, train.atc_arrow, train.velocity)
+
+ if not train.atc_command then return end
+ command=string.match(train.atc_command, "^%s*(.*)$")
+
+ if string.match(command, "^%s*$") then
+ train.atc_command=nil
+ return
+ end
+
+ for pattern, func in pairs(matchptn) do
+ local match=string.match(command, "^"..pattern)
+ if match then
+ local patlen=func(id, train, match)
+
+ atprint("Executing: "..string.sub(command, 1, patlen))
+
+ train.atc_command=string.sub(command, patlen+1)
+ if train.atc_delay<=0 and not train.atc_wait_finish then
+ --continue (recursive, cmds shouldn't get too long, and it's a end-recursion.)
+ atc.execute_atc_command(id, train)
+ end
+ return
+ end
+ end
+ atwarn(sid(id), attrans("ATC command parse error: Unknown command: @1", command))
+ atc.train_reset_command(train, true)
+end
+
+
+
+--move table to desired place
+advtrains.atc=atc
diff --git a/advtrains/copytool.lua b/advtrains/copytool.lua
new file mode 100644
index 0000000..0c1cdfe
--- /dev/null
+++ b/advtrains/copytool.lua
@@ -0,0 +1,183 @@
+--clipboard = {trainlen = number, [n] = {type = string, flipped = bool, }
+
+-- Yaw is in radians. There are 2pi rad in a circle. North is the 0 point and the angle increases anticlockwise.
+-- 4.712389 = 1.5pi; sin(1.5pi) = -1
+-- 7.853981 = 2.5pi; sin(2.5pi) = 1
+
+minetest.register_tool("advtrains:copytool", {
+ description = attrans("Train copy/paste tool\n\nLeft-click: copy train\nRight-click: paste train"),
+ inventory_image = "advtrains_copytool.png",
+ wield_image = "advtrains_copytool.png",
+ stack_max = 1,
+ -- Paste: Take the clipboard and the player yaw, and attempt to place a new train in the world.
+ -- The front of the train is used as the start of the new train and it proceeds backwards from
+ -- the direction of travel.
+ on_place = function(itemstack, placer, pointed_thing)
+ if ((not pointed_thing.type == "node") or (not placer.get_player_name)) 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, {default=true})) 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, {default=true})
+ 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 meta = itemstack:get_meta()
+ if not meta then
+ minetest.chat_send_player(pname, attrans("The clipboard couldn't access the metadata. Paste failed."))
+ return
+ end
+ local clipboard = meta:get_string("clipboard")
+ if (clipboard == "") then
+ minetest.chat_send_player(pname, "The clipboard is empty.");
+ return
+ end
+ clipboard = minetest.deserialize(clipboard)
+ if (clipboard.wagons == nil) then
+ minetest.chat_send_player(pname, "The clipboard is empty.");
+ return
+ end
+
+ local wagons = {}
+ local n = 1
+ for _, wagonProto in pairs(clipboard.wagons) do
+ local wagon = advtrains.create_wagon(wagonProto.type, pname)
+ advtrains.wagons[wagon].wagon_flipped = wagonProto.wagon_flipped
+ wagons[n] = wagon
+ n = n + 1
+ end
+
+ local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, wagons)
+ local train = advtrains.trains[id]
+ train.off_track = train.end_index<train.path_trk_b
+ if (train.off_track) then
+ minetest.chat_send_player(pname, "Back of train would end up off track, cancelling.")
+ advtrains.remove_train(id)
+ return
+ end
+ train.text_outside = clipboard.text_outside
+ train.text_inside = clipboard.text_inside
+ train.routingcode = clipboard.routingcode
+ train.line = clipboard.line
+
+ return
+ end,
+ -- Copy: Take the pointed-at train and put it on the clipboard
+ on_use = function(itemstack, user, pointed_thing)
+ if not user:get_player_name() then return end
+ if (pointed_thing.type ~= "object") then return end
+
+ local le = pointed_thing.ref:get_luaentity()
+ if (le == nil) then
+ minetest.chat_send_player(user:get_player_name(), "No such lua entity!")
+ return
+ end
+
+ local wagon = advtrains.wagons[le.id]
+ if (not (le.id and advtrains.wagons[le.id])) then
+ minetest.chat_send_player(user:get_player_name(), string.format("No such wagon: %s", le.id))
+ return
+ end
+
+ local train = advtrains.trains[wagon.train_id]
+ if (not train) then
+ minetest.chat_send_player(user:get_player_name(), string.format("No such train: %s", wagon.train_id))
+ return
+ end
+
+ -- Record the train length. The paste operation should require this much free space.
+ local clipboard = {
+ trainlen = math.ceil(train.trainlen),
+ text_outside = train.text_outside,
+ text_inside = train.text_inside,
+ routingcode = train.routingcode,
+ line = train.line,
+ wagons = {}
+ }
+ local trainLength = math.ceil(train.trainlen)
+
+ local n = 1
+ for _, wagonid in pairs(train.trainparts) do
+ local wagon = advtrains.wagons[wagonid]
+ clipboard.wagons[n] = {
+ wagon_flipped = wagon.wagon_flipped,
+ type = wagon.type
+ }
+ n = n + 1
+ end
+
+
+ local function flip_clipboard(wagon_clipboard)
+ local flipped = {}
+ local numWagons = #wagon_clipboard
+ for k, v in ipairs(wagon_clipboard) do
+ local otherSide = (numWagons-k)+1
+ flipped[otherSide] = v
+ local wagon = flipped[otherSide]
+ wagon.wagon_flipped = not wagon.wagon_flipped
+ end
+ return flipped
+ end
+
+ local function is_loco(wagon_id)
+ local wagon = advtrains.wagons[wagon_id]
+ if (not wagon) then return false end
+ local wagon_proto = advtrains.wagon_prototypes[wagon.type or wagon.entity_name]
+ if wagon_proto and wagon_proto.is_locomotive then
+ return true
+ end
+ return false
+ end
+
+ -- Decide on a new 'front of train' and possibly flip the train.
+ -- Locomotive on one end = loco-hauled, that end is front;
+ -- if (advtrains.wagons[train.trainparts[1]].is_locomotive) then -- do nothing, train is already in right order
+ local numWagons = #train.trainparts
+ local backLoco = train.trainparts[numWagons]
+ backLoco = is_loco(backLoco)
+ local frontLoco = train.trainparts[1]
+ frontLoco = is_loco(frontLoco)
+ if ((backLoco) and (not frontLoco)) then
+ clipboard.wagons = flip_clipboard(clipboard.wagons)
+ --minetest.chat_send_player(user:get_player_name(), "Flipped train: Loco-hauled")
+ end
+ -- locomotives on both ends = train is push-pull / multi-unit, has no front, do nothing
+ -- no locomotives on ends = rake of wagons, front is end closest to where player copied.
+ if ((not frontLoco) and (not backLoco)) then
+
+ if (wagon.pos_in_trainparts / numWagons > 0.5) then -- towards the end of the rain
+ clipboard.wagons = flip_clipboard(clipboard.wagons)
+ --minetest.chat_send_player(user:get_player_name(), "Flipped train: Rake")
+ end
+ end
+
+ local meta = itemstack:get_meta()
+ if not meta then
+ minetest.chat_send_player(pname, attrans("The clipboard couldn't access the metadata. Copy failed."))
+ return
+ end
+ meta:set_string("clipboard", minetest.serialize(clipboard))
+ minetest.chat_send_player(user:get_player_name(), attrans("Train copied!"))
+ return itemstack
+ end
+})
diff --git a/advtrains/couple.lua b/advtrains/couple.lua
new file mode 100644
index 0000000..a38ca69
--- /dev/null
+++ b/advtrains/couple.lua
@@ -0,0 +1,182 @@
+--couple.lua
+--defines couple entities.
+
+--advtrains:discouple
+--set into existing trains to split them when punched.
+--they are attached to the wagons.
+--[[fields
+wagon
+
+wagons keep their couple entity minetest-internal id inside the field discouple_id. if it refers to nowhere, they will spawn a new one if player is near
+]]
+
+local couple_max_dist=3
+
+minetest.register_entity("advtrains:discouple", {
+ visual="sprite",
+ textures = {"advtrains_discouple.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},
+
+ is_discouple=true,
+ static_save = false,
+ on_activate=function(self, staticdata)
+ if staticdata=="DISCOUPLE" then
+ --couple entities have no right to exist further...
+ atprint("Discouple loaded from staticdata, destroying")
+ self.object:remove()
+ return
+ end
+ self.object:set_armor_groups({immortal=1})
+ end,
+ get_staticdata=function() return "DISCOUPLE" end,
+ on_punch=function(self, player)
+ local pname = player:get_player_name()
+ if pname and pname~="" and self.wagon then
+ if advtrains.safe_decouple_wagon(self.wagon.id, pname) then
+ self.object:remove()
+ else
+ minetest.add_entity(self.object:getpos(), "advtrains:lockmarker")
+ end
+ end
+ end,
+ on_step=function(self, dtime)
+ if not self.wagon then
+ self.object:remove()
+ return
+ end
+ --getyaw seems to be a reliable method to check if an object is loaded...if it returns nil, it is not.
+ if not self.wagon.object:get_yaw() then
+ self.object:remove()
+ return
+ end
+ if not self.wagon:train() or self.wagon:train().velocity > 0 then
+ self.object:remove()
+ return
+ end
+ end,
+})
+
+-- advtrains:couple
+-- Couple entity
+local function lockmarker(obj)
+ minetest.add_entity(obj:get_pos(), "advtrains:lockmarker")
+ obj:remove()
+end
+
+minetest.register_entity("advtrains:couple", {
+ visual="sprite",
+ textures = {"advtrains_couple.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},
+
+ is_couple=true,
+ static_save = false,
+ on_activate=function(self, staticdata)
+ if staticdata=="COUPLE" then
+ --couple entities have no right to exist further...
+ atprint("Couple loaded from staticdata, destroying")
+ self.object:remove()
+ return
+ end
+ self.object:set_armor_groups({immmortal=1})
+ end,
+ get_staticdata=function(self) return "COUPLE" end,
+ on_rightclick=function(self, clicker)
+ if not self.train_id_1 or not self.train_id_2 then return end
+
+ local pname=clicker
+ if type(clicker)~="string" then pname=clicker:get_player_name() end
+
+ if advtrains.safe_couple_trains(self.train_id_1, self.train_id_2, self.t1_is_front, self.t2_is_front, pname) then
+ self.object:remove()
+ else
+ lockmarker(self.object)
+ end
+ end,
+ on_step=function(self, dtime)
+ if advtrains.wagon_outside_range(self.object:getpos()) then
+ self.object:remove()
+ return
+ end
+
+ if not self.train_id_1 or not self.train_id_2 then atprint("Couple: train ids not set!") self.object:remove() return end
+ local train1=advtrains.trains[self.train_id_1]
+ local train2=advtrains.trains[self.train_id_2]
+ if not train1 or not train2 then
+ atprint("Couple: trains missing, destroying")
+ self.object:remove()
+ return
+ end
+
+ --shh, silence here, this is an on-step callback!
+ if not advtrains.train_ensure_init(self.train_id_1, train1) then
+ --atwarn("Train",self.train_id_1,"is not initialized! Operation aborted!")
+ return
+ end
+ if not advtrains.train_ensure_init(self.train_id_2, train2) then
+ --atwarn("Train",self.train_id_2,"is not initialized! Operation aborted!")
+ return
+ end
+
+ if train1.velocity>0 or train2.velocity>0 then
+ if not self.position_set then --ensures that train stands a single time before check fires. Using flag below
+ return
+ end
+ atprint("Couple: train is moving, destroying")
+ self.object:remove()
+ return
+ end
+
+ if not self.position_set then
+ local tp1
+ if self.t1_is_front then
+ tp1=advtrains.path_get_interpolated(train1, train1.index)
+ else
+ tp1=advtrains.path_get_interpolated(train1, train1.end_index)
+ end
+ local tp2
+ if self.t2_is_front then
+ tp2=advtrains.path_get_interpolated(train2, train2.index)
+ else
+ tp2=advtrains.path_get_interpolated(train2, train2.end_index)
+ end
+ local pos_median=advtrains.pos_median(tp1, tp2)
+ if not vector.equals(pos_median, self.object:getpos()) then
+ self.object:set_pos(pos_median)
+ end
+ self.position_set=true
+ end
+ atprintbm("couple step", t)
+ advtrains.atprint_context_tid=nil
+ end,
+})
+minetest.register_entity("advtrains:lockmarker", {
+ visual="sprite",
+ textures = {"advtrains_cpl_lock.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},
+
+ is_lockmarker=true,
+ static_save = false,
+ on_activate=function(self, staticdata)
+ if staticdata=="COUPLE" then
+ --couple entities have no right to exist further...
+ atprint("Couple loaded from staticdata, destroying")
+ self.object:remove()
+ return
+ end
+ self.object:set_armor_groups({immmortal=1})
+ self.life=5
+ end,
+ get_staticdata=function(self) return "COUPLE" end,
+ on_step=function(self, dtime)
+ self.life=(self.life or 5)-dtime
+ if self.life<0 then
+ self.object:remove()
+ end
+ end,
+})
diff --git a/advtrains/craft_items.lua b/advtrains/craft_items.lua
new file mode 100644
index 0000000..0e693eb
--- /dev/null
+++ b/advtrains/craft_items.lua
@@ -0,0 +1,23 @@
+
+core.register_craftitem("advtrains:boiler", {
+ description = attrans("Boiler"),
+ inventory_image = "advtrains_boiler.png",
+})
+
+
+core.register_craftitem("advtrains:driver_cab", {
+ description = attrans("driver's cab"),
+ inventory_image = "advtrains_driver_cab.png",
+})
+
+
+core.register_craftitem("advtrains:wheel", {
+ description = attrans("Wheel"),
+ inventory_image = "advtrains_wheel.png",
+})
+
+
+core.register_craftitem("advtrains:chimney", {
+ description = attrans("Chimney"),
+ inventory_image = "advtrains_chimney.png",
+})
diff --git a/advtrains/crafting.lua b/advtrains/crafting.lua
new file mode 100644
index 0000000..7626d55
--- /dev/null
+++ b/advtrains/crafting.lua
@@ -0,0 +1,102 @@
+--advtrains by orwell96, see readme.txt and license.txt
+--crafting.lua
+--registers crafting recipes
+
+--tracks: see advtrains_train_track
+--signals
+minetest.register_craft({
+ output = 'advtrains:retrosignal_off 2',
+ recipe = {
+ {'dye:red', 'default:steel_ingot', 'default:steel_ingot'},
+ {'', '', 'default:steel_ingot'},
+ {'', '', 'default:steel_ingot'},
+ },
+})
+minetest.register_craft({
+ output = 'advtrains:signal_off 2',
+ recipe = {
+ {'', 'dye:red', 'default:steel_ingot'},
+ {'', 'dye:dark_green', 'default:steel_ingot'},
+ {'', '', 'default:steel_ingot'},
+ },
+})
+--Wallmounted Signal
+minetest.register_craft({
+ output = 'advtrains:signal_wall_r_off 2',
+ recipe = {
+ {'dye:red', 'default:steel_ingot', 'default:steel_ingot'},
+ {'', 'default:steel_ingot', ''},
+ {'dye:dark_green', 'default:steel_ingot', 'default:steel_ingot'},
+ },
+})
+
+--Wallmounted Signals can be converted into every orientation by shapeless crafting
+minetest.register_craft({
+ output = 'advtrains:signal_wall_l_off',
+ type = "shapeless",
+ recipe = {'advtrains:signal_wall_r_off'},
+})
+minetest.register_craft({
+ output = 'advtrains:signal_wall_t_off',
+ type = "shapeless",
+ recipe = {'advtrains:signal_wall_l_off'},
+})
+minetest.register_craft({
+ output = 'advtrains:signal_wall_r_off',
+ type = "shapeless",
+ recipe = {'advtrains:signal_wall_t_off'},
+})
+
+--trackworker
+minetest.register_craft({
+ output = 'advtrains:trackworker',
+ recipe = {
+ {'default:diamond'},
+ {'screwdriver:screwdriver'},
+ {'default:steel_ingot'},
+ },
+})
+
+--boiler
+minetest.register_craft({
+ output = 'advtrains:boiler',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'doors:trapdoor_steel', '', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ },
+})
+
+--drivers'cab
+minetest.register_craft({
+ output = 'advtrains:driver_cab',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'', '', 'default:glass'},
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ },
+})
+
+--drivers'cab
+minetest.register_craft({
+ output = 'advtrains:wheel',
+ recipe = {
+ {'', 'default:steel_ingot', ''},
+ {'default:steel_ingot', 'group:stick', 'default:steel_ingot'},
+ {'', 'default:steel_ingot', ''},
+ },
+})
+
+--chimney
+minetest.register_craft({
+ output = 'advtrains:chimney',
+ recipe = {
+ {'', 'default:steel_ingot', ''},
+ {'', 'default:steel_ingot', 'default:torch'},
+ {'', 'default:steel_ingot', ''},
+ },
+})
+
+
+--misc_nodes
+--crafts for platforms see misc_nodes.lua
diff --git a/advtrains/debugitems.lua b/advtrains/debugitems.lua
new file mode 100644
index 0000000..e672308
--- /dev/null
+++ b/advtrains/debugitems.lua
@@ -0,0 +1,53 @@
+minetest.register_tool("advtrains:tunnelborer",
+{
+ description = "tunnelborer",
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "drwho_screwdriver.png",
+ wield_image = "drwho_screwdriver.png",
+ stack_max = 1,
+ range = 7.0,
+
+ on_place = function(itemstack, placer, pointed_thing)
+
+ end,
+ --[[
+ ^ Shall place item and return the leftover itemstack
+ ^ default: minetest.item_place ]]
+ on_use = function(itemstack, user, pointed_thing)
+ if pointed_thing.type=="node" then
+ for x=-1,1 do
+ for y=-1,1 do
+ for z=-1,1 do
+ minetest.remove_node(vector.add(pointed_thing.under, {x=x, y=y, z=z}))
+ end
+ end
+ end
+ end
+ end,
+}
+)
+
+minetest.register_chatcommand("atyaw",
+ {
+ params = "angledeg conn1 conn2",
+ description = "",
+ func = function(name, param)
+ local angledegs, conn1s, conn2s = string.match(param, "^(%S+)%s(%S+)%s(%S+)$")
+ if angledegs and conn1s and conn2s then
+ local angledeg, conn1, conn2 = angledegs+0,conn1s+0,conn2s+0
+ local yaw = angledeg*math.pi/180
+ local yaw1 = advtrains.dir_to_angle(conn1)
+ local yaw2 = advtrains.dir_to_angle(conn2)
+ local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
+ local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
+
+ atdebug("yaw1",atfloor(yaw1*180/math.pi))
+ atdebug("yaw2",atfloor(yaw2*180/math.pi))
+ atdebug("dif1",atfloor(adiff1*180/math.pi))
+ atdebug("dif2",atfloor(adiff2*180/math.pi))
+
+ minetest.chat_send_all(advtrains.yawToAnyDir(yaw))
+ return true, advtrains.yawToDirection(yaw, conn1, conn2)
+ end
+ end,
+})
diff --git a/advtrains/debugringbuffer.lua b/advtrains/debugringbuffer.lua
new file mode 100644
index 0000000..bdb4a3a
--- /dev/null
+++ b/advtrains/debugringbuffer.lua
@@ -0,0 +1,47 @@
+--so, some ringbuffers one for each train
+
+local ringbuflen=1000
+
+local ringbufs={}
+local ringbufcnt={}
+
+function advtrains.drb_record(tid, msg)
+ if not ringbufs[tid] then
+ ringbufs[tid]={}
+ ringbufcnt[tid]=0
+ end
+ ringbufs[tid][ringbufcnt[tid]]=msg
+ ringbufcnt[tid]=ringbufcnt[tid]+1
+ if ringbufcnt[tid] > ringbuflen then
+ ringbufcnt[tid]=0
+ end
+end
+function advtrains.drb_dump(tid)
+ atdebug("Debug ring buffer output for '"..tid.."':")
+ local stopcnt=ringbufcnt[tid]
+ if not stopcnt then
+ atdebug("ID unknown!")
+ return
+ end
+ repeat
+ local t = ringbufs[tid][ringbufcnt[tid]]
+ if t then
+ atdebug(t)
+ end
+ ringbufcnt[tid]=ringbufcnt[tid]+1
+ if ringbufcnt[tid] > ringbuflen then
+ ringbufcnt[tid]=0
+ end
+ until ringbufcnt[tid]==stopcnt
+end
+
+minetest.register_chatcommand("atdebug_show",
+ {
+ params = "train sid", -- Short parameter description
+ description = "Dump debug log", -- Full description
+ privs = {train_operator=true}, -- Require the "privs" privilege to run
+ func = function(name, param)
+ advtrains.drb_dump(param)
+ end, -- Called when command is run.
+ -- Returns boolean success and text output.
+ })
diff --git a/advtrains/description.txt b/advtrains/description.txt
new file mode 100644
index 0000000..bb13b97
--- /dev/null
+++ b/advtrains/description.txt
@@ -0,0 +1,8 @@
+Advanced Trains v1.3.8, by orwell and contributors. Also see readme.
+Good-looking, realistic trains for minetest.
+
+For crafting recipes, see manual.pdf
+
+Website: http://advtrains.bleipb.de/
+Manual: https://github.com/orwell96/advtrains/blob/master/manual.pdf
+Forum : https://forum.minetest.net/viewtopic.php?f=11&t=14726
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
new file mode 100644
index 0000000..cf890ca
--- /dev/null
+++ b/advtrains/helpers.lua
@@ -0,0 +1,472 @@
+--advtrains by orwell96, see readme.txt
+
+local dir_trans_tbl={
+ [0]={x=0, z=1, y=0},
+ [1]={x=1, z=2, y=0},
+ [2]={x=1, z=1, y=0},
+ [3]={x=2, z=1, y=0},
+ [4]={x=1, z=0, y=0},
+ [5]={x=2, z=-1, y=0},
+ [6]={x=1, z=-1, y=0},
+ [7]={x=1, z=-2, y=0},
+ [8]={x=0, z=-1, y=0},
+ [9]={x=-1, z=-2, y=0},
+ [10]={x=-1, z=-1, y=0},
+ [11]={x=-2, z=-1, y=0},
+ [12]={x=-1, z=0, y=0},
+ [13]={x=-2, z=1, y=0},
+ [14]={x=-1, z=1, y=0},
+ [15]={x=-1, z=2, y=0},
+}
+
+local dir_angle_tbl={}
+for d,v in pairs(dir_trans_tbl) do
+ local uvec = vector.normalize(v)
+ dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
+end
+
+
+function advtrains.dir_to_angle(dir)
+ return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
+end
+
+function advtrains.dirCoordSet(coord, dir)
+ return vector.add(coord, advtrains.dirToCoord(dir))
+end
+advtrains.pos_add_dir = advtrains.dirCoordSet
+
+function advtrains.pos_add_angle(pos, ang)
+ -- 0 is +Z -> meaning of sin/cos swapped
+ return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
+end
+
+function advtrains.dirToCoord(dir)
+ return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
+end
+advtrains.dir_to_vector = advtrains.dirToCoord
+
+function advtrains.maxN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n+1
+ end
+ return n-1
+end
+
+function advtrains.minN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n-1
+ end
+ return n+1
+end
+
+function atround(number)
+ return math.floor(number+0.5)
+end
+atfloor = math.floor
+
+
+function advtrains.round_vector_floor_y(vec)
+ return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
+end
+
+function advtrains.yawToDirection(yaw, conn1, conn2)
+ if not conn1 or not conn2 then
+ error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
+ end
+ local yaw1 = advtrains.dir_to_angle(conn1)
+ local yaw2 = advtrains.dir_to_angle(conn2)
+ local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
+ local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
+
+ if math.abs(adiff2)<math.abs(adiff1) then
+ return conn2
+ else
+ return conn1
+ end
+end
+
+function advtrains.yawToAnyDir(yaw)
+ local min_conn, min_diff=0, 10
+ for conn, vec in pairs(advtrains.dir_trans_tbl) do
+ local yaw1 = advtrains.dir_to_angle(conn)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_conn = conn
+ min_diff = diff
+ end
+ end
+ return min_conn
+end
+function advtrains.yawToClosestConn(yaw, conns)
+ local min_connid, min_diff=1, 10
+ for connid, conn in ipairs(conns) do
+ local yaw1 = advtrains.dir_to_angle(conn.c)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_connid = connid
+ min_diff = diff
+ end
+ end
+ return min_connid
+end
+
+local pi, pi2 = math.pi, 2*math.pi
+function advtrains.minAngleDiffRad(r1, r2)
+ while r1>pi2 do
+ r1=r1-pi2
+ end
+ while r1<0 do
+ r1=r1+pi2
+ end
+ while r2>pi2 do
+ r2=r2-pi2
+ end
+ while r1<0 do
+ r2=r2+pi2
+ end
+ local try1=r2-r1
+ local try2=r2+pi2-r1
+ local try3=r2-pi2-r1
+
+ local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
+ if minabs==math.abs(try1) then
+ return try1
+ end
+ if minabs==math.abs(try2) then
+ return try2
+ end
+ if minabs==math.abs(try3) then
+ return try3
+ end
+end
+
+
+-- Takes 2 connections (0...AT_CMAX) as argument
+-- Returns the angle median of those 2 positions from the pov
+-- of standing on the cdir1 side and looking towards cdir2
+-- cdir1 - >NODE> - cdir2
+function advtrains.conn_angle_median(cdir1, cdir2)
+ local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
+ local ang2 = advtrains.dir_to_angle(cdir2)
+ return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
+end
+
+function advtrains.merge_tables(a, ...)
+ local new={}
+ for _,t in ipairs({a,...}) do
+ for k,v in pairs(t) do new[k]=v end
+ end
+ return new
+end
+function advtrains.save_keys(tbl, keys)
+ local new={}
+ for _,key in ipairs(keys) do
+ new[key] = tbl[key]
+ end
+ return new
+end
+
+function advtrains.get_real_index_position(path, index)
+ if not path or not index then return end
+
+ local first_pos=path[math.floor(index)]
+ local second_pos=path[math.floor(index)+1]
+
+ if not first_pos or not second_pos then return nil end
+
+ local factor=index-math.floor(index)
+ local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
+ return actual_pos
+end
+function advtrains.pos_median(pos1, pos2)
+ return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
+end
+function advtrains.abs_ceil(i)
+ return math.ceil(math.abs(i))*math.sign(i)
+end
+
+function advtrains.serialize_inventory(inv)
+ local ser={}
+ local liszts=inv:get_lists()
+ for lisztname, liszt in pairs(liszts) do
+ ser[lisztname]={}
+ for idx, item in ipairs(liszt) do
+ local istring=item:to_string()
+ if istring~="" then
+ ser[lisztname][idx]=istring
+ end
+ end
+ end
+ return minetest.serialize(ser)
+end
+function advtrains.deserialize_inventory(sers, inv)
+ local ser=minetest.deserialize(sers)
+ if ser then
+ inv:set_lists(ser)
+ return true
+ end
+ return false
+end
+
+--is_protected wrapper that checks for protection_bypass privilege
+function advtrains.is_protected(pos, name)
+ if not name then
+ error("advtrains.is_protected() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, {protection_bypass=true}) then
+ --player can bypass protection
+ return false
+ end
+ return minetest.is_protected(pos, name)
+end
+
+function advtrains.is_creative(name)
+ if not name then
+ error("advtrains.is_creative() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, {creative=true}) then
+ return true
+ end
+ return minetest.settings:get_bool("creative_mode")
+end
+
+function advtrains.is_damage_enabled(name)
+ if not name then
+ error("advtrains.is_damage_enabled() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, "train_admin") then
+ return false
+ end
+ return minetest.settings:get_bool("enable_damage")
+end
+
+function advtrains.ms_to_kmh(speed)
+ return speed * 3.6
+end
+
+-- 4 possible inputs:
+-- integer: just do that modulo calculation
+-- table with c set: rotate c
+-- table with tables: rotate each
+-- table with integers: rotate each (probably no use case)
+function advtrains.rotate_conn_by(conn, rotate)
+ if tonumber(conn) then
+ return (conn+rotate)%AT_CMAX
+ elseif conn.c then
+ return { c = (conn.c+rotate)%AT_CMAX, y = conn.y}
+ end
+ local tmp={}
+ for connid, data in ipairs(conn) do
+ tmp[connid]=advtrains.rotate_conn_by(data, rotate)
+ end
+ return tmp
+end
+
+
+function advtrains.oppd(dir)
+ return advtrains.rotate_conn_by(dir, AT_CMAX/2)
+end
+--conn_to_match like rotate_conn_by
+--other_conns have to be a table of conn tables!
+function advtrains.conn_matches_to(conn, other_conns)
+ if tonumber(conn) then
+ for connid, data in ipairs(other_conns) do
+ if advtrains.oppd(conn) == data.c then return connid end
+ end
+ return false
+ elseif conn.c then
+ for connid, data in ipairs(other_conns) do
+ local cmp = advtrains.oppd(conn)
+ if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end
+ end
+ return false
+ end
+ local tmp={}
+ for connid, data in ipairs(conn) do
+ local backmatch = advtrains.conn_matches_to(data, other_conns)
+ if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail>
+ end
+ return false
+end
+
+-- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid
+-- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
+-- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided.
+function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on)
+ local this_pos = advtrains.round_vector_floor_y(this_posnr)
+ local this_conns = this_conns_p
+ if not this_conns then
+ _, this_conns = advtrains.get_rail_info_at(this_pos)
+ end
+ if not conn_idx then
+ for coni, _ in ipairs(this_conns) do
+ local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
+ if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
+ end
+ return nil
+ end
+
+ local conn = this_conns[conn_idx]
+ local conn_y = conn.y or 0
+ local adj_pos = advtrains.dirCoordSet(this_pos, conn.c);
+
+ while conn_y>=1 do
+ conn_y = conn_y - 1
+ adj_pos.y = adj_pos.y + 1
+ end
+
+ local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ if not nextnode_ok then
+ adj_pos.y = adj_pos.y - 1
+ conn_y = conn_y + 1
+ nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ if not nextnode_ok then
+ return nil
+ end
+ end
+ local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
+ if adj_connid then
+ return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
+ end
+ return nil
+end
+
+-- when a train enters a rail on connid 'conn', which connid will it go out?
+-- nconns: number of connections in connection table:
+-- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub)
+-- returns: connid_out
+local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}}
+function advtrains.get_matching_conn(conn, nconns)
+ return connlku[nconns][conn]
+end
+
+function advtrains.random_id()
+ local idst=""
+ for i=0,5 do
+ idst=idst..(math.random(0,9))
+ end
+ return idst
+end
+-- Shorthand for pos_to_string and round_vector_floor_y
+function advtrains.roundfloorpts(pos)
+ return minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
+end
+
+-- insert an element into a table if it does not yet exist there
+-- equalfunc is a function to compare equality, defaults to ==
+-- returns true if the element was inserted
+function advtrains.insert_once(tab, elem, equalfunc)
+ for _,e in pairs(tab) do
+ if equalfunc and equalfunc(elem, e) or e==elem then return false end
+ end
+ tab[#tab+1] = elem
+ return true
+end
+
+local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"}
+local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15}
+
+local f = atfloor
+
+local function hex(i)
+ local x=i+32768
+ local c4 = x % 16
+ x = f(x / 16)
+ local c3 = x % 16
+ x = f(x / 16)
+ local c2 = x % 16
+ x = f(x / 16)
+ local c1 = x % 16
+ return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4])
+end
+
+local function c(s,i) return dect[string.sub(s,i,i)] end
+
+local function dec(s)
+ return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768
+end
+-- Takes a position vector and outputs a encoded value suitable as table index
+-- This is essentially a hexadecimal representation of the position (+32768)
+-- Order (YYY)YXXXXZZZZ
+function advtrains.encode_pos(pos)
+ return hex(pos.y) .. hex(pos.x) .. hex(pos.z)
+end
+
+-- decodes a position encoded with encode_pos
+function advtrains.decode_pos(pts)
+ if not pts or not #pts==6 then return nil end
+ local stry = string.sub(pts, 1,4)
+ local strx = string.sub(pts, 5,8)
+ local strz = string.sub(pts, 9,12)
+ return vector.new(dec(strx), dec(stry), dec(strz))
+end
+
+--[[ Benchmarking code
+local tdt = {}
+local tlt = {}
+local tet = {}
+
+for i=1,1000000 do
+ tdt[i] = vector.new(math.random(-65536, 65535), math.random(-65536, 65535), math.random(-65536, 65535))
+ if i%1000 == 0 then
+ tlt[#tlt+1] = tdt[i]
+ end
+end
+
+local t1=os.clock()
+for i=1,1000000 do
+ local pe = advtrains.encode_pos(tdt[i])
+ local pb = advtrains.decode_pos(pe)
+ tet[pe] = i
+end
+for i,v in ipairs(tlt) do
+ local lk = tet[advtrains.encode_pos(v)]
+end
+atdebug("endec",os.clock()-t1,"s")
+
+tet = {}
+
+t1=os.clock()
+for i=1,1000000 do
+ local pe = minetest.pos_to_string(tdt[i])
+ local pb = minetest.string_to_pos(pe)
+ tet[pe] = i
+end
+for i,v in ipairs(tlt) do
+ local lk = tet[minetest.pos_to_string(v)]
+end
+atdebug("pts",os.clock()-t1,"s")
+
+--Results:
+--2018-11-29 16:57:08: ACTION[Main]: [advtrains]endec 1.786451 s
+--2018-11-29 16:57:10: ACTION[Main]: [advtrains]pts 2.566377 s
+]]
+
+
+-- Function to check whether a position is near (within range of) any player
+function advtrains.position_in_range(pos, range)
+ if not pos then
+ return true
+ end
+ for _,p in pairs(minetest.get_connected_players()) do
+ if vector.distance(p:get_pos(),pos)<=range then
+ return true
+ end
+ end
+ return false
+end
+
+local active_node_range = tonumber(minetest.settings:get("active_block_range"))*16 + 16
+-- Function to check whether node at position(pos) is "loaded"/"active"
+-- That is, whether it is within the active_block_range to a player
+if minetest.is_block_active then -- define function differently whether minetest.is_block_active is available or not
+ advtrains.is_node_loaded = minetest.is_block_active
+else
+ function advtrains.is_node_loaded(pos)
+ if advtrains.position_in_range(pos, active_node_range) then
+ return true
+ end
+ end
+end
diff --git a/advtrains/init.lua b/advtrains/init.lua
new file mode 100644
index 0000000..7cc0ccd
--- /dev/null
+++ b/advtrains/init.lua
@@ -0,0 +1,737 @@
+
+--[[
+Advanced Trains - Minetest Mod
+
+Copyright (C) 2016-2020 Moritz Blei (orwell96) and contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+]]
+
+local lot = os.clock()
+minetest.log("action", "[advtrains] Loading...")
+
+-- There is no need to support 0.4.x anymore given that the compatitability with it is already broken by 1bb1d825f46af3562554c12fba35a31b9f7973ff
+attrans = minetest.get_translator ("advtrains")
+
+--advtrains
+advtrains = {trains={}, player_to_train_mapping={}}
+
+-- =======================Development/debugging settings=====================
+-- DO NOT USE FOR NORMAL OPERATION
+local DUMP_DEBUG_SAVE = false
+-- dump the save files in human-readable format into advtrains_DUMP
+
+local GENERATE_ATRICIFIAL_LAG = false
+local HOW_MANY_LAG = 1.0
+-- Simulate a higher server step interval, as it occurs when the server is on high load
+
+advtrains.IGNORE_WORLD = false
+-- Run advtrains without respecting the world map
+-- - No world collision checks occur
+-- - The NDB forcibly places all nodes stored in it into the world regardless of the world's content.
+-- - Rails do not set the 'attached_node' group
+-- This mode can be useful for debugging/testing a world without the map data available
+-- In this case, choose 'singlenode' as mapgen
+
+local NO_SAVE = false
+-- Do not save any data to advtrains save files
+
+-- ==========================================================================
+
+-- Use a global slowdown factor to slow down train movements. Now a setting
+advtrains.DTIME_LIMIT = tonumber(minetest.settings:get("advtrains_dtime_limit")) or 0.2
+advtrains.SAVE_INTERVAL = tonumber(minetest.settings:get("advtrains_save_interval")) or 60
+
+--Constant for maximum connection value/division of the circle
+AT_CMAX = 16
+
+-- get wagon loading range
+advtrains.wagon_load_range = tonumber(minetest.settings:get("advtrains_wagon_load_range"))
+if not advtrains.wagon_load_range then
+ advtrains.wagon_load_range = tonumber(minetest.settings:get("active_block_range"))*16
+end
+
+--pcall
+local no_action=false
+
+local function reload_saves()
+ atwarn("Restoring saved state in 1 second...")
+ no_action=true
+ advtrains.lock_path_inval = false
+ --read last save state and continue, as if server was restarted
+ for aoi, le in pairs(minetest.luaentities) do
+ if le.is_wagon then
+ le.object:remove()
+ end
+ end
+ minetest.after(1, function()
+ advtrains.load()
+ atwarn("Reload successful!")
+ advtrains.ndb.restore_all()
+ end)
+end
+
+advtrains.modpath = minetest.get_modpath("advtrains")
+
+--Advtrains dump (special treatment of pos and sigd)
+function atdump(t, intend)
+ local str
+ if type(t)=="table" then
+ if t.x and t.y and t.z then
+ str=minetest.pos_to_string(t)
+ elseif t.p and t.s then -- interlocking sigd
+ str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]"
+ elseif advtrains.lines and t.s and t.m then -- RwT
+ str=advtrains.lines.rwt.to_string(t)
+ else
+ str="{"
+ local intd = (intend or "") .. " "
+ for k,v in pairs(t) do
+ if type(k)~="string" or not string.match(k, "^path[_]?") then
+ -- do not print anything path-related
+ str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd)
+ end
+ end
+ str = str .. "\n" .. (intend or "") .. "}"
+ end
+ elseif type(t)=="boolean" then
+ if t then
+ str="true"
+ else
+ str="false"
+ end
+ elseif type(t)=="function" then
+ str="<function>"
+ elseif type(t)=="userdata" then
+ str="<userdata>"
+ else
+ str=""..t
+ end
+ return str
+end
+
+function advtrains.print_concat_table(a)
+ local str=""
+ local stra=""
+ local t
+ for i=1,20 do
+ t=a[i]
+ if t==nil then
+ stra=stra.."nil "
+ else
+ str=str..stra
+ stra=""
+ str=str..atdump(t).." "
+ end
+ end
+ return str
+end
+
+atprint=function() end
+atlog=function(t, ...)
+ local text=advtrains.print_concat_table({t, ...})
+ minetest.log("action", "[advtrains]"..text)
+end
+atwarn=function(t, ...)
+ local text=advtrains.print_concat_table({t, ...})
+ minetest.log("warning", "[advtrains]"..text)
+ minetest.chat_send_all("[advtrains] -!- "..text)
+end
+sid=function(id) if id then return string.sub(id, -6) end end
+
+
+--ONLY use this function for temporary debugging. for consistent debug prints use atprint
+atdebug=function(t, ...)
+ local text=advtrains.print_concat_table({t, ...})
+ minetest.log("action", "[advtrains]"..text)
+ minetest.chat_send_all("[advtrains]"..text)
+end
+
+if minetest.settings:get_bool("advtrains_enable_debugging") then
+ atprint=function(t, ...)
+ local context=advtrains.atprint_context_tid or ""
+ if not context then return end
+ local text=advtrains.print_concat_table({t, ...})
+ advtrains.drb_record(context, text)
+
+ --atlog("@@",advtrains.atprint_context_tid,t,...)
+ end
+ dofile(advtrains.modpath.."/debugringbuffer.lua")
+
+end
+
+function assertt(var, typ)
+ if type(var)~=typ then
+ error("Assertion failed, variable has to be of type "..typ)
+ end
+end
+
+dofile(advtrains.modpath.."/helpers.lua");
+--dofile(advtrains.modpath.."/debugitems.lua");
+
+advtrains.meseconrules =
+{{x=0, y=0, z=-1},
+ {x=1, y=0, z=0},
+ {x=-1, y=0, z=0},
+ {x=0, y=0, z=1},
+ {x=1, y=1, z=0},
+ {x=1, y=-1, z=0},
+ {x=-1, y=1, z=0},
+ {x=-1, y=-1, z=0},
+ {x=0, y=1, z=1},
+ {x=0, y=-1, z=1},
+ {x=0, y=1, z=-1},
+ {x=0, y=-1, z=-1},
+ {x=0, y=-2, z=0}}
+
+advtrains.fpath=minetest.get_worldpath().."/advtrains"
+
+dofile(advtrains.modpath.."/path.lua")
+dofile(advtrains.modpath.."/trainlogic.lua")
+dofile(advtrains.modpath.."/trainhud.lua")
+dofile(advtrains.modpath.."/trackplacer.lua")
+dofile(advtrains.modpath.."/copytool.lua")
+dofile(advtrains.modpath.."/tracks.lua")
+dofile(advtrains.modpath.."/occupation.lua")
+dofile(advtrains.modpath.."/atc.lua")
+dofile(advtrains.modpath.."/wagons.lua")
+dofile(advtrains.modpath.."/protection.lua")
+
+dofile(advtrains.modpath.."/trackdb_legacy.lua")
+dofile(advtrains.modpath.."/nodedb.lua")
+dofile(advtrains.modpath.."/couple.lua")
+
+dofile(advtrains.modpath.."/signals.lua")
+dofile(advtrains.modpath.."/misc_nodes.lua")
+dofile(advtrains.modpath.."/crafting.lua")
+dofile(advtrains.modpath.."/craft_items.lua")
+
+dofile(advtrains.modpath.."/log.lua")
+dofile(advtrains.modpath.."/passive.lua")
+if mesecon then
+ dofile(advtrains.modpath.."/p_mesecon_iface.lua")
+end
+
+
+dofile(advtrains.modpath.."/lzb.lua")
+
+
+--load/save
+
+-- backup variables, used if someone should accidentally delete a sub-mod
+-- As of version 4, only used once during migration from version 3 to 4
+-- Since version 4, each of the mods stores a separate save file.
+local MDS_interlocking, MDS_lines
+
+
+advtrains.fpath=minetest.get_worldpath().."/advtrains"
+dofile(advtrains.modpath.."/log.lua")
+function advtrains.read_component(name)
+ local path = advtrains.fpath.."_"..name
+ minetest.log("action", "[advtrains] loading "..path)
+ local file, err = io.open(path, "r")
+ if not file then
+ minetest.log("warning", " Failed to read advtrains save data from file "..path..": "..(err or "Unknown Error"))
+ minetest.log("warning", " (this is normal when first enabling advtrains on this world)")
+ return
+ end
+ local tbl = minetest.deserialize(file:read("*a"))
+ file:close()
+ return tbl
+end
+
+function advtrains.avt_load()
+ -- check for new, split advtrains save file
+
+ local version = advtrains.read_component("version")
+ local tbl
+ if version and version == 4 then
+ advtrains.load_version_4()
+ return
+ -- NOTE: From here, legacy loading code!
+ elseif version and version == 3 then
+ -- we are dealing with the split-up system
+ minetest.log("action", "[advtrains] loading savefiles version 3")
+ local il_save = {
+ tcbs = true,
+ ts = true,
+ signalass = true,
+ rs_locks = true,
+ rs_callbacks = true,
+ influence_points = true,
+ npr_rails = true,
+ }
+ tbl={
+ trains = true,
+ wagon_save = true,
+ ptmap = true,
+ atc = true,
+ ndb = true,
+ lines = true,
+ version = 2,
+ }
+ for i,k in pairs(il_save) do
+ il_save[i] = advtrains.read_component("interlocking_"..i)
+ end
+ for i,k in pairs(tbl) do
+ tbl[i] = advtrains.read_component(i)
+ end
+ tbl["interlocking"] = il_save
+ else
+ local file, err = io.open(advtrains.fpath, "r")
+ if not file then
+ minetest.log("warning", " Failed to read advtrains save data from file "..advtrains.fpath..": "..(err or "Unknown Error"))
+ minetest.log("warning", " (this is normal when first enabling advtrains on this world)")
+ return
+ else
+ tbl = minetest.deserialize(file:read("*a"))
+ file:close()
+ end
+ end
+ if type(tbl) == "table" then
+ if tbl.version then
+ --congrats, we have the new save format.
+ advtrains.trains = tbl.trains
+ --Save the train id into the train table to avoid having to pass id around
+ for id, train in pairs(advtrains.trains) do
+ train.id = id
+ end
+ advtrains.wagons = tbl.wagon_save
+ advtrains.player_to_train_mapping = tbl.ptmap or {}
+ advtrains.ndb.load_data_pre_v4(tbl.ndb)
+ advtrains.atc.load_data(tbl.atc)
+ if advtrains.interlocking then
+ advtrains.interlocking.db.load(tbl.interlocking)
+ else
+ MDS_interlocking = tbl.interlocking
+ end
+ if advtrains.lines then
+ advtrains.lines.load(tbl.lines)
+ else
+ MDS_lines = tbl.lines
+ end
+ --remove wagon_save entries that are not part of a train
+ local todel=advtrains.merge_tables(advtrains.wagon_save)
+ for tid, train in pairs(advtrains.trains) do
+ train.id = tid
+ for _, wid in ipairs(train.trainparts) do
+ todel[wid]=nil
+ end
+ end
+ for wid, _ in pairs(todel) do
+ atwarn("Removing unused wagon", wid, "from wagon_save table.")
+ advtrains.wagon_save[wid]=nil
+ end
+ else
+ --oh no, its the old one...
+ advtrains.trains=tbl
+ --load ATC
+ advtrains.fpath_atc=minetest.get_worldpath().."/advtrains_atc"
+ local file, err = io.open(advtrains.fpath_atc, "r")
+ if not file then
+ local er=err or "Unknown Error"
+ atprint("Failed loading advtrains atc save file "..er)
+ else
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ advtrains.atc.controllers=tbl.controllers
+ end
+ file:close()
+ end
+ --load wagon saves
+ advtrains.fpath_ws=minetest.get_worldpath().."/advtrains_wagon_save"
+ local file, err = io.open(advtrains.fpath_ws, "r")
+ if not file then
+ local er=err or "Unknown Error"
+ atprint("Failed loading advtrains save file "..er)
+ else
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ advtrains.wagon_save=tbl
+ end
+ file:close()
+ end
+ end
+ else
+ minetest.log("error", " Failed to deserialize advtrains save data: Not a table!")
+ end
+ -- moved from advtrains.load()
+ atlatc.load_pre_v4()
+ -- end of legacy loading code
+end
+
+function advtrains.load_version_4()
+ minetest.log("action", "[advtrains] loading savefiles version 4 (serialize_lib)")
+
+ --== load core ==
+ local at_save = serialize_lib.load_atomic(advtrains.fpath.."_core.ls")
+ if at_save then
+ advtrains.trains = at_save.trains
+ --Save the train id into the train table to avoid having to pass id around
+ for id, train in pairs(advtrains.trains) do
+ train.id = id
+ end
+ advtrains.wagons = at_save.wagons
+ advtrains.player_to_train_mapping = at_save.ptmap or {}
+ advtrains.atc.load_data(at_save.atc)
+
+ --remove wagon_save entries that are not part of a train
+ local todel=advtrains.merge_tables(advtrains.wagon_save)
+ for tid, train in pairs(advtrains.trains) do
+ train.id = tid
+ for _, wid in ipairs(train.trainparts) do
+ todel[wid]=nil
+ end
+ end
+ for wid, _ in pairs(todel) do
+ atwarn("Removing unused wagon", wid, "from wagon_save table.")
+ advtrains.wagon_save[wid]=nil
+ end
+ end
+ --== load ndb
+ serialize_lib.load_atomic(advtrains.fpath.."_ndb4.ls", advtrains.ndb.load_callback)
+
+ --== load interlocking ==
+ if advtrains.interlocking then
+ local il_save = serialize_lib.load_atomic(advtrains.fpath.."_interlocking.ls")
+ if il_save then
+ advtrains.interlocking.db.load(il_save)
+ end
+ end
+
+ --== load lines ==
+ if advtrains.lines then
+ local ln_save = serialize_lib.load_atomic(advtrains.fpath.."_lines.ls")
+ if ln_save then
+ advtrains.lines.load(ln_save)
+ end
+ end
+
+ --== load luaatc ==
+ if atlatc then
+ local la_save = serialize_lib.load_atomic(advtrains.fpath.."_atlatc.ls")
+ if la_save then
+ atlatc.load(la_save)
+ end
+ end
+end
+
+advtrains.save_component = function (tbl, name)
+ -- Saves each component of the advtrains file separately
+ --
+ -- required for now to shrink the advtrains db to overcome lua
+ -- limitations.
+ -- Note: as of version 4, only used for the "advtrains_version" file
+ local datastr = minetest.serialize(tbl)
+ if not datastr then
+ minetest.log("error", " Failed to serialize advtrains save data!")
+ return
+ end
+ local path = advtrains.fpath.."_"..name
+ local success = minetest.safe_file_write(path, datastr)
+
+ if not success then
+ minetest.log("error", " Failed to write advtrains save data to file "..path)
+ end
+
+end
+
+advtrains.avt_save = function(remove_players_from_wagons)
+ --atdebug("Saving advtrains files (version 4)")
+
+ if remove_players_from_wagons then
+ for w_id, data in pairs(advtrains.wagons) do
+ data.seatp={}
+ end
+ advtrains.player_to_train_mapping={}
+ end
+
+ local tmp_trains={}
+ for id, train in pairs(advtrains.trains) do
+ --first, deep_copy the train
+ if #train.trainparts > 0 then
+ local v=advtrains.save_keys(train, {
+ "last_pos", "last_connid", "last_frac", "velocity", "tarvelocity",
+ "trainparts", "recently_collided_with_env",
+ "atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open",
+ "text_outside", "text_inside", "line", "routingcode",
+ "il_sections", "speed_restriction", "is_shunt",
+ "points_split", "autocouple", "ars_disable",
+ })
+ --then save it
+ tmp_trains[id]=v
+ else
+ atwarn("Train",id,"had no wagons left because of some bug. It is being deleted. Wave it goodbye!")
+ advtrains.remove_train(id)
+ end
+ end
+
+ for id, wdata in pairs(advtrains.wagons) do
+ local _,proto = advtrains.get_wagon_prototype(wdata)
+ if proto.has_inventory then
+ local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..id})
+ if inv then -- inventory is not initialized when wagon was never loaded
+ -- TOOD: What happens with unloading rails when they don't find the inventory?
+ wdata.ser_inv=advtrains.serialize_inventory(inv)
+ end
+ end
+ -- TODO apply save-keys here too
+ -- TODO temp
+ wdata.dcpl_lock = nil
+ end
+
+ --versions:
+ -- 1 - Initial new save format.
+ -- 2 - version as of tss branch 11-2018+
+ -- 3 - split-up savefile system by gabriel
+ -- 4 - serialize_lib
+
+ -- save of core advtrains
+ local at_save={
+ trains = tmp_trains,
+ wagons = advtrains.wagons,
+ ptmap = advtrains.player_to_train_mapping,
+ atc = advtrains.atc.save_data(),
+ }
+
+ --save of interlocking
+ local il_save
+ if advtrains.interlocking then
+ il_save = advtrains.interlocking.db.save()
+ else
+ il_save = MDS_interlocking
+ end
+
+ -- save of lines
+ local ln_save
+ if advtrains.lines then
+ ln_save = advtrains.lines.save()
+ else
+ ln_save = MDS_lines
+ end
+
+ -- save of luaatc
+ local la_save
+ if atlatc then
+ la_save = atlatc.save()
+ end
+
+ -- parts table for serialize_lib API:
+ -- any table that is nil will not be included and thus not be overwritten
+ local parts_table = {
+ ["core.ls"] = at_save,
+ ["interlocking.ls"] = il_save,
+ ["lines.ls"] = ln_save,
+ ["atlatc.ls"] = la_save,
+ ["ndb4.ls"] = true, -- data not used
+ }
+ local callbacks_table = {
+ ["ndb4.ls"] = advtrains.ndb.save_callback
+ }
+
+ if DUMP_DEBUG_SAVE then
+ local file, err = io.open(advtrains.fpath.."_DUMP", "w")
+ if err then
+ return
+ end
+ file:write(dump(parts_table))
+ file:close()
+ end
+
+ --THE MAGIC HAPPENS HERE
+ local succ, err = serialize_lib.save_atomic_multiple(parts_table, advtrains.fpath.."_", callbacks_table)
+
+ if not succ then
+ atwarn("Saving failed: "..err)
+ else
+ -- store version
+ advtrains.save_component(4, "version")
+ end
+end
+
+--## MAIN LOOP ##--
+--Calls all subsequent main tasks of both advtrains and atlatc
+local init_load=false
+local save_timer = advtrains.SAVE_INTERVAL
+advtrains.mainloop_runcnt=0
+advtrains.global_slowdown = 1
+
+local t = 0
+minetest.register_globalstep(function(dtime_mt)
+ if no_action then
+ -- the advtrains globalstep is skipped by command. Return immediately
+ return
+ end
+
+ advtrains.mainloop_runcnt=advtrains.mainloop_runcnt+1
+ --atprint("Running the main loop, runcnt",advtrains.mainloop_runcnt)
+ --call load once. see advtrains.load() comment
+ if not init_load then
+ advtrains.load()
+ end
+
+ local dtime = dtime_mt * advtrains.global_slowdown
+ if GENERATE_ATRICIFIAL_LAG then
+ dtime = HOW_MANY_LAG
+ if os.clock()<t then
+ return
+ end
+
+ t = os.clock()+HOW_MANY_LAG
+ end
+ -- if dtime is too high, decrease global slowdown
+ if advtrains.DTIME_LIMIT~=0 then
+ if dtime > advtrains.DTIME_LIMIT then
+ if advtrains.global_slowdown > 0.1 then
+ advtrains.global_slowdown = advtrains.global_slowdown - 0.05
+ else
+ advtrains.global_slowdown = advtrains.global_slowdown / 2
+ end
+ dtime = advtrains.DTIME_LIMIT
+ end
+ -- recover global slowdown slowly over time
+ advtrains.global_slowdown = math.min(advtrains.global_slowdown*1.02, 1)
+ end
+
+ advtrains.mainloop_trainlogic(dtime,advtrains.mainloop_runcnt)
+ if advtrains_itm_mainloop then
+ advtrains_itm_mainloop(dtime)
+ end
+ if atlatc then
+ --atlatc.mainloop_stepcode(dtime)
+ atlatc.interrupt.mainloop(dtime)
+ end
+ if advtrains.lines then
+ advtrains.lines.step(dtime)
+ end
+
+ --trigger a save when necessary
+ save_timer=save_timer-dtime
+ if save_timer<=0 then
+ local t=os.clock()
+ --save
+ advtrains.save()
+ save_timer = advtrains.SAVE_INTERVAL
+ atprintbm("saving", t)
+ end
+end)
+
+--if something goes wrong in these functions, there is no help. no pcall here.
+
+--## MAIN LOAD ROUTINE ##
+-- Causes the loading of everything
+-- first time called in main loop (after the init phase) because luaautomation has to initialize first.
+function advtrains.load()
+ advtrains.avt_load() --loading advtrains. includes ndb at advtrains.ndb.load_data()
+ --if atlatc then
+ -- atlatc.load() --includes interrupts
+ --end == No longer loading here. Now part of avt_save() legacy loading.
+ if advtrains_itm_init then
+ advtrains_itm_init()
+ end
+ init_load=true
+ no_action=false
+ atlog("[load_all]Loaded advtrains save files")
+end
+
+--## MAIN SAVE ROUTINE ##
+-- Causes the saving of everything
+function advtrains.save(remove_players_from_wagons)
+ if not init_load then
+ --wait... we haven't loaded yet?!
+ atwarn("Instructed to save() but load() was never called!")
+ return
+ end
+
+ if advtrains.IGNORE_WORLD then
+ advtrains.ndb.restore_all()
+ end
+
+ if NO_SAVE then
+ return
+ end
+ if no_action then
+ atlog("[save] Saving requested externally, but Advtrains step is disabled. Not saving any data as state may be inconsistent.")
+ return
+ end
+
+ local t1 = os.clock()
+ advtrains.avt_save(remove_players_from_wagons) --saving advtrains. includes ndb at advtrains.ndb.save_data()
+ if atlatc then
+ atlatc.save()
+ end
+ atlog("Saved advtrains save files, took",math.floor((os.clock()-t1) * 1000),"ms")
+
+ -- Cleanup actions
+ --TODO very simple yet hacky workaround for the "green signals" bug
+ advtrains.invalidate_all_paths()
+end
+minetest.register_on_shutdown(advtrains.save)
+
+-- This chat command provides a solution to the problem known on the LinuxWorks server
+-- There are many players that joined a single time, got on a train and then left forever
+-- These players still occupy seats in the trains.
+minetest.register_chatcommand("at_empty_seats",
+ {
+ params = "", -- Short parameter description
+ description = "Detach all players, especially the offline ones, from all trains. Use only when no one serious is on a train.", -- Full description
+ privs = {train_operator=true, server=true}, -- Require the "privs" privilege to run
+ func = function(name, param)
+ atwarn("Data is being saved. While saving, advtrains will remove the players from trains. Save files will be reloaded afterwards!")
+ advtrains.save(true)
+ reload_saves()
+ end,
+})
+-- This chat command solves another problem: Trains getting randomly stuck.
+minetest.register_chatcommand("at_reroute",
+ {
+ params = "",
+ description = "Delete all train routes, force them to recalculate",
+ privs = {train_operator=true}, -- Only train operator is required, since this is relatively safe.
+ func = function(name, param)
+ advtrains.invalidate_all_paths()
+ return true, "Successfully invalidated train routes"
+ end,
+})
+
+minetest.register_chatcommand("at_disable_step",
+ {
+ params = "<yes/no>",
+ description = "Disable the advtrains globalstep temporarily",
+ privs = {server=true},
+ func = function(name, param)
+ if minetest.is_yes(param) then
+ -- disable everything, and turn off saving
+ no_action = true;
+ atwarn("The advtrains globalstep has been disabled. Trains are not moving, and no data is saved! Run '/at_disable_step no' to enable again!")
+ return true, "Disabled advtrains successfully"
+ elseif no_action then
+ atwarn("Re-enabling advtrains globalstep...")
+ reload_saves()
+ return true
+ else
+ return false, "Advtrains is already running normally!"
+ end
+ end,
+})
+
+advtrains.is_no_action = function()
+ return no_action
+end
+
+
+local tot=(os.clock()-lot)*1000
+minetest.log("action", "[advtrains] Loaded in "..tot.."ms")
+
diff --git a/advtrains/locale/advtrains.de.tr b/advtrains/locale/advtrains.de.tr
new file mode 100644
index 0000000..cd43eed
--- /dev/null
+++ b/advtrains/locale/advtrains.de.tr
@@ -0,0 +1,72 @@
+# textdomain: advtrains
+This wagon is owned by @1, you can't destroy it.=Dieser Waggon gehört @1, du kannst ihn nicht abbauen.
+Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon.=Warnung: Du erhältst nur etwas Stahl zurück. Wenn du sicher bist, dass du den Waggon zerstören willst, halte 'Schleichen' und klicke links.
+Show Inventory=Zeige Inventar
+Select seat:=Wähle einen Sitzplatz aus:
+ATC controller, unconfigured.=Zugbeeinflussungsschiene, nicht konfiguiert.
+ATC controller=Zugbeeinflussungsschiene
+ATC controller, mode @1@nChannel: @2=Zugbeeinflussungsschiene in Betriebsart "@1"@nKanal: @2
+ATC controller, mode @1@nCommand: @2=Zugbeeinflussungsschiene in Betriebsart "@1"@nBefehl: @2
+Command=Befehl
+Command (on)=Befehl (wenn ein)
+Digiline channel=Digiline-Kanal
+Save=Speichern
+ATC Reverse command warning: didn't reverse train, train moving!=Zugbeeinflussung - Warnung: Befehl 'R' nicht ausgeführt, Zug in Bewegung!
+ATC command syntax error: I statement not closed: @1=Zugbeeinflussung - Syntaxfehler: I-Anweisung nicht geschlossen: @1
+ATC command parse error: Unknown command: @1=Zugbeeinflussung - Fehler: Unbekannter Befehl: @1
+This position is protected!=Diese Position ist geschützt!
+You need to own at least one neighboring wagon to destroy this couple.=Du musst Besitzer eines angrenzenden Waggons sein, um hier abzukuppeln.
+@1 Platform (low)=Niedriger @1-Bahnsteig
+@1 Platform (high)=Hoher @1-Bahnsteig
+off=aus
+on=ein
+Lampless Signal (@1)=Mechanisches Signal (@1)
+Signal (@1)=Lichtsignal (@1)
+Track Worker Tool@n@nLeft-click: change rail type (straight/curve/switch)@nRight-click: rotate rail/bumper/signal/etc.=Schienenwerkzeug@n@nLinksklick: Schienentyp ändern, Rechtsklick: Objekt drehen.
+This node can't be rotated using the trackworker!=Kann diesen Block nicht mit dem Schienenwerkzeug drehen.
+This node can't be changed using the trackworker!=Kann diesen Block nicht mit dem Schienenwerkzeug bearbeiten.
+Can't place: not pointing at node=Kann nicht platzieren: Du zeigst nicht auf einen Block.
+Can't place: space occupied!=Kann nicht platzieren: Platz besetzt.
+Can't place: protected position!=Kann nicht platzieren: Position geschützt.
+Can't place: Not enough slope items left (@1 required)=Kann nicht platzieren: nicht genug Steigungsblöcke, es werden insgesamt @1 benötigt.
+Can't place: There's no slope of length @1=Kann nicht platzieren: Keine Steigung der Länge @1 definiert.
+Can't place: no supporting node at upper end.=Kann nicht platzieren: kein unterstützender Block am Ende der Steigung.
+Deprecated Track=ausrangierte Schiene, nicht verwenden.
+Track=Schiene
+Bumper=Prellbock
+Detector Rail=Detektorschiene
+Speed:=Geschw.:
+Target:=Zielges.:
+@1 Slope=@1 Steigung
+Can't get on: wagon full or doors closed!=Kann nicht einsteigen: Waggon voll oder Türen geschlossen.
+Use Sneak+rightclick to bypass closed doors!=Nutze Sneak+Rechtsklick, um die Türnotöffnung zu aktivieren und trotzdem einzusteigen.
+Lock couples=Kupplungen sperren
+Save wagon properties=Waggon-Einstellungen speichern
+Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!=Türen sind geschlossen! Sneak+Rechtsklick, um die Türnotöffnung zu aktivieren und trotzdem auszusteigen.
+Wagon properties=Waggon-Einstellungen
+Get off=Aussteigen
+Get off (forced)=Aussteigen (erzwingen)
+(Doors closed)=(Türen geschlossen)
+Access to @1=Zugang zu @1
+Default Seat=Standardsitzplatz
+Default Seat (driver stand)=Standardsitzplatz (Führerstand)
+Driver Stand=Führerstand
+Driver Stand (left)=Führerstand Links
+Driver Stand (right)=Führerstand Rechts
+Industrial Train Engine=Industrielle Lokomotive
+Industrial tank wagon=Tankwaggon
+Industrial wood wagon=Holztransportwaggon
+Japanese Train Engine=Japanische Personenzug-Lokomotive
+Japanese Train Wagon=Japanischer Personenzug-Passagierwaggon
+Steam Engine=Dampflokomotive
+Detailed Steam Engine=detaillierte Dampflokomotive
+Passenger Wagon=Passagierwaggon
+Box wagon=Güterwaggon
+Subway Passenger Wagon=U-Bahn-Waggon
+The wagon's inventory is not empty!=Das Inventar dieses Waggons ist nicht leer!
+This track can not be changed!=Diese Schiene kann nicht geändert werden!
+This track can not be rotated!=Diese Schiene kann nicht gedreht werden!
+This track can not be removed!=Diese Schiene kann nicht entfernt werden!
+Position is occupied by a train.=Ein Zug steht an dieser Position.
+There's a Track Circuit Break here.=Hier ist eine Gleisabschnittsgrenze (TCB).
+There's a Signal Influence Point here.=Hier ist ein Signal-Beeinflussungspunkt.
diff --git a/advtrains/locale/advtrains.zh_CN.tr b/advtrains/locale/advtrains.zh_CN.tr
new file mode 100644
index 0000000..ef9c99b
--- /dev/null
+++ b/advtrains/locale/advtrains.zh_CN.tr
@@ -0,0 +1,107 @@
+# textdomain: advtrains
+
+# Advtrains Core (unorganized)
+This wagon is owned by @1, you can't destroy it.=这是@1的车厢, 你不能摧毁它.
+Warning: If you destroy this wagon, you only get some steel back! If you are sure, hold Sneak and left-click the wagon.=警告: 如果你摧毁此车厢, 你只能拿到一些钢方块. 如果你确定要摧毁这个车厢,请按潜行键并左键单击此车厢.
+ATC controller, unconfigured.=ATC控制器 (未配置)
+ATC controller=ATC控制器
+ATC controller, mode @1@nChannel: @2=ATC控制器@n模式: @1@n频道: @2
+ATC controller, mode @1@nCommand: @2=ATC控制器@n模式: @1@n命令: @2
+Command=命令
+Command (on)=命令(激活时)
+Digiline channel=Digiline 频道
+ATC Reverse command warning: didn't reverse train, train moving!=ATC警告:未执行“R”命令, 火车在移动
+ATC command syntax error: I statement not closed: @1=ATC语法错误: "I"命令不完整: @1
+ATC command parse error: Unknown command: @1=ATC语法错误: 未知命令: @1
+This position is protected!=这里已被保护.
+You need to own at least one neighboring wagon to destroy this couple.=你必须至少拥有其中一个车厢才能解耦这两个车厢.
+This node can't be rotated using the trackworker!=你不能使用铁路调整工具旋转这个方块.
+This node can't be changed using the trackworker!=你不能使用铁路调整工具调整这个方块.
+Can't place: not pointing at node=无法放置: 你没有选择任何方块.
+Can't place: space occupied!=无法放置: 此区域已被占用.
+Can't place: protected position!=无法放置: 此区域已被保护.
+Can't place: Not enough slope items left (@1 required)=无法放置: 你没有足够的铁路斜坡放置工具 (你需要@1个)
+Can't place: There's no slope of length @1=无法放置: advtrains不支持长度为@1m的斜坡.
+Can't place: no supporting node at upper end.=无法放置: 较高端没有支撑方块.
+Deprecated Track=请不要使用
+Can't get on: wagon full or doors closed!=无法上车: 车门已关闭或车厢已满
+Use Sneak+rightclick to bypass closed doors!=请使用潜行+右键上车
+Lock couples=锁定连接处
+Doors are closed! Use Sneak+rightclick to ignore the closed doors and get off!=车门已关闭, 请使用潜行+右键单击下车
+Access to @1=可前往@1
+The clipboard couldn't access the metadata. Paste failed.=无法粘贴: 剪贴板无法访问元数据
+The clipboard couldn't access the metadata. Copy failed.=无法复制: 剪贴板无法访问元数据
+
+# Train HUD/Formspecs
+Speed:=速度:
+Target:=目标速度:
+Show Inventory=显示物品栏
+Select seat:=请选择座位
+Wagon properties=车厢属性
+Save wagon properties=保存车厢属性
+Text displayed outside on train=车厢外部显示
+Text displayed inside train=车厢内部显示
+Line=火车线路
+Routingcode=路由码
+Get off=下车
+Get off (forced)=强制下车
+(Doors closed)=(车门已关闭)
+
+# General
+Save=保存
+# "off" and "on" can be translated differently depending on the context and are therefore not translated.
+off=off
+on=on
+
+# Line automation
+Station Code=车站代码
+Station Name=车站名称
+Door Delay=车门关闭时间
+Departure Speed=出发速度
+Stop Time=停站时间
+
+# Items
+Track Worker Tool@n@nLeft-click: change rail type (straight/curve/switch)@nRight-click: rotate rail/bumper/signal/etc.=铁路调整工具@n@n左键单击: 切换轨道类型@n右键单击: 旋转方块
+Passive Component Naming Tool@n@nRight-click to name a passive component.=被动元件命名工具@n@n右键单击命名所选元件.
+Train copy/paste tool@n@nLeft-click: copy train@nRight-click: paste train=火车复制工具@n@n左键单击: 复制@n右键单击: 粘帖
+Track=铁轨
+Perpendicular Diamond Crossing Track=垂直交叉铁轨
+45/90 Degree Diamond Crossing Track=45度交叉铁轨
+Unloading Track=卸货铁轨
+Loading Track=装货铁轨
+Bumper=保险杠
+Detector Rail=探测铁轨
+@1 Slope=@1斜坡
+@1 Platform (low)=50cm高的@1站台
+@1 Platform (high)=1m高的@1站台
+@1 Platform (45 degree)=1m高的@1站台 (45度)
+Lampless Signal (@1)=臂板信号机 (@1)
+Signal (@1)=信号灯 (@1)
+Wallmounted Signal (l)=壁挂式信号灯 (左侧)
+Wallmounted Signal (r)=壁挂式信号灯 (右侧)
+Wallmounted Signal (t)=悬挂式信号灯
+Andrew's Cross=铁路道口信号灯
+Boiler=锅炉
+driver's cab=驾驶室
+Wheel=车轮
+Chimney=烟囱
+
+# Seats
+Default Seat=默认座位
+Default Seat (driver stand)=默认座位 (司机座位)
+Driver Stand=司机座位
+Driver Stand (left)=左侧司机座位
+Driver Stand (right)=右侧司机座位
+
+# Wagon/engine types
+Industrial Train Engine=工业用火车头
+Big Industrial Train Engine=大型工业用火车头
+Industrial tank wagon=液体运输车厢
+Industrial wood wagon=木材运输车厢
+Japanese Train Engine=高速列车车头
+Japanese Train Wagon=高速列车车厢
+Steam Engine=蒸汽机车
+Detailed Steam Engine=精细的蒸汽机车
+Passenger Wagon=客车
+Box Wagon=货运车厢
+Subway Passenger Wagon=地铁车厢
diff --git a/advtrains/log.lua b/advtrains/log.lua
new file mode 100644
index 0000000..d7053a2
--- /dev/null
+++ b/advtrains/log.lua
@@ -0,0 +1,17 @@
+-- Log accesses to driver stands and changes to switches
+
+advtrains.log = function() end
+
+if minetest.settings:get_bool("advtrains_enable_logging") then
+ advtrains.logfile = advtrains.fpath .. "_log"
+
+ local log = io.open(advtrains.logfile, "a+")
+
+ function advtrains.log (event, player, pos, data)
+ log:write(os.date()..": "..event.." by "..player.." at "..minetest.pos_to_string(pos).." -- "..(data or "").."\n")
+ end
+
+ minetest.register_on_shutdown(function()
+ log:close()
+ end)
+end
diff --git a/advtrains/lzb.lua b/advtrains/lzb.lua
new file mode 100644
index 0000000..cbdc422
--- /dev/null
+++ b/advtrains/lzb.lua
@@ -0,0 +1,276 @@
+-- lzb.lua
+-- Enforced and/or automatic train override control, providing the on_train_approach callback
+
+--[[
+Documentation of train.lzb table
+train.lzb = {
+ trav_index = Current index that the traverser has advanced so far
+ checkpoints = table containing oncoming signals, in order of index
+ {
+ pos = position of the point
+ index = where this is on the path
+ speed = speed allowed to pass. nil = no effect
+ callback = function(pos, id, train, index, speed, lzbdata)
+ -- Function that determines what to do on the train in the moment it drives over that point.
+ -- When spd==0, called instead when train has stopped in front
+ -- nil = no effect
+ lzbdata = {}
+ -- Table of custom data filled in by approach callbacks
+ -- Whenever an approach callback inserts an LZB checkpoint with changed lzbdata,
+ -- all consecutive approach callbacks will see these passed as lzbdata table.
+
+ udata = arbitrary user data, no official way to retrieve (do not use)
+ }
+ trav_lzbdata = currently active lzbdata table at traverser index
+}
+The LZB subsystem keeps track of "checkpoints" the train will pass in the future, and has two main tasks:
+1. run approach callbacks, and run callbacks when passing LZB checkpoints
+2. keep track of the permitted speed at checkpoints, and make sure that the train brakes accordingly
+To perform 2, it populates the train.path_speed table which is handled along with the path subsystem.
+This table is used in trainlogic.lua/train_step_b() and applied to the velocity calculations.
+
+Note: in contrast to node enter callbacks, which are called when the train passes the .5 index mark, LZB callbacks are executed on passing the .0 index mark!
+If an LZB checkpoint has speed 0, the train will still enter the node (the enter callback will be called), but will stop at the 0.9 index mark (for details, see SLOW_APPROACH in trainlogic.lua)
+
+The start point for the LZB traverser (and thus the first node that will receive an approach callback) is floor(train.index) + 1. This means, once the LZB checkpoint callback has fired,
+this path node will not receive any further approach callbacks for the same approach situation
+]]
+
+
+local params = {
+ BRAKE_SPACE = 10,
+ AWARE_ZONE = 50,
+
+ ADD_STAND = 2.5,
+ ADD_SLOW = 1.5,
+ ADD_FAST = 7,
+ ZONE_ROLL = 2,
+ ZONE_HOLD = 5, -- added on top of ZONE_ROLL
+ ZONE_VSLOW = 3, -- When speed is <2, still allow accelerating
+
+ DST_FACTOR = 1.5,
+
+ SHUNT_SPEED_MAX = advtrains.SHUNT_SPEED_MAX,
+}
+
+function advtrains.set_lzb_param(par, val)
+ if params[par] and tonumber(val) then
+ params[par] = tonumber(val)
+ else
+ error("Inexistant param or not a number")
+ end
+end
+
+local function resolve_latest_lzbdata(ckp, index)
+ local i = #ckp
+ local ckpi
+ while i>0 do
+ ckpi = ckp[i]
+ if ckpi.index <= index and ckpi.lzbdata then
+ return ckpi.lzbdata
+ end
+ i=i-1
+ end
+ return {}
+end
+
+local function look_ahead(id, train)
+ local lzb = train.lzb
+ if lzb.zero_checkpoint then
+ -- if the checkpoints list contains a zero checkpoint, don't look ahead
+ -- in order to not trigger approach callbacks on the wrong path
+ return
+ end
+
+ local acc = advtrains.get_acceleration(train, 1)
+ -- worst-case: the starting point is maximum speed
+ local vel = train.max_speed or train.velocity
+ local brakedst = ( -(vel*vel) / (2*acc) ) * params.DST_FACTOR
+
+ --local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE)
+ -- worst case (don't use index_by_offset)
+ local brake_i = atfloor(train.index + brakedst + params.BRAKE_SPACE)
+ atprint("LZB: looking ahead up to ", brake_i)
+
+ --local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
+
+ local trav = lzb.trav_index
+ -- retrieve latest lzbdata
+ if not lzb.trav_lzbdata then
+ lzb.trav_lzbdata = resolve_latest_lzbdata(lzb.checkpoints, trav)
+ end
+
+ if lzb.trav_lzbdata.off_track then
+ --previous position was off track, do not scan any further
+ end
+
+ while trav <= brake_i and not lzb.zero_checkpoint do
+ local pos = advtrains.path_get(train, trav)
+ -- check offtrack
+ if trav - 1 == train.path_trk_f then
+ lzb.trav_lzbdata.off_track = true
+ advtrains.lzb_add_checkpoint(train, trav - 1, 0, nil, lzb.trav_lzbdata)
+ else
+ -- run callbacks
+ -- Note: those callbacks are defined in trainlogic.lua for consistency with the other node callbacks
+ advtrains.tnc_call_approach_callback(pos, id, train, trav, lzb.trav_lzbdata)
+
+ end
+ trav = trav + 1
+
+ end
+
+ lzb.trav_index = trav
+
+end
+advtrains.lzb_look_ahead = look_ahead
+
+
+local function call_runover_callbacks(id, train)
+ if not train.lzb then return end
+
+ local i = 1
+ local idx = atfloor(train.index)
+ local ckp = train.lzb.checkpoints
+ while ckp[i] do
+ if ckp[i].index <= idx then
+ atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed)
+ -- call callback
+ local it = ckp[i]
+ if it.callback then
+ it.callback(it.pos, id, train, it.index, it.speed, train.lzb.lzbdata)
+ end
+ -- note: lzbdata is always defined as look_ahead was called before
+ table.remove(ckp, i)
+ else
+ i = i + 1
+ end
+ end
+end
+
+-- Flood-fills train.path_speed, based on this checkpoint
+local function apply_checkpoint_to_path(train, checkpoint)
+ if not checkpoint.speed then
+ return
+ end
+ atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed)
+
+ if checkpoint.speed == 0 then
+ train.lzb.zero_checkpoint = true
+ end
+
+ -- make sure path exists until checkpoint
+ local pos = advtrains.path_get(train, checkpoint.index)
+
+ local brake_accel = advtrains.get_acceleration(train, 11)
+
+ -- start with the checkpoint index at specified speed
+ local index = checkpoint.index
+ local p_speed -- speed in path_speed
+ local c_speed = checkpoint.speed -- calculated speed at current index
+ while true do
+ p_speed = train.path_speed[index]
+ if (p_speed and p_speed <= c_speed) or index < train.index then
+ --we're done. train already slower than wanted at this position
+ return
+ end
+ -- insert calculated target speed
+ train.path_speed[index] = c_speed
+ -- calculate c_speed at previous index
+ advtrains.path_get(train, index-1)
+ local eldist = train.path_dist[index] - train.path_dist[index-1]
+ -- Calculate the start velocity the train would have if it had a end velocity of c_speed and accelerating with brake_accel, after a distance of eldist:
+ -- v0² = v1² - 2*a*s
+ c_speed = math.sqrt( (c_speed * c_speed) - (2 * brake_accel * eldist) )
+ index = index - 1
+ end
+end
+
+--[[
+Distance needed to accelerate from v0 to v1 with constant acceleration a:
+
+ v1 - v0 a / v1 - v0 \ 2 v1^2 - v0^2
+s = v0 * ------- + - * | ------- | = -----------
+ a 2 \ a / 2*a
+]]
+
+-- Removes all LZB checkpoints and restarts the traverser at the current train index
+function advtrains.lzb_invalidate(train)
+ train.lzb = {
+ trav_index = atfloor(train.index) + 1,
+ checkpoints = {},
+ }
+end
+
+-- LZB part of path_invalidate_ahead. Clears all checkpoints that are ahead of start_idx
+-- in contrast to path_inv_ahead, doesn't complain if start_idx is behind train.index, clears everything then
+function advtrains.lzb_invalidate_ahead(train, start_idx)
+ if train.lzb then
+ local idx = atfloor(start_idx)
+ local i = 1
+ while train.lzb.checkpoints[i] do
+ if train.lzb.checkpoints[i].index >= idx then
+ table.remove(train.lzb.checkpoints, i)
+ else
+ i=i+1
+ end
+ end
+ train.lzb.trav_index = idx
+ -- FIX reset trav_lzbdata (look_ahead fetches these when required)
+ train.lzb.trav_lzbdata = nil
+ -- re-apply all checkpoints to path_speed
+ train.path_speed = {}
+ train.lzb.zero_checkpoint = false
+ for _,ckp in ipairs(train.lzb.checkpoints) do
+ apply_checkpoint_to_path(train, ckp)
+ end
+ end
+end
+
+-- Add LZB control point
+-- lzbdata: If you modify lzbdata in an approach callback, you MUST add a checkpoint AND pass the (modified) lzbdata into it.
+-- If you DON'T modify lzbdata, you MUST pass nil as lzbdata. Always modify the lzbdata table in place, never overwrite it!
+-- udata: user-defined data, do not use externally
+function advtrains.lzb_add_checkpoint(train, index, speed, callback, lzbdata, udata)
+ local lzb = train.lzb
+ local pos = advtrains.path_get(train, index)
+ local lzbdata_c = nil
+ if lzbdata then
+ -- make a shallow copy of lzbdata
+ lzbdata_c = {}
+ for k,v in pairs(lzbdata) do lzbdata_c[k] = v end
+ end
+ local ckp = {
+ pos = pos,
+ index = index,
+ speed = speed,
+ callback = callback,
+ lzbdata = lzbdata_c,
+ udata = udata,
+ }
+ table.insert(lzb.checkpoints, ckp)
+
+ apply_checkpoint_to_path(train, ckp)
+end
+
+
+advtrains.te_register_on_new_path(function(id, train)
+ advtrains.lzb_invalidate(train)
+ -- Taken care of in pre-move hook (see train_step_b)
+ --look_ahead(id, train)
+end)
+
+advtrains.te_register_on_invalidate_ahead(function(id, train, start_idx)
+ advtrains.lzb_invalidate_ahead(train, start_idx)
+end)
+
+advtrains.te_register_on_update(function(id, train)
+ if not train.path or not train.lzb then
+ atprint("LZB run: no path on train, skip step")
+ return
+ end
+ -- Note: look_ahead called from train_step_b before applying movement
+ -- TODO: if more pre-move hooks are added, make a separate callback hook
+ --look_ahead(id, train)
+ call_runover_callbacks(id, train)
+end, true)
diff --git a/advtrains/misc_nodes.lua b/advtrains/misc_nodes.lua
new file mode 100644
index 0000000..fe484ac
--- /dev/null
+++ b/advtrains/misc_nodes.lua
@@ -0,0 +1,123 @@
+--all nodes that do not fit in any other category
+
+function advtrains.register_platform(modprefix, preset)
+ local ndef=minetest.registered_nodes[preset]
+ if not ndef then
+ minetest.log("warning", " register_platform couldn't find preset node "..preset)
+ return
+ end
+ local btex=ndef.tiles
+ if type(btex)=="table" then
+ btex=btex[1]
+ end
+ local desc=ndef.description or ""
+ local nodename=string.match(preset, ":(.+)$")
+ minetest.register_node(modprefix .. ":platform_low_"..nodename, {
+ description = attrans("@1 Platform (low)", desc),
+ tiles = {btex.."^advtrains_platform.png", btex, btex, btex, btex, btex},
+ groups = {cracky = 1, not_blocking_trains = 1, platform=1},
+ sounds = default.node_sound_stone_defaults(),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.1, -0.1, 0.5, 0 , 0.5},
+ {-0.5, -0.5, 0 , 0.5, -0.1, 0.5}
+ },
+ },
+ paramtype2="facedir",
+ paramtype = "light",
+ sunlight_propagates = true,
+ })
+ minetest.register_node(modprefix .. ":platform_high_"..nodename, {
+ description = attrans("@1 Platform (high)", desc),
+ tiles = {btex.."^advtrains_platform.png", btex, btex, btex, btex, btex},
+ groups = {cracky = 1, not_blocking_trains = 1, platform=2},
+ sounds = default.node_sound_stone_defaults(),
+ drawtype = "nodebox",
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, 0.3, 0, 0.5, 0.5, 0.5},
+ {-0.5, -0.5, 0.1 , 0.5, 0.3, 0.5}
+ },
+ },
+ paramtype2="facedir",
+ paramtype = "light",
+ sunlight_propagates = true,
+ })
+ local diagonalbox = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, 0.5, -0.25, 0.5, -0.8 },
+ {-0.25, -0.5, 0.5 , 0, 0.5, -0.55},
+ {0, -0.5, 0.5 , 0.25, 0.5, -0.3 },
+ {0.25 , -0.5, 0.5, 0.5, 0.5, -0.05}
+ }
+ }
+ minetest.register_node(modprefix..":platform_45_"..nodename, {
+ description = attrans("@1 Platform (45 degree)", desc),
+ groups = {cracky = 1, not_blocking_trains = 1, platform=2},
+ sounds = default.node_sound_stone_defaults(),
+ drawtype = "mesh",
+ mesh = "advtrains_platform_diag.b3d",
+ selection_box = diagonalbox,
+ collision_box = diagonalbox,
+ tiles = {btex, btex.."^advtrains_platform_diag.png"},
+ paramtype2 = "facedir",
+ paramtype = "light",
+ sunlight_propagates = true,
+ })
+ local diagonalbox_low = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, 0.5, -0.25, 0, -0.8 },
+ {-0.25, -0.5, 0.5 , 0, 0, -0.55},
+ {0, -0.5, 0.5 , 0.25, 0, -0.3 },
+ {0.25 , -0.5, 0.5, 0.5, 0, -0.05}
+ }
+ }
+ minetest.register_node(modprefix..":platform_45_low_"..nodename, {
+ description = attrans("@1 Platform (low, 45 degree)", desc),
+ groups = {cracky = 1, not_blocking_trains = 1, platform=2},
+ sounds = default.node_sound_stone_defaults(),
+ drawtype = "mesh",
+ mesh = "advtrains_platform_diag_low.b3d",
+ selection_box = diagonalbox_low,
+ collision_box = diagonalbox_low,
+ tiles = {btex, btex.."^advtrains_platform_diag.png"},
+ paramtype2 = "facedir",
+ paramtype = "light",
+ sunlight_propagates = true,
+ })
+ minetest.register_craft({
+ type="shapeless",
+ output = modprefix .. ":platform_high_"..nodename.." 4",
+ recipe = {
+ "dye:yellow", preset, preset
+ },
+ })
+ minetest.register_craft({
+ type="shapeless",
+ output = modprefix .. ":platform_low_"..nodename.." 4",
+ recipe = {
+ "dye:yellow", preset
+ },
+ })
+ minetest.register_craft({
+ type="shapeless",
+ output = modprefix .. ":platform_45_"..nodename.." 2",
+ recipe = {
+ "dye:yellow", preset, preset, preset
+ }
+ })
+ minetest.register_craft({
+ type="shapeless",
+ output = modprefix .. ":platform_45_low_"..nodename.." 2",
+ recipe = { modprefix .. ":platform_45_"..nodename },
+ })
+end
+
+
+advtrains.register_platform("advtrains", "default:stonebrick")
+advtrains.register_platform("advtrains", "default:sandstonebrick")
diff --git a/advtrains/mod.conf b/advtrains/mod.conf
new file mode 100644
index 0000000..027e08a
--- /dev/null
+++ b/advtrains/mod.conf
@@ -0,0 +1,7 @@
+name=advtrains
+title=Advanced Trains Core
+description=Core system for realistic trains in Minetest
+author=orwell96
+
+depends=serialize_lib
+optional_depends=mesecons,digtron
diff --git a/advtrains/models/advtrains_across.obj b/advtrains/models/advtrains_across.obj
new file mode 100644
index 0000000..4ebf393
--- /dev/null
+++ b/advtrains/models/advtrains_across.obj
@@ -0,0 +1,537 @@
+# Blender v2.78 (sub 0) OBJ File: 'andreaskreuz.blend'
+# www.blender.org
+o Cube
+v 0.183133 0.500062 -0.365883
+v 0.300122 0.552646 -0.365883
+v 0.300122 0.552646 -0.337392
+v 0.183133 0.500062 -0.337392
+v -0.300121 1.575195 -0.365883
+v -0.183133 1.627780 -0.365883
+v -0.183133 1.627780 -0.337392
+v -0.300121 1.575195 -0.337392
+v -0.035079 -0.503415 -0.299723
+v -0.035079 1.055075 -0.299723
+v -0.024805 -0.503415 -0.324528
+v -0.024805 1.055075 -0.324528
+v -0.000000 -0.503415 -0.334802
+v -0.000000 1.055075 -0.334802
+v 0.024805 -0.503415 -0.324528
+v 0.024805 1.055075 -0.324528
+v 0.035079 -0.503415 -0.299723
+v 0.035079 1.055075 -0.299723
+v 0.024805 -0.503415 -0.274918
+v 0.024805 1.055075 -0.274918
+v -0.000000 -0.503415 -0.264644
+v -0.000000 1.055075 -0.264644
+v -0.024805 -0.503415 -0.274918
+v -0.024805 1.055075 -0.274918
+v -0.000000 -0.698893 -0.299723
+v -0.032318 0.979391 -0.365883
+v -0.084671 1.095865 -0.365883
+v 0.084671 1.031976 -0.365883
+v 0.032318 1.148450 -0.365883
+v 0.084671 1.031976 -0.337392
+v 0.032318 1.148450 -0.337392
+v -0.032318 0.979391 -0.337392
+v -0.084671 1.095865 -0.337392
+v -0.133841 1.055075 -0.323880
+v -0.096352 1.055075 -0.376499
+v -0.115909 0.988154 -0.323880
+v -0.083444 1.006899 -0.376499
+v -0.066920 0.939165 -0.323880
+v -0.048176 0.971631 -0.376499
+v -0.000000 0.921234 -0.323880
+v -0.000000 0.958722 -0.376499
+v 0.066920 0.939165 -0.323880
+v 0.048176 0.971631 -0.376499
+v 0.115909 0.988154 -0.323880
+v 0.083443 1.006899 -0.376499
+v 0.133841 1.055075 -0.323880
+v 0.096352 1.055075 -0.376499
+v 0.115909 1.121995 -0.323880
+v 0.083443 1.103251 -0.376499
+v 0.066920 1.170984 -0.323880
+v 0.048176 1.138518 -0.376499
+v 0.000000 1.188915 -0.323880
+v 0.000000 1.151427 -0.376499
+v -0.066920 1.170984 -0.323880
+v -0.048176 1.138518 -0.376499
+v -0.115909 1.121995 -0.323880
+v -0.083444 1.103251 -0.376499
+v -0.115909 0.988154 -0.376499
+v -0.133841 1.055075 -0.376499
+v -0.066920 0.939165 -0.376499
+v -0.000000 0.921234 -0.376499
+v 0.066920 0.939165 -0.376499
+v 0.115909 0.988154 -0.376499
+v 0.133841 1.055075 -0.376499
+v 0.115909 1.121995 -0.376499
+v 0.066920 1.170984 -0.376499
+v 0.000000 1.188915 -0.376499
+v -0.066920 1.170984 -0.376499
+v -0.115909 1.121995 -0.376499
+v -0.094673 1.000415 -0.376499
+v -0.109318 1.055075 -0.376499
+v -0.054659 0.960402 -0.376499
+v -0.000000 0.945756 -0.376499
+v 0.054659 0.960402 -0.376499
+v 0.094672 1.000415 -0.376499
+v 0.109318 1.055075 -0.376499
+v 0.094672 1.109734 -0.376499
+v 0.054659 1.149747 -0.376499
+v 0.000000 1.164393 -0.376499
+v -0.054659 1.149747 -0.376499
+v -0.094672 1.109734 -0.376499
+v -0.094673 1.000415 -0.397361
+v -0.109318 1.055075 -0.397361
+v -0.054659 0.960402 -0.397361
+v -0.000000 0.945756 -0.397361
+v 0.054659 0.960402 -0.397361
+v 0.094672 1.000415 -0.397361
+v 0.109318 1.055075 -0.397361
+v 0.094672 1.109734 -0.459571
+v 0.054659 1.149747 -0.459571
+v 0.000000 1.164393 -0.459571
+v -0.054659 1.149747 -0.459571
+v -0.094672 1.109734 -0.459571
+v -0.083444 1.006899 -0.397361
+v -0.096352 1.055075 -0.397361
+v -0.048176 0.971631 -0.397361
+v -0.000000 0.958722 -0.397361
+v 0.048176 0.971631 -0.397361
+v 0.083443 1.006899 -0.397361
+v 0.096352 1.055075 -0.397361
+v 0.083443 1.103251 -0.459571
+v 0.048176 1.138518 -0.459571
+v 0.000000 1.151427 -0.459571
+v -0.048176 1.138518 -0.459571
+v -0.083444 1.103251 -0.459571
+v 0.084671 1.095865 -0.337392
+v 0.032318 0.979391 -0.337392
+v -0.032318 1.148450 -0.337392
+v -0.084671 1.031976 -0.337392
+v -0.032318 1.148450 -0.365883
+v -0.084671 1.031976 -0.365883
+v 0.084671 1.095865 -0.365883
+v 0.032318 0.979391 -0.365883
+v 0.300122 1.575195 -0.337392
+v 0.183133 1.627780 -0.337392
+v 0.183133 1.627780 -0.365883
+v 0.300122 1.575195 -0.365883
+v -0.183133 0.500062 -0.337392
+v -0.300121 0.552646 -0.337392
+v -0.300121 0.552646 -0.365883
+v -0.183133 0.500062 -0.365883
+vt 0.9201 0.4239
+vt 0.9201 0.4892
+vt 0.9044 0.4892
+vt 0.9044 0.4239
+vt 0.1944 0.7581
+vt 0.2101 0.7581
+vt 0.2101 0.8279
+vt 0.1944 0.8279
+vt 0.2539 0.3060
+vt 0.2539 0.0160
+vt 0.3247 0.0160
+vt 0.3247 0.3060
+vt 0.4596 0.8350
+vt 0.7496 0.8350
+vt 0.7496 0.8507
+vt 0.4596 0.8507
+vt 0.2539 0.6281
+vt 0.2539 0.3381
+vt 0.3247 0.3381
+vt 0.3247 0.6281
+vt 0.7609 0.4251
+vt 0.7609 0.1508
+vt 0.7767 0.1508
+vt 0.7767 0.4251
+vt 0.2081 0.7222
+vt 0.2081 0.0160
+vt 0.2218 0.0199
+vt 0.2218 0.7261
+vt 0.0434 0.0160
+vt 0.0434 0.9840
+vt 0.0297 0.9840
+vt 0.0297 0.0160
+vt 0.0160 0.9840
+vt 0.0160 0.0160
+vt 0.1623 0.0183
+vt 0.1623 0.9082
+vt 0.1486 0.9060
+vt 0.1486 0.0160
+vt 0.1350 0.9082
+vt 0.1350 0.0183
+vt 0.0755 0.9840
+vt 0.0755 0.0160
+vt 0.0892 0.0160
+vt 0.0892 0.9840
+vt 0.7932 0.9090
+vt 0.7830 0.8984
+vt 0.7830 0.8836
+vt 0.7932 0.8734
+vt 0.8076 0.8737
+vt 0.8178 0.8844
+vt 0.8178 0.8991
+vt 0.8076 0.9093
+vt 0.1029 0.0160
+vt 0.1029 0.9840
+vt 0.1944 0.7261
+vt 0.1944 0.0199
+vt 0.2199 0.8822
+vt 0.2124 0.8943
+vt 0.2019 0.8972
+vt 0.1944 0.8892
+vt 0.1944 0.8750
+vt 0.2019 0.8629
+vt 0.2124 0.8600
+vt 0.2199 0.8680
+vt 0.8087 0.4251
+vt 0.8087 0.1508
+vt 0.8245 0.1508
+vt 0.8245 0.4251
+vt 0.4275 0.3381
+vt 0.4275 0.6281
+vt 0.3567 0.6281
+vt 0.3567 0.3381
+vt 0.6653 0.4408
+vt 0.6653 0.1508
+vt 0.6811 0.1508
+vt 0.6811 0.4408
+vt 0.5625 0.3060
+vt 0.5625 0.0160
+vt 0.6333 0.0160
+vt 0.6333 0.3060
+vt 0.7433 0.4931
+vt 0.7723 0.4931
+vt 0.7723 0.5268
+vt 0.7433 0.5268
+vt 0.7723 0.5650
+vt 0.7433 0.5650
+vt 0.7723 0.5975
+vt 0.7433 0.5975
+vt 0.9044 0.2874
+vt 0.9334 0.2874
+vt 0.9334 0.3198
+vt 0.9044 0.3198
+vt 0.9334 0.3581
+vt 0.9044 0.3581
+vt 0.9334 0.3918
+vt 0.9044 0.3918
+vt 0.8566 0.8329
+vt 0.8856 0.8329
+vt 0.8856 0.8708
+vt 0.8566 0.8708
+vt 0.8856 0.9063
+vt 0.8566 0.9063
+vt 0.9334 0.2553
+vt 0.9044 0.2553
+vt 0.9044 0.2220
+vt 0.9334 0.2220
+vt 0.9044 0.1838
+vt 0.9334 0.1838
+vt 0.7029 0.1188
+vt 0.6791 0.1050
+vt 0.6653 0.0812
+vt 0.6653 0.0536
+vt 0.6791 0.0298
+vt 0.7029 0.0160
+vt 0.7305 0.0160
+vt 0.7543 0.0298
+vt 0.7681 0.0536
+vt 0.7681 0.0812
+vt 0.7543 0.1050
+vt 0.7305 0.1188
+vt 0.9044 0.1508
+vt 0.9334 0.1508
+vt 0.7433 0.4729
+vt 0.7723 0.4729
+vt 0.4596 0.7507
+vt 0.4596 0.7124
+vt 0.4787 0.6793
+vt 0.5118 0.6602
+vt 0.5501 0.6602
+vt 0.5832 0.6793
+vt 0.6023 0.7124
+vt 0.6023 0.7507
+vt 0.5832 0.7838
+vt 0.5501 0.8029
+vt 0.5118 0.8029
+vt 0.4787 0.7838
+vt 0.3408 0.6733
+vt 0.3096 0.6733
+vt 0.3061 0.6602
+vt 0.3443 0.6602
+vt 0.2826 0.6889
+vt 0.2730 0.6793
+vt 0.2669 0.7160
+vt 0.2539 0.7124
+vt 0.2669 0.7472
+vt 0.2539 0.7507
+vt 0.2826 0.7742
+vt 0.2730 0.7838
+vt 0.3096 0.7898
+vt 0.3061 0.8029
+vt 0.3408 0.7898
+vt 0.3443 0.8029
+vt 0.3679 0.7742
+vt 0.3774 0.7838
+vt 0.3835 0.7472
+vt 0.3966 0.7507
+vt 0.3835 0.7160
+vt 0.3966 0.7124
+vt 0.3679 0.6889
+vt 0.3774 0.6793
+vt 0.8044 0.4994
+vt 0.8044 0.4729
+vt 0.8159 0.4729
+vt 0.8159 0.4994
+vt 0.9044 0.6442
+vt 0.9044 0.6187
+vt 0.9159 0.6187
+vt 0.9159 0.6442
+vt 0.8460 0.0160
+vt 0.8460 0.0429
+vt 0.8001 0.0429
+vt 0.8001 0.0160
+vt 0.2773 0.8808
+vt 0.2539 0.8808
+vt 0.2539 0.8350
+vt 0.2773 0.8350
+vt 0.7112 0.4729
+vt 0.7112 0.4994
+vt 0.6997 0.4994
+vt 0.6997 0.4729
+vt 0.9044 0.6715
+vt 0.9159 0.6715
+vt 0.8460 0.0742
+vt 0.8001 0.0742
+vt 0.3048 0.8808
+vt 0.3048 0.8350
+vt 0.7112 0.5306
+vt 0.6997 0.5306
+vt 0.8460 0.1014
+vt 0.8001 0.1014
+vt 0.3290 0.8808
+vt 0.3290 0.8693
+vt 0.7112 0.5582
+vt 0.6997 0.5582
+vt 0.7302 0.7561
+vt 0.7440 0.7323
+vt 0.7509 0.7341
+vt 0.7353 0.7611
+vt 0.7440 0.7047
+vt 0.7509 0.7029
+vt 0.7302 0.6809
+vt 0.7353 0.6758
+vt 0.7064 0.6671
+vt 0.7083 0.6602
+vt 0.6789 0.6671
+vt 0.6770 0.6602
+vt 0.6550 0.6809
+vt 0.6500 0.6758
+vt 0.6413 0.7047
+vt 0.6344 0.7029
+vt 0.6413 0.7323
+vt 0.6344 0.7341
+vt 0.6550 0.7561
+vt 0.6500 0.7611
+vt 0.6789 0.7699
+vt 0.6770 0.7768
+vt 0.7064 0.7699
+vt 0.7083 0.7768
+vt 0.9024 0.7257
+vt 0.9024 0.7491
+vt 0.8566 0.7491
+vt 0.8566 0.7257
+vt 0.7112 0.5746
+vt 0.6653 0.5746
+vt 0.8896 0.0160
+vt 0.8896 0.0400
+vt 0.8781 0.0400
+vt 0.8781 0.0160
+vt 0.8044 0.5306
+vt 0.8159 0.5306
+vt 0.9024 0.7766
+vt 0.8566 0.7766
+vt 0.8896 0.0675
+vt 0.8781 0.0675
+vt 0.8044 0.5582
+vt 0.8159 0.5582
+vt 0.9024 0.8008
+vt 0.8909 0.8008
+vt 0.8896 0.0912
+vt 0.8781 0.0912
+vt 0.3756 0.8659
+vt 0.3756 0.8350
+vt 0.3872 0.8350
+vt 0.4215 0.8659
+vt 0.3436 0.8808
+vt 0.3436 0.8693
+vt 0.3756 0.8949
+vt 0.4215 0.8949
+vt 0.7987 0.8413
+vt 0.7830 0.8413
+vt 0.7830 0.6602
+vt 0.7987 0.6602
+vt 0.4596 0.0160
+vt 0.5304 0.0160
+vt 0.5304 0.3060
+vt 0.4596 0.3060
+vt 0.8723 0.6936
+vt 0.8566 0.6936
+vt 0.8566 0.5125
+vt 0.8723 0.5125
+vt 0.8723 0.4572
+vt 0.8566 0.4572
+vt 0.9044 0.5213
+vt 0.9201 0.5213
+vt 0.9201 0.5866
+vt 0.9044 0.5866
+vt 0.5304 0.6281
+vt 0.4596 0.6281
+vt 0.4596 0.3381
+vt 0.5304 0.3381
+vt 0.3567 0.0160
+vt 0.4275 0.0160
+vt 0.4275 0.3060
+vt 0.3567 0.3060
+vt 0.8723 0.4251
+vt 0.8566 0.4251
+vt 0.8566 0.1508
+vt 0.8723 0.1508
+vt 0.7289 0.4251
+vt 0.7131 0.4251
+vt 0.7131 0.1508
+vt 0.7289 0.1508
+vt 0.6333 0.6281
+vt 0.5625 0.6281
+vt 0.5625 0.3381
+vt 0.6333 0.3381
+vn 0.4100 -0.9121 0.0000
+vn -0.4100 0.9121 0.0000
+vn 0.0000 0.0000 -1.0000
+vn 0.9121 0.4100 0.0000
+vn 0.0000 0.0000 1.0000
+vn -0.9121 -0.4100 0.0000
+vn -0.9239 0.0000 -0.3827
+vn -0.3827 0.0000 -0.9239
+vn 0.3827 0.0000 -0.9239
+vn 0.9239 0.0000 -0.3827
+vn 0.9239 0.0000 0.3827
+vn 0.3827 0.0000 0.9239
+vn 0.0000 1.0000 0.0000
+vn -0.3827 0.0000 0.9239
+vn -0.9239 0.0000 0.3827
+vn 0.0000 -1.0000 0.0000
+vn -0.9659 -0.2588 0.0000
+vn -0.7071 -0.7071 0.0000
+vn -0.2588 -0.9659 -0.0000
+vn 0.2588 -0.9659 0.0000
+vn 0.7071 -0.7071 0.0000
+vn 0.9659 -0.2588 0.0000
+vn 0.9659 0.2588 0.0000
+vn 0.7071 0.7071 0.0000
+vn 0.2588 0.9659 0.0000
+vn -0.2588 0.9659 -0.0000
+vn -0.7071 0.7071 0.0000
+vn -0.9659 0.2588 0.0000
+vn 0.1966 -0.7339 -0.6502
+vn -0.1967 -0.7339 -0.6502
+vn -0.9121 0.4100 0.0000
+vn 0.4100 0.9121 0.0000
+vn -0.4100 -0.9121 0.0000
+vn 0.9121 -0.4100 -0.0000
+s off
+f 1/1/1 2/2/1 3/3/1 4/4/1
+f 5/5/2 8/6/2 7/7/2 6/8/2
+f 27/9/3 5/10/3 6/11/3 29/12/3
+f 29/13/4 6/14/4 7/15/4 31/16/4
+f 31/17/5 7/18/5 8/19/5 33/20/5
+f 26/21/6 1/22/6 4/23/6 32/24/6
+f 9/25/7 10/26/7 12/27/7 11/28/7
+f 11/29/8 12/30/8 14/31/8 13/32/8
+f 13/32/9 14/31/9 16/33/9 15/34/9
+f 15/35/10 16/36/10 18/37/10 17/38/10
+f 17/38/11 18/37/11 20/39/11 19/40/11
+f 19/41/12 20/42/12 22/43/12 21/44/12
+f 12/45/13 10/46/13 24/47/13 22/48/13 20/49/13 18/50/13 16/51/13 14/52/13
+f 21/44/14 22/43/14 24/53/14 23/54/14
+f 23/55/15 24/56/15 10/26/15 9/25/15
+f 9/57/16 11/58/16 13/59/16 15/60/16 17/61/16 19/62/16 21/63/16 23/64/16
+f 5/65/6 27/66/6 33/67/6 8/68/6
+f 3/69/5 30/70/5 32/71/5 4/72/5
+f 2/73/4 28/74/4 30/75/4 3/76/4
+f 1/77/3 26/78/3 28/79/3 2/80/3
+f 34/81/17 59/82/17 58/83/17 36/84/17
+f 36/84/18 58/83/18 60/85/18 38/86/18
+f 38/86/19 60/85/19 61/87/19 40/88/19
+f 40/89/20 61/90/20 62/91/20 42/92/20
+f 42/92/21 62/91/21 63/93/21 44/94/21
+f 44/94/22 63/93/22 64/95/22 46/96/22
+f 46/97/23 64/98/23 65/99/23 48/100/23
+f 48/100/24 65/99/24 66/101/24 50/102/24
+f 50/103/25 66/104/25 67/105/25 52/106/25
+f 52/106/26 67/105/26 68/107/26 54/108/26
+f 37/109/3 35/110/3 57/111/3 55/112/3 53/113/3 51/114/3 49/115/3 47/116/3 45/117/3 43/118/3 41/119/3 39/120/3
+f 54/108/27 68/107/27 69/121/27 56/122/27
+f 56/123/28 69/124/28 59/82/28 34/81/28
+f 34/125/5 36/126/5 38/127/5 40/128/5 42/129/5 44/130/5 46/131/5 48/132/5 50/133/5 52/134/5 54/135/5 56/136/5
+f 71/137/3 70/138/3 58/139/3 59/140/3
+f 70/138/3 72/141/3 60/142/3 58/139/3
+f 72/141/3 73/143/3 61/144/3 60/142/3
+f 73/143/3 74/145/3 62/146/3 61/144/3
+f 74/145/3 75/147/3 63/148/3 62/146/3
+f 75/147/3 76/149/3 64/150/3 63/148/3
+f 76/149/3 77/151/3 65/152/3 64/150/3
+f 77/151/3 78/153/3 66/154/3 65/152/3
+f 78/153/3 79/155/3 67/156/3 66/154/3
+f 79/155/3 80/157/3 68/158/3 67/156/3
+f 80/157/3 81/159/3 69/160/3 68/158/3
+f 81/159/3 71/137/3 59/140/3 69/160/3
+f 74/161/20 73/162/20 85/163/20 86/164/20
+f 37/165/24 39/166/24 96/167/24 94/168/24
+f 81/169/27 80/170/27 92/171/27 93/172/27
+f 51/173/19 53/174/19 103/175/19 102/176/19
+f 73/177/19 72/178/19 84/179/19 85/180/19
+f 35/181/23 37/165/23 94/168/23 95/182/23
+f 80/170/26 79/183/26 91/184/26 92/171/26
+f 49/185/18 51/173/18 102/176/18 101/186/18
+f 72/178/18 70/187/18 82/188/18 84/179/18
+f 79/183/25 78/189/25 90/190/25 91/184/25
+f 47/191/17 49/185/17 101/186/17 100/192/17
+f 70/187/17 71/193/17 83/194/17 82/188/17
+f 95/195/3 94/196/3 82/197/3 83/198/3
+f 94/196/3 96/199/3 84/200/3 82/197/3
+f 96/199/3 97/201/3 85/202/3 84/200/3
+f 97/201/3 98/203/3 86/204/3 85/202/3
+f 98/203/3 99/205/3 87/206/3 86/204/3
+f 99/205/3 100/207/3 88/208/3 87/206/3
+f 100/207/29 101/209/29 89/210/29 88/208/29
+f 101/209/3 102/211/3 90/212/3 89/210/3
+f 102/211/3 103/213/3 91/214/3 90/212/3
+f 103/213/3 104/215/3 92/216/3 91/214/3
+f 104/215/3 105/217/3 93/218/3 92/216/3
+f 105/217/30 95/195/30 83/198/30 93/218/30
+f 53/219/20 55/220/20 104/221/20 103/222/20
+f 71/193/28 81/223/28 93/224/28 83/194/28
+f 39/225/25 41/226/25 97/227/25 96/228/25
+f 75/229/21 74/161/21 86/164/21 87/230/21
+f 55/220/21 57/231/21 105/232/21 104/221/21
+f 41/226/26 43/233/26 98/234/26 97/227/26
+f 76/235/22 75/229/22 87/230/22 88/236/22
+f 57/231/22 35/237/22 95/238/22 105/232/22
+f 43/233/27 45/239/27 99/240/27 98/234/27
+f 77/241/23 76/242/23 88/243/23 89/244/23
+f 45/245/28 47/191/28 100/192/28 99/246/28
+f 78/247/24 77/241/24 89/244/24 90/248/24
+f 120/249/31 119/250/31 109/251/31 111/252/31
+f 112/253/3 110/254/3 116/255/3 117/256/3
+f 110/257/31 108/258/31 115/259/31 116/260/31
+f 117/261/32 116/260/32 115/259/32 114/262/32
+f 121/263/33 118/264/33 119/265/33 120/266/33
+f 119/267/5 118/268/5 107/269/5 109/270/5
+f 108/271/5 106/272/5 114/273/5 115/274/5
+f 113/275/34 107/276/34 118/277/34 121/278/34
+f 117/279/34 114/280/34 106/281/34 112/282/34
+f 121/283/3 120/284/3 111/285/3 113/286/3
diff --git a/advtrains/models/advtrains_dtrack_bumper_st.b3d b/advtrains/models/advtrains_dtrack_bumper_st.b3d
new file mode 100644
index 0000000..a6d9745
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_bumper_st.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_bumper_st_30.b3d b/advtrains/models/advtrains_dtrack_bumper_st_30.b3d
new file mode 100644
index 0000000..5f5b3f4
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_bumper_st_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_bumper_st_45.b3d b/advtrains/models/advtrains_dtrack_bumper_st_45.b3d
new file mode 100644
index 0000000..f13ae75
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_bumper_st_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_bumper_st_60.b3d b/advtrains/models/advtrains_dtrack_bumper_st_60.b3d
new file mode 100644
index 0000000..59a2285
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_bumper_st_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_cr.b3d b/advtrains/models/advtrains_dtrack_cr.b3d
new file mode 100644
index 0000000..c708292
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_cr.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_cr_30.b3d b/advtrains/models/advtrains_dtrack_cr_30.b3d
new file mode 100644
index 0000000..7ca0bda
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_cr_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_cr_45.b3d b/advtrains/models/advtrains_dtrack_cr_45.b3d
new file mode 100644
index 0000000..b22ea0d
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_cr_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_cr_60.b3d b/advtrains/models/advtrains_dtrack_cr_60.b3d
new file mode 100644
index 0000000..e9b16d6
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_cr_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_st.b3d b/advtrains/models/advtrains_dtrack_st.b3d
new file mode 100644
index 0000000..c240416
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_st.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_st_30.b3d b/advtrains/models/advtrains_dtrack_st_30.b3d
new file mode 100644
index 0000000..fd77e66
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_st_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_st_45.b3d b/advtrains/models/advtrains_dtrack_st_45.b3d
new file mode 100644
index 0000000..af3afb1
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_st_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_st_60.b3d b/advtrains/models/advtrains_dtrack_st_60.b3d
new file mode 100644
index 0000000..6cb19e6
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_st_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlcr.b3d b/advtrains/models/advtrains_dtrack_swlcr.b3d
new file mode 100644
index 0000000..8b1ac0e
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlcr.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlcr_30.b3d b/advtrains/models/advtrains_dtrack_swlcr_30.b3d
new file mode 100644
index 0000000..6def7ab
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlcr_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlcr_45.b3d b/advtrains/models/advtrains_dtrack_swlcr_45.b3d
new file mode 100644
index 0000000..cc874ca
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlcr_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlcr_60.b3d b/advtrains/models/advtrains_dtrack_swlcr_60.b3d
new file mode 100644
index 0000000..1472a00
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlcr_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlst.b3d b/advtrains/models/advtrains_dtrack_swlst.b3d
new file mode 100644
index 0000000..ecdb326
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlst.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlst_30.b3d b/advtrains/models/advtrains_dtrack_swlst_30.b3d
new file mode 100644
index 0000000..fd6e91d
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlst_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlst_45.b3d b/advtrains/models/advtrains_dtrack_swlst_45.b3d
new file mode 100644
index 0000000..dae694e
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlst_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swlst_60.b3d b/advtrains/models/advtrains_dtrack_swlst_60.b3d
new file mode 100644
index 0000000..8f2b0e2
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swlst_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrcr.b3d b/advtrains/models/advtrains_dtrack_swrcr.b3d
new file mode 100644
index 0000000..4610826
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrcr.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrcr_30.b3d b/advtrains/models/advtrains_dtrack_swrcr_30.b3d
new file mode 100644
index 0000000..71b87f3
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrcr_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrcr_45.b3d b/advtrains/models/advtrains_dtrack_swrcr_45.b3d
new file mode 100644
index 0000000..5457972
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrcr_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrcr_60.b3d b/advtrains/models/advtrains_dtrack_swrcr_60.b3d
new file mode 100644
index 0000000..167a3ff
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrcr_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrst.b3d b/advtrains/models/advtrains_dtrack_swrst.b3d
new file mode 100644
index 0000000..69b6996
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrst.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrst_30.b3d b/advtrains/models/advtrains_dtrack_swrst_30.b3d
new file mode 100644
index 0000000..19ee483
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrst_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrst_45.b3d b/advtrains/models/advtrains_dtrack_swrst_45.b3d
new file mode 100644
index 0000000..70d051f
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrst_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_swrst_60.b3d b/advtrains/models/advtrains_dtrack_swrst_60.b3d
new file mode 100644
index 0000000..69a58fb
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_swrst_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_dtrack_vst1.obj b/advtrains/models/advtrains_dtrack_vst1.obj
new file mode 100644
index 0000000..33d9bf3
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst1.obj
@@ -0,0 +1,348 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst1_Cube.031
+v 0.753760 -0.500000 -0.500000
+v 0.753760 -0.500000 0.500000
+v 0.753760 -0.000000 0.500000
+v -0.753760 -0.500000 -0.500000
+v -0.753760 -0.500000 0.500000
+v -0.753760 -0.000000 0.500000
+v 0.329165 -0.196108 0.171599
+v 0.329165 -0.032047 0.171599
+v 0.329165 -0.367707 -0.171599
+v 0.329165 -0.203646 -0.171599
+v 0.462280 -0.196108 0.171599
+v 0.462280 -0.032047 0.171599
+v 0.462280 -0.367707 -0.171599
+v 0.462280 -0.203646 -0.171599
+v 0.412289 0.040000 0.500000
+v 0.412289 -0.460000 -0.500000
+v 0.560528 -0.460000 -0.500000
+v 0.560528 0.040000 0.500000
+v 0.474055 0.060000 0.500000
+v 0.474055 -0.440000 -0.500000
+v 0.498762 -0.440000 -0.500000
+v 0.498762 0.060000 0.500000
+v 0.474055 0.110000 0.500000
+v 0.474055 -0.390000 -0.500000
+v 0.498762 -0.390000 -0.500000
+v 0.498762 0.110000 0.500000
+v 0.529645 -0.385000 -0.500000
+v 0.529645 0.115000 0.500000
+v 0.443172 0.115000 0.500000
+v 0.443172 -0.385000 -0.500000
+v 0.529645 -0.360000 -0.500000
+v 0.529645 0.140000 0.500000
+v 0.443172 0.140000 0.500000
+v 0.443172 -0.360000 -0.500000
+v 0.741194 -0.042500 0.435000
+v -0.741194 -0.042500 0.435000
+v -0.741194 -0.102500 0.315000
+v 0.741194 -0.102500 0.315000
+v 0.741193 0.007500 0.435000
+v -0.741194 0.007500 0.435000
+v -0.741194 -0.052500 0.315000
+v 0.741194 -0.052500 0.315000
+v -0.011733 2.879186 0.500000
+v -0.011732 2.379186 -0.500001
+v -0.011733 2.900814 0.500000
+v -0.011732 2.400814 -0.500000
+v 0.011732 2.379186 -0.500000
+v 0.011732 2.879186 0.500000
+v 0.011732 2.400814 -0.500000
+v 0.011732 2.900814 0.500000
+v -0.412289 -0.460000 -0.500000
+v -0.412289 0.040000 0.500000
+v -0.560528 0.040000 0.500000
+v -0.560528 -0.460000 -0.500000
+v -0.474055 -0.440000 -0.500000
+v -0.474055 0.060000 0.500000
+v -0.498762 0.060000 0.500000
+v -0.498762 -0.440000 -0.500000
+v -0.474055 -0.390000 -0.500000
+v -0.474055 0.110000 0.500000
+v -0.498762 0.110000 0.500000
+v -0.498762 -0.390000 -0.500000
+v -0.529645 0.115000 0.500000
+v -0.529645 -0.385000 -0.500000
+v -0.443172 -0.385000 -0.500000
+v -0.443172 0.115000 0.500000
+v -0.529645 0.140000 0.500000
+v -0.529645 -0.360000 -0.500000
+v -0.443172 -0.360000 -0.500000
+v -0.443172 0.140000 0.500000
+v -0.741194 -0.477500 -0.435000
+v 0.741194 -0.477500 -0.435000
+v 0.741194 -0.417500 -0.315000
+v -0.741194 -0.417500 -0.315000
+v -0.741193 -0.427500 -0.435000
+v 0.741194 -0.427500 -0.435000
+v 0.741194 -0.367500 -0.315000
+v -0.741194 -0.367500 -0.315000
+v 0.741194 -0.166069 0.187862
+v -0.741194 -0.166069 0.187862
+v -0.741194 -0.226069 0.067862
+v 0.741194 -0.226069 0.067862
+v 0.741193 -0.116069 0.187862
+v -0.741194 -0.116069 0.187862
+v -0.741194 -0.176069 0.067862
+v 0.741194 -0.176069 0.067862
+v -0.741194 -0.353931 -0.187862
+v 0.741194 -0.353931 -0.187862
+v 0.741194 -0.293931 -0.067862
+v -0.741194 -0.293931 -0.067862
+v -0.741193 -0.303931 -0.187862
+v 0.741194 -0.303931 -0.187862
+v 0.741194 -0.243931 -0.067862
+v -0.741194 -0.243931 -0.067862
+v -0.329165 -0.196108 0.171599
+v -0.329165 -0.032047 0.171599
+v -0.329165 -0.367707 -0.171599
+v -0.329165 -0.203646 -0.171599
+v -0.462280 -0.196108 0.171599
+v -0.462280 -0.032047 0.171599
+v -0.462280 -0.367707 -0.171599
+v -0.462280 -0.203646 -0.171599
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 1.2064 -0.1190
+vt -0.2978 -0.1190
+vt -0.2978 -0.6179
+vt 1.2064 -0.6179
+vt -0.2924 0.9963
+vt -0.2924 -0.0015
+vt 1.2117 -0.0015
+vt 1.2117 0.9963
+vt 1.2064 0.9965
+vt 1.6526 0.1041
+vt -0.2978 0.9965
+vt -0.7440 0.1041
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.8944 -0.4472
+vn 1.0000 0.0000 0.0000
+vn -0.2782 0.8591 -0.4296
+vn 0.2782 0.8591 -0.4296
+vn 0.1433 -0.8852 0.4426
+vn -0.1433 -0.8852 0.4426
+vn 0.0000 -0.8944 0.4472
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst1_Cube.031_SlopeRailMaterial
+s off
+f 8/1/1 10/2/1 9/3/1 7/4/1
+f 10/2/2 14/5/2 13/6/2 9/7/2
+f 12/8/3 8/1/3 7/9/3 11/10/3
+f 12/8/4 14/5/4 10/2/4 8/1/4
+f 22/11/5 21/12/5 25/13/5 26/14/5
+f 15/15/6 19/16/6 20/17/6 16/18/6
+f 16/19/2 20/20/2 21/21/2 17/22/2
+f 17/23/7 21/12/7 22/11/7 18/24/7
+f 19/25/3 15/26/3 18/27/3 22/28/3
+f 26/29/3 23/30/3 19/25/3 22/28/3
+f 19/16/1 23/31/1 24/32/1 20/17/1
+f 21/21/2 20/20/2 24/33/2 25/34/2
+f 26/14/8 25/13/8 27/35/8 28/36/8
+f 24/32/9 23/31/9 29/37/9 30/38/9
+f 28/36/5 27/35/5 31/39/5 32/40/5
+f 30/38/1 29/37/1 33/41/1 34/42/1
+f 31/39/4 34/42/4 33/41/4 32/40/4
+f 23/30/3 26/29/3 28/43/3 32/40/3 33/41/3 29/44/3
+f 24/33/2 30/45/2 34/42/2 31/39/2 27/46/2 25/34/2
+f 39/47/4 42/48/4 41/49/4 40/50/4
+f 35/51/3 39/47/3 40/50/3 36/52/3
+f 36/53/1 40/50/1 41/49/1 37/54/1
+f 37/55/2 41/49/2 42/48/2 38/56/2
+f 39/47/5 35/57/5 38/58/5 42/48/5
+f 43/59/1 45/60/1 46/61/1 44/62/1
+f 47/63/5 49/64/5 50/65/5 48/66/5
+f 43/67/10 44/68/10 47/63/10 48/66/10
+f 45/60/4 50/65/4 49/64/4 46/61/4
+f 58/69/1 57/70/1 61/71/1 62/72/1
+f 51/73/7 55/74/7 56/75/7 52/76/7
+f 52/77/3 56/78/3 57/79/3 53/80/3
+f 53/81/6 57/70/6 58/69/6 54/82/6
+f 55/83/2 51/84/2 54/85/2 58/86/2
+f 62/87/2 59/88/2 55/83/2 58/86/2
+f 55/74/5 59/89/5 60/90/5 56/75/5
+f 57/79/3 56/78/3 60/91/3 61/92/3
+f 62/72/9 61/71/9 63/93/9 64/94/9
+f 60/90/8 59/89/8 65/95/8 66/96/8
+f 64/94/1 63/93/1 67/97/1 68/98/1
+f 66/96/5 65/95/5 69/99/5 70/100/5
+f 67/97/4 70/100/4 69/99/4 68/98/4
+f 59/88/2 62/87/2 64/101/2 68/98/2 69/99/2 65/102/2
+f 60/91/3 66/103/3 70/100/3 67/97/3 63/104/3 61/92/3
+f 75/105/4 78/106/4 77/107/4 76/108/4
+f 71/109/2 75/105/2 76/108/2 72/110/2
+f 72/111/5 76/108/5 77/107/5 73/112/5
+f 73/113/3 77/107/3 78/106/3 74/114/3
+f 75/105/1 71/115/1 74/116/1 78/106/1
+f 83/117/4 86/118/4 85/119/4 84/120/4
+f 79/121/3 83/117/3 84/120/3 80/122/3
+f 80/123/1 84/120/1 85/119/1 81/124/1
+f 81/125/2 85/119/2 86/118/2 82/126/2
+f 83/117/5 79/127/5 82/128/5 86/118/5
+f 91/129/4 94/130/4 93/131/4 92/132/4
+f 87/133/2 91/129/2 92/132/2 88/134/2
+f 88/135/5 92/132/5 93/131/5 89/136/5
+f 89/137/3 93/131/3 94/130/3 90/138/3
+f 91/129/1 87/139/1 90/140/1 94/130/1
+f 96/141/5 95/142/5 97/143/5 98/144/5
+f 98/144/2 97/145/2 101/146/2 102/147/2
+f 100/148/3 99/149/3 95/150/3 96/141/3
+f 100/148/4 96/141/4 98/144/4 102/147/4
+g dtrack_vst1_Cube.031_SlopeGravelMaterial
+f 3/151/3 6/152/3 5/153/3 2/154/3
+f 1/155/11 2/156/11 5/157/11 4/158/11
+f 1/159/5 3/151/5 2/160/5
+f 6/152/1 4/161/1 5/162/1
+f 4/161/4 6/152/4 3/151/4 1/159/4
diff --git a/advtrains/models/advtrains_dtrack_vst1_45.obj b/advtrains/models/advtrains_dtrack_vst1_45.obj
new file mode 100644
index 0000000..8ff8343
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst1_45.obj
@@ -0,0 +1,434 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst1_45_Cube.033
+v 0.111416 -0.221340 0.354093
+v 0.111416 -0.057279 0.354093
+v 0.354093 -0.342660 0.111416
+v 0.354093 -0.178600 0.111416
+v 0.205542 -0.221340 0.448220
+v 0.205542 -0.057279 0.448220
+v 0.448220 -0.342660 0.205543
+v 0.448220 -0.178600 0.205543
+v -0.208358 0.039814 0.791422
+v 0.791423 -0.460000 -0.208357
+v 0.896243 -0.460000 -0.103536
+v -0.103538 0.039814 0.896242
+v -0.164683 0.059814 0.835097
+v 0.835098 -0.440000 -0.164682
+v 0.852568 -0.440000 -0.147212
+v -0.147213 0.059814 0.852567
+v -0.164683 0.109814 0.835097
+v 0.835098 -0.390000 -0.164682
+v 0.852568 -0.390000 -0.147212
+v -0.147213 0.109814 0.852567
+v 0.874406 -0.385000 -0.125374
+v -0.125375 0.114814 0.874405
+v -0.186520 0.114814 0.813259
+v 0.813260 -0.385000 -0.186519
+v 0.874406 -0.360000 -0.125374
+v -0.125375 0.139814 0.874405
+v -0.186520 0.139814 0.813259
+v 0.813260 -0.360000 -0.186519
+v 0.476117 -0.186104 0.572089
+v -0.572088 -0.186104 -0.476117
+v -0.656942 -0.143684 -0.391265
+v 0.391264 -0.143684 0.656941
+v 0.476117 -0.236104 0.572089
+v -0.572088 -0.236104 -0.476117
+v -0.656941 -0.193684 -0.391265
+v 0.391264 -0.193684 0.656942
+v -0.508186 2.879001 0.491594
+v 0.491594 2.379186 -0.508186
+v -0.508186 2.900629 0.491593
+v 0.491594 2.400814 -0.508186
+v 0.508186 2.379186 -0.491594
+v -0.491594 2.879001 0.508186
+v 0.508186 2.400814 -0.491594
+v -0.491594 2.900629 0.508186
+v 0.208358 -0.460000 -0.791421
+v -0.791423 0.039814 0.208357
+v -0.896244 0.039814 0.103537
+v 0.103537 -0.460000 -0.896242
+v 0.164682 -0.440000 -0.835097
+v -0.835099 0.059815 0.164682
+v -0.852568 0.059814 0.147212
+v 0.147212 -0.440000 -0.852567
+v 0.164682 -0.390000 -0.835097
+v -0.835099 0.109815 0.164682
+v -0.852568 0.109814 0.147212
+v 0.147212 -0.390000 -0.852567
+v -0.874406 0.114814 0.125374
+v 0.125375 -0.385000 -0.874404
+v 0.186520 -0.385000 -0.813259
+v -0.813261 0.114815 0.186520
+v -0.874406 0.139814 0.125374
+v 0.125375 -0.360000 -0.874404
+v 0.186520 -0.360000 -0.813259
+v -0.813261 0.139814 0.186520
+v -0.230055 -0.407095 -0.818151
+v 0.818151 -0.407095 0.230055
+v 0.733298 -0.364675 0.314908
+v -0.314908 -0.364675 -0.733299
+v -0.230055 -0.357095 -0.818151
+v 0.818151 -0.357095 0.230056
+v 0.733298 -0.314675 0.314908
+v -0.314908 -0.314675 -0.733298
+v 0.314908 -0.105511 0.733298
+v -0.733298 -0.105511 -0.314908
+v -0.818151 -0.063091 -0.230056
+v 0.230055 -0.063091 0.818151
+v 0.314908 -0.155511 0.733298
+v -0.733298 -0.155511 -0.314908
+v -0.818151 -0.113091 -0.230055
+v 0.230055 -0.113091 0.818151
+v -0.391264 -0.326502 -0.656942
+v 0.656941 -0.326502 0.391265
+v 0.572089 -0.284082 0.476118
+v -0.476117 -0.284082 -0.572089
+v -0.391264 -0.276502 -0.656941
+v 0.656942 -0.276502 0.391265
+v 0.572088 -0.234082 0.476117
+v -0.476117 -0.234082 -0.572089
+v -0.354093 -0.221340 -0.111416
+v -0.354093 -0.057279 -0.111416
+v -0.111416 -0.342660 -0.354093
+v -0.111416 -0.178600 -0.354093
+v -0.448220 -0.221340 -0.205543
+v -0.448220 -0.057279 -0.205543
+v -0.205543 -0.342660 -0.448220
+v -0.205543 -0.178600 -0.448220
+v -0.069408 -0.487406 -0.978798
+v 0.978798 -0.487406 0.069409
+v 0.893945 -0.444986 0.154261
+v -0.154261 -0.444986 -0.893945
+v -0.069408 -0.437406 -0.978798
+v 0.978798 -0.437406 0.069409
+v 0.893945 -0.394986 0.154261
+v -0.154261 -0.394986 -0.893945
+v 0.069408 -0.032780 0.978798
+v -0.978798 -0.032780 -0.069409
+v -0.893945 -0.075200 -0.154261
+v 0.154261 -0.075200 0.893945
+v 0.069408 0.017220 0.978797
+v -0.978798 0.017220 -0.069409
+v -0.893945 -0.025200 -0.154261
+v 0.154261 -0.025200 0.893945
+v -0.029561 -0.500000 -1.029558
+v 1.029558 -0.500000 0.029561
+v -1.029558 -0.500000 -0.029561
+v -1.029558 0.000000 -0.029561
+v 0.029561 -0.500000 1.029558
+v 0.029561 0.000000 1.029558
+v -0.500000 0.000000 1.559119
+v -1.559119 0.000000 0.500000
+v -0.499999 0.000000 0.499999
+v -1.559120 -0.500000 0.500000
+v -0.500000 -0.500000 1.559119
+v -0.500003 -0.500000 0.500000
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.8837 0.9625
+vt 0.8524 0.0348
+vt 0.8837 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.0035
+vt 0.9588 0.0348
+vt 0.8837 0.0035
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.8837 0.9625
+vt 0.8524 0.0348
+vt 0.8837 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.0035
+vt 0.9588 0.0348
+vt 0.8837 0.0035
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt -0.3235 0.5003
+vt 0.6181 0.0296
+vt 0.6181 0.5003
+vt 0.6188 0.9848
+vt -0.3228 0.5141
+vt 0.6188 0.5141
+vt 0.0669 1.0031
+vt 0.0669 0.5045
+vt 0.0669 0.0059
+vt 1.1195 0.0059
+vt 1.1195 1.0031
+vt 0.1925 0.0071
+vt 0.1925 0.5057
+vt -0.3061 0.0071
+vt -0.4317 1.0031
+vt -0.4317 0.0059
+vt 1.1341 0.0071
+vt 1.1341 1.0043
+vt 0.1925 1.0043
+vt 1.1202 0.9906
+vt 0.4151 0.9906
+vt 0.4151 0.5199
+vt 1.1202 0.5199
+vt 0.4050 0.0285
+vt 1.1101 0.0285
+vt 1.1101 0.4992
+vt 0.4050 0.4992
+vt -0.3061 1.0043
+vt 1.1174 0.5141
+vt 1.1174 0.9848
+vt 1.1167 0.0296
+vt 1.1167 0.5003
+vn -0.7071 0.0000 -0.7071
+vn 0.7071 0.0000 -0.7071
+vn -0.7071 0.0000 0.7071
+vn 0.2357 0.9428 -0.2357
+vn 0.7071 0.0000 0.7071
+vn 0.0189 0.9017 -0.4319
+vn 0.4319 0.9017 -0.0189
+vn -0.1263 -0.9320 0.3397
+vn -0.3397 -0.9320 0.1263
+vn -0.2357 -0.9428 0.2357
+vn 0.0000 -1.0000 0.0000
+vn 0.0000 1.0000 0.0000
+vn 0.0000 0.0000 1.0000
+vn -1.0000 0.0000 0.0000
+g dtrack_vst1_45_Cube.033_SlopeRailMaterial
+s off
+f 2/1/1 4/2/1 3/3/1 1/4/1
+f 4/2/2 8/5/2 7/6/2 3/7/2
+f 6/8/3 2/1/3 1/9/3 5/10/3
+f 6/8/4 8/5/4 4/2/4 2/1/4
+f 16/11/5 15/12/5 19/13/5 20/14/5
+f 9/15/6 13/16/6 14/17/6 10/18/6
+f 10/19/2 14/20/2 15/21/2 11/22/2
+f 11/23/7 15/12/7 16/11/7 12/24/7
+f 13/25/3 9/26/3 12/27/3 16/28/3
+f 20/29/3 17/30/3 13/25/3 16/28/3
+f 13/16/1 17/31/1 18/32/1 14/17/1
+f 15/21/2 14/20/2 18/33/2 19/34/2
+f 20/14/8 19/13/8 21/35/8 22/36/8
+f 18/32/9 17/31/9 23/37/9 24/38/9
+f 22/36/5 21/35/5 25/39/5 26/40/5
+f 24/38/1 23/37/1 27/41/1 28/42/1
+f 25/39/4 28/42/4 27/41/4 26/40/4
+f 17/30/3 20/29/3 22/43/3 26/40/3 27/41/3 23/44/3
+f 18/33/2 24/45/2 28/42/2 25/39/2 21/46/2 19/34/2
+f 32/47/5 36/48/5 33/49/5 29/50/5
+f 34/51/2 30/52/2 29/50/2 33/53/2
+f 35/54/1 31/55/1 30/52/1 34/56/1
+f 36/57/3 32/47/3 31/55/3 35/58/3
+f 32/47/4 29/50/4 30/52/4 31/55/4
+f 37/59/1 39/60/1 40/61/1 38/62/1
+f 41/63/5 43/64/5 44/65/5 42/66/5
+f 37/67/10 38/68/10 41/63/10 42/66/10
+f 39/60/4 44/65/4 43/64/4 40/61/4
+f 52/69/1 51/70/1 55/71/1 56/72/1
+f 45/73/7 49/74/7 50/75/7 46/76/7
+f 46/77/3 50/78/3 51/79/3 47/80/3
+f 47/81/6 51/70/6 52/69/6 48/82/6
+f 49/83/2 45/84/2 48/85/2 52/86/2
+f 56/87/2 53/88/2 49/83/2 52/86/2
+f 49/74/5 53/89/5 54/90/5 50/75/5
+f 51/79/3 50/78/3 54/91/3 55/92/3
+f 56/72/9 55/71/9 57/93/9 58/94/9
+f 54/90/8 53/89/8 59/95/8 60/96/8
+f 58/94/1 57/93/1 61/97/1 62/98/1
+f 60/96/5 59/95/5 63/99/5 64/100/5
+f 61/97/4 64/100/4 63/99/4 62/98/4
+f 53/88/2 56/87/2 58/101/2 62/98/2 63/99/2 59/102/2
+f 54/91/3 60/103/3 64/100/3 61/97/3 57/104/3 55/92/3
+f 69/105/4 72/106/4 71/107/4 70/108/4
+f 65/109/2 69/105/2 70/108/2 66/110/2
+f 66/111/5 70/108/5 71/107/5 67/112/5
+f 67/113/3 71/107/3 72/106/3 68/114/3
+f 69/105/1 65/115/1 68/116/1 72/106/1
+f 76/117/5 80/118/5 77/119/5 73/120/5
+f 78/121/2 74/122/2 73/120/2 77/123/2
+f 79/124/1 75/125/1 74/122/1 78/126/1
+f 80/127/3 76/117/3 75/125/3 79/128/3
+f 76/117/4 73/120/4 74/122/4 75/125/4
+f 85/129/4 88/130/4 87/131/4 86/132/4
+f 81/133/2 85/129/2 86/132/2 82/134/2
+f 82/135/5 86/132/5 87/131/5 83/136/5
+f 83/137/3 87/131/3 88/130/3 84/138/3
+f 85/129/1 81/139/1 84/140/1 88/130/1
+f 90/141/5 89/142/5 91/143/5 92/144/5
+f 92/144/2 91/145/2 95/146/2 96/147/2
+f 94/148/3 93/149/3 89/150/3 90/141/3
+f 94/148/4 90/141/4 92/144/4 96/147/4
+f 101/151/4 104/152/4 103/153/4 102/154/4
+f 97/155/2 101/151/2 102/154/2 98/156/2
+f 98/157/5 102/154/5 103/153/5 99/158/5
+f 99/159/3 103/153/3 104/152/3 100/160/3
+f 101/151/1 97/161/1 100/162/1 104/152/1
+f 109/163/4 112/164/4 111/165/4 110/166/4
+f 105/167/3 109/163/3 110/166/3 106/168/3
+f 106/169/1 110/166/1 111/165/1 107/170/1
+f 107/171/2 111/165/2 112/164/2 108/172/2
+f 109/163/5 105/173/5 108/174/5 112/164/5
+g dtrack_vst1_45_Cube.033_SlopeGravelMaterial
+f 114/175/5 118/176/5 117/177/5
+f 116/178/1 113/179/1 115/180/1
+f 116/181/4 121/182/4 118/183/4 114/184/4 113/185/4
+f 115/186/11 124/187/11 122/188/11
+f 116/181/12 120/189/12 121/182/12
+f 118/183/12 121/182/12 119/190/12
+f 113/191/11 114/192/11 117/193/11 124/187/11 115/186/11
+f 121/194/13 120/195/13 122/196/13 124/197/13
+f 119/198/14 121/199/14 124/200/14 123/201/14
+f 123/202/11 124/187/11 117/193/11
+f 116/178/1 115/180/1 122/203/1 120/204/1
+f 117/177/5 118/176/5 119/205/5 123/206/5
diff --git a/advtrains/models/advtrains_dtrack_vst2.obj b/advtrains/models/advtrains_dtrack_vst2.obj
new file mode 100644
index 0000000..444ce42
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst2.obj
@@ -0,0 +1,372 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst2_Cube.032
+v 0.753760 0.000000 -0.500000
+v 0.753760 0.000000 0.500000
+v 0.753760 0.500000 0.500000
+v -0.753760 0.000000 -0.500000
+v -0.753760 0.000000 0.500000
+v -0.753760 0.500000 0.500000
+v 0.329165 0.303892 0.171599
+v 0.329165 0.467953 0.171599
+v 0.329165 0.132293 -0.171599
+v 0.329165 0.296354 -0.171599
+v 0.462280 0.303892 0.171599
+v 0.462280 0.467953 0.171599
+v 0.462280 0.132293 -0.171599
+v 0.462280 0.296354 -0.171599
+v 0.412289 0.540000 0.500000
+v 0.412289 0.040000 -0.500000
+v 0.560528 0.040000 -0.500000
+v 0.560528 0.540000 0.500000
+v 0.474055 0.560000 0.500000
+v 0.474055 0.060000 -0.500000
+v 0.498762 0.060000 -0.500000
+v 0.498762 0.560000 0.500000
+v 0.474055 0.610000 0.500000
+v 0.474055 0.110000 -0.500000
+v 0.498762 0.110000 -0.500000
+v 0.498762 0.610000 0.500000
+v 0.529645 0.115000 -0.500000
+v 0.529645 0.615000 0.500000
+v 0.443172 0.615000 0.500000
+v 0.443172 0.115000 -0.500000
+v 0.529645 0.140000 -0.500000
+v 0.529645 0.640000 0.500000
+v 0.443172 0.640000 0.500000
+v 0.443172 0.140000 -0.500000
+v 0.741194 0.457500 0.435000
+v -0.741194 0.457500 0.435000
+v -0.741194 0.397500 0.315000
+v 0.741194 0.397500 0.315000
+v 0.741193 0.507500 0.435000
+v -0.741194 0.507500 0.435000
+v -0.741194 0.447500 0.315000
+v 0.741194 0.447500 0.315000
+v -0.011733 3.379186 0.500000
+v -0.011732 2.879186 -0.500001
+v -0.011733 3.400814 0.500000
+v -0.011732 2.900814 -0.500000
+v 0.011732 2.879186 -0.500000
+v 0.011732 3.379186 0.500000
+v 0.011732 2.900814 -0.500000
+v 0.011732 3.400814 0.500000
+v -0.412289 0.040000 -0.500000
+v -0.412289 0.540000 0.500000
+v -0.560528 0.540000 0.500000
+v -0.560528 0.040000 -0.500000
+v -0.474055 0.060000 -0.500000
+v -0.474055 0.560000 0.500000
+v -0.498762 0.560000 0.500000
+v -0.498762 0.060000 -0.500000
+v -0.474055 0.110000 -0.500000
+v -0.474055 0.610000 0.500000
+v -0.498762 0.610000 0.500000
+v -0.498762 0.110000 -0.500000
+v -0.529645 0.615000 0.500000
+v -0.529645 0.115000 -0.500000
+v -0.443172 0.115000 -0.500000
+v -0.443172 0.615000 0.500000
+v -0.529645 0.640000 0.500000
+v -0.529645 0.140000 -0.500000
+v -0.443172 0.140000 -0.500000
+v -0.443172 0.640000 0.500000
+v -0.741194 0.022500 -0.435000
+v 0.741194 0.022500 -0.435000
+v 0.741194 0.082500 -0.315000
+v -0.741194 0.082500 -0.315000
+v -0.741193 0.072500 -0.435000
+v 0.741194 0.072500 -0.435000
+v 0.741194 0.132500 -0.315000
+v -0.741194 0.132500 -0.315000
+v 0.741194 0.333931 0.187862
+v -0.741194 0.333931 0.187862
+v -0.741194 0.273931 0.067862
+v 0.741194 0.273931 0.067862
+v 0.741193 0.383931 0.187862
+v -0.741194 0.383931 0.187862
+v -0.741194 0.323931 0.067862
+v 0.741194 0.323931 0.067862
+v -0.741194 0.146069 -0.187862
+v 0.741194 0.146069 -0.187862
+v 0.741194 0.206069 -0.067862
+v -0.741194 0.206069 -0.067862
+v -0.741193 0.196069 -0.187862
+v 0.741194 0.196069 -0.187862
+v 0.741194 0.256069 -0.067862
+v -0.741194 0.256069 -0.067862
+v -0.329165 0.303892 0.171599
+v -0.329165 0.467953 0.171599
+v -0.329165 0.132293 -0.171599
+v -0.329165 0.296354 -0.171599
+v -0.462280 0.303892 0.171599
+v -0.462280 0.467953 0.171599
+v -0.462280 0.132293 -0.171599
+v -0.462280 0.296354 -0.171599
+v 0.753760 -0.500000 0.500000
+v 0.753760 -0.500000 -0.500000
+v -0.753760 -0.500000 0.500000
+v -0.753760 -0.500000 -0.500000
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 1.2113 -0.1167
+vt -0.2846 -0.1167
+vt -0.2846 -0.6128
+vt 1.2113 -0.6128
+vt -0.2852 0.4989
+vt 1.2107 0.4989
+vt 1.2107 0.9950
+vt -0.2852 0.9950
+vt 1.2113 0.9927
+vt 1.6551 0.1052
+vt -0.2846 0.9927
+vt -0.7284 0.1052
+vt -0.2873 0.9952
+vt -0.2873 0.0029
+vt 1.2086 0.0029
+vt 1.2086 0.9952
+vt 0.7066 0.9945
+vt -0.2857 0.9945
+vt -0.2857 0.4984
+vt 0.7066 0.4984
+vt 1.2107 0.9918
+vt -0.2852 0.9918
+vt -0.2852 0.4956
+vt 1.2107 0.4956
+vt 0.7071 0.9996
+vt -0.2852 0.9996
+vt -0.2852 0.5034
+vt 0.7071 0.5034
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.8944 -0.4472
+vn 1.0000 0.0000 0.0000
+vn -0.2782 0.8591 -0.4296
+vn 0.2782 0.8591 -0.4296
+vn 0.1433 -0.8852 0.4426
+vn -0.1433 -0.8852 0.4426
+vn 0.0000 -0.8944 0.4472
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst2_Cube.032_SlopeRailMaterial
+s off
+f 8/1/1 10/2/1 9/3/1 7/4/1
+f 10/2/2 14/5/2 13/6/2 9/7/2
+f 12/8/3 8/1/3 7/9/3 11/10/3
+f 12/8/4 14/5/4 10/2/4 8/1/4
+f 22/11/5 21/12/5 25/13/5 26/14/5
+f 15/15/6 19/16/6 20/17/6 16/18/6
+f 16/19/2 20/20/2 21/21/2 17/22/2
+f 17/23/7 21/12/7 22/11/7 18/24/7
+f 19/25/3 15/26/3 18/27/3 22/28/3
+f 26/29/3 23/30/3 19/25/3 22/28/3
+f 19/16/1 23/31/1 24/32/1 20/17/1
+f 21/21/2 20/20/2 24/33/2 25/34/2
+f 26/14/8 25/13/8 27/35/8 28/36/8
+f 24/32/9 23/31/9 29/37/9 30/38/9
+f 28/36/5 27/35/5 31/39/5 32/40/5
+f 30/38/1 29/37/1 33/41/1 34/42/1
+f 31/39/4 34/42/4 33/41/4 32/40/4
+f 23/30/3 26/29/3 28/43/3 32/40/3 33/41/3 29/44/3
+f 24/33/2 30/45/2 34/42/2 31/39/2 27/46/2 25/34/2
+f 39/47/4 42/48/4 41/49/4 40/50/4
+f 35/51/3 39/47/3 40/50/3 36/52/3
+f 36/53/1 40/50/1 41/49/1 37/54/1
+f 37/55/2 41/49/2 42/48/2 38/56/2
+f 39/47/5 35/57/5 38/58/5 42/48/5
+f 43/59/1 45/60/1 46/61/1 44/62/1
+f 47/63/5 49/64/5 50/65/5 48/66/5
+f 43/67/10 44/68/10 47/63/10 48/66/10
+f 45/60/4 50/65/4 49/64/4 46/61/4
+f 58/69/1 57/70/1 61/71/1 62/72/1
+f 51/73/7 55/74/7 56/75/7 52/76/7
+f 52/77/3 56/78/3 57/79/3 53/80/3
+f 53/81/6 57/70/6 58/69/6 54/82/6
+f 55/83/2 51/84/2 54/85/2 58/86/2
+f 62/87/2 59/88/2 55/83/2 58/86/2
+f 55/74/5 59/89/5 60/90/5 56/75/5
+f 57/79/3 56/78/3 60/91/3 61/92/3
+f 62/72/9 61/71/9 63/93/9 64/94/9
+f 60/90/8 59/89/8 65/95/8 66/96/8
+f 64/94/1 63/93/1 67/97/1 68/98/1
+f 66/96/5 65/95/5 69/99/5 70/100/5
+f 67/97/4 70/100/4 69/99/4 68/98/4
+f 59/88/2 62/87/2 64/101/2 68/98/2 69/99/2 65/102/2
+f 60/91/3 66/103/3 70/100/3 67/97/3 63/104/3 61/92/3
+f 75/105/4 78/106/4 77/107/4 76/108/4
+f 71/109/2 75/105/2 76/108/2 72/110/2
+f 72/111/5 76/108/5 77/107/5 73/112/5
+f 73/113/3 77/107/3 78/106/3 74/114/3
+f 75/105/1 71/115/1 74/116/1 78/106/1
+f 83/117/4 86/118/4 85/119/4 84/120/4
+f 79/121/3 83/117/3 84/120/3 80/122/3
+f 80/123/1 84/120/1 85/119/1 81/124/1
+f 81/125/2 85/119/2 86/118/2 82/126/2
+f 83/117/5 79/127/5 82/128/5 86/118/5
+f 91/129/4 94/130/4 93/131/4 92/132/4
+f 87/133/2 91/129/2 92/132/2 88/134/2
+f 88/135/5 92/132/5 93/131/5 89/136/5
+f 89/137/3 93/131/3 94/130/3 90/138/3
+f 91/129/1 87/139/1 90/140/1 94/130/1
+f 96/141/5 95/142/5 97/143/5 98/144/5
+f 98/144/2 97/145/2 101/146/2 102/147/2
+f 100/148/3 99/149/3 95/150/3 96/141/3
+f 100/148/4 96/141/4 98/144/4 102/147/4
+g dtrack_vst2_Cube.032_SlopeGravelMaterial
+f 3/151/3 6/152/3 5/153/3 2/154/3
+f 4/155/2 1/156/2 104/157/2 106/158/2
+f 1/159/5 3/151/5 2/160/5
+f 6/152/1 4/161/1 5/162/1
+f 4/161/4 6/152/4 3/151/4 1/159/4
+f 104/163/11 103/164/11 105/165/11 106/166/11
+f 5/167/1 4/168/1 106/169/1 105/170/1
+f 2/171/3 5/172/3 105/173/3 103/174/3
+f 1/175/5 2/176/5 103/177/5 104/178/5
diff --git a/advtrains/models/advtrains_dtrack_vst2_45.obj b/advtrains/models/advtrains_dtrack_vst2_45.obj
new file mode 100644
index 0000000..5dc708b
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst2_45.obj
@@ -0,0 +1,462 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst2_45_Cube.035
+v 0.111416 0.278660 0.354093
+v 0.111416 0.442721 0.354093
+v 0.354093 0.157340 0.111416
+v 0.354093 0.321400 0.111416
+v 0.205542 0.278660 0.448220
+v 0.205542 0.442721 0.448220
+v 0.448220 0.157340 0.205543
+v 0.448220 0.321400 0.205543
+v -0.208358 0.539814 0.791422
+v 0.791423 0.040000 -0.208357
+v 0.896243 0.040000 -0.103536
+v -0.103538 0.539814 0.896242
+v -0.164683 0.559814 0.835097
+v 0.835098 0.060000 -0.164682
+v 0.852568 0.060000 -0.147212
+v -0.147213 0.559814 0.852567
+v -0.164683 0.609814 0.835097
+v 0.835098 0.110000 -0.164682
+v 0.852568 0.110000 -0.147212
+v -0.147213 0.609814 0.852567
+v 0.874406 0.115000 -0.125374
+v -0.125375 0.614814 0.874405
+v -0.186520 0.614814 0.813259
+v 0.813260 0.115000 -0.186519
+v 0.874406 0.140000 -0.125374
+v -0.125375 0.639814 0.874405
+v -0.186520 0.639814 0.813259
+v 0.813260 0.140000 -0.186519
+v 0.476117 0.313896 0.572089
+v -0.572088 0.313896 -0.476117
+v -0.656942 0.356316 -0.391265
+v 0.391264 0.356316 0.656941
+v 0.476117 0.263896 0.572089
+v -0.572088 0.263896 -0.476117
+v -0.656941 0.306316 -0.391265
+v 0.391264 0.306316 0.656942
+v -0.508186 3.379001 0.491594
+v 0.491594 2.879186 -0.508186
+v -0.508186 3.400629 0.491593
+v 0.491594 2.900814 -0.508186
+v 0.508186 2.879186 -0.491594
+v -0.491594 3.379001 0.508186
+v 0.508186 2.900814 -0.491594
+v -0.491594 3.400629 0.508186
+v 0.208358 0.040000 -0.791421
+v -0.791423 0.539814 0.208357
+v -0.896244 0.539814 0.103537
+v 0.103537 0.040000 -0.896242
+v 0.164682 0.060000 -0.835097
+v -0.835099 0.559815 0.164682
+v -0.852568 0.559814 0.147212
+v 0.147212 0.060000 -0.852567
+v 0.164682 0.110000 -0.835097
+v -0.835099 0.609815 0.164682
+v -0.852568 0.609814 0.147212
+v 0.147212 0.110000 -0.852567
+v -0.874406 0.614814 0.125374
+v 0.125375 0.115000 -0.874404
+v 0.186520 0.115000 -0.813259
+v -0.813261 0.614815 0.186520
+v -0.874406 0.639814 0.125374
+v 0.125375 0.140000 -0.874404
+v 0.186520 0.140000 -0.813259
+v -0.813261 0.639814 0.186520
+v -0.230055 0.092905 -0.818151
+v 0.818151 0.092905 0.230055
+v 0.733298 0.135325 0.314908
+v -0.314908 0.135325 -0.733299
+v -0.230055 0.142905 -0.818151
+v 0.818151 0.142905 0.230056
+v 0.733298 0.185325 0.314908
+v -0.314908 0.185325 -0.733298
+v 0.314908 0.394489 0.733298
+v -0.733298 0.394489 -0.314908
+v -0.818151 0.436909 -0.230056
+v 0.230055 0.436909 0.818151
+v 0.314908 0.344489 0.733298
+v -0.733298 0.344489 -0.314908
+v -0.818151 0.386909 -0.230055
+v 0.230055 0.386909 0.818151
+v -0.391264 0.173498 -0.656942
+v 0.656941 0.173498 0.391265
+v 0.572089 0.215918 0.476118
+v -0.476117 0.215918 -0.572089
+v -0.391264 0.223498 -0.656941
+v 0.656942 0.223498 0.391265
+v 0.572088 0.265918 0.476117
+v -0.476117 0.265918 -0.572089
+v -0.354093 0.278660 -0.111416
+v -0.354093 0.442721 -0.111416
+v -0.111416 0.157340 -0.354093
+v -0.111416 0.321400 -0.354093
+v -0.448220 0.278660 -0.205543
+v -0.448220 0.442721 -0.205543
+v -0.205543 0.157340 -0.448220
+v -0.205543 0.321400 -0.448220
+v -0.069408 0.012594 -0.978798
+v 0.978798 0.012594 0.069409
+v 0.893945 0.055014 0.154261
+v -0.154261 0.055014 -0.893945
+v -0.069408 0.062594 -0.978798
+v 0.978798 0.062594 0.069409
+v 0.893945 0.105014 0.154261
+v -0.154261 0.105014 -0.893945
+v 0.069408 0.467220 0.978798
+v -0.978798 0.467220 -0.069409
+v -0.893945 0.424800 -0.154261
+v 0.154261 0.424800 0.893945
+v 0.069408 0.517220 0.978797
+v -0.978798 0.517220 -0.069409
+v -0.893945 0.474800 -0.154261
+v 0.154261 0.474800 0.893945
+v -0.029561 -0.000000 -1.029558
+v 1.029558 0.000000 0.029561
+v -1.029558 -0.000000 -0.029561
+v -1.029558 0.500000 -0.029561
+v 0.029561 0.000000 1.029558
+v 0.029561 0.500000 1.029558
+v -0.500000 0.500000 1.559119
+v -1.559119 0.500000 0.500000
+v -0.499999 0.500000 0.499999
+v -1.559120 -0.000000 0.500000
+v -0.500000 0.000000 1.559119
+v -0.500003 0.000000 0.500000
+v 1.029558 -0.500000 0.029561
+v -0.029561 -0.500000 -1.029558
+v 0.029561 -0.500000 1.029558
+v -1.029558 -0.500000 -0.029561
+v -0.500003 -0.500000 0.500000
+v -0.500000 -0.500000 1.559119
+v -1.559120 -0.500000 0.500000
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.8837 0.9625
+vt 0.8524 0.0348
+vt 0.8837 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.0035
+vt 0.9588 0.0348
+vt 0.8837 0.0035
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.8837 0.9625
+vt 0.8524 0.0348
+vt 0.8837 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.0035
+vt 0.9588 0.0348
+vt 0.8837 0.0035
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt -0.2938 0.5095
+vt 0.6540 0.0358
+vt 0.6540 0.5096
+vt 0.6549 0.9852
+vt -0.2928 0.5114
+vt 0.6549 0.5114
+vt 0.1047 0.9953
+vt 0.1047 0.4934
+vt 0.1047 -0.0085
+vt 1.1643 -0.0085
+vt 1.1643 0.9953
+vt 1.1568 0.5114
+vt 0.6549 0.0377
+vt 1.1568 0.0377
+vt -0.3972 0.9953
+vt -0.3972 -0.0085
+vt 1.1431 0.5009
+vt 0.4334 0.5009
+vt 0.4334 0.0272
+vt 1.1431 0.0272
+vt 1.1431 0.9747
+vt 0.4334 0.9747
+vt 0.7633 0.9654
+vt 0.0536 0.9654
+vt 0.0536 0.4916
+vt 0.7633 0.4916
+vt 1.1559 0.5096
+vt 1.1559 0.9833
+vt 0.6540 0.9833
+vt 0.1053 -0.0176
+vt 0.1052 0.4843
+vt -0.3966 -0.0176
+vt 1.0530 -0.0176
+vt 1.0530 0.9861
+vt 0.1053 0.9861
+vt -0.3966 0.9861
+vt 0.0536 0.0179
+vt 0.7633 0.0179
+vt -0.2928 0.0377
+vt -0.2938 0.9833
+vt 0.0455 0.1273
+vt 1.0492 0.1273
+vt 1.0492 0.6011
+vt 0.0455 0.6011
+vt 1.1568 0.9852
+vt 1.1559 0.0358
+vn -0.7071 0.0000 -0.7071
+vn 0.7071 0.0000 -0.7071
+vn -0.7071 0.0000 0.7071
+vn 0.2357 0.9428 -0.2357
+vn 0.7071 0.0000 0.7071
+vn 0.0189 0.9017 -0.4319
+vn 0.4319 0.9017 -0.0189
+vn -0.1263 -0.9320 0.3397
+vn -0.3397 -0.9320 0.1263
+vn -0.2357 -0.9428 0.2357
+vn 0.0000 1.0000 0.0000
+vn -0.0000 0.0000 1.0000
+vn -1.0000 0.0000 0.0000
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst2_45_Cube.035_SlopeRailMaterial
+s off
+f 2/1/1 4/2/1 3/3/1 1/4/1
+f 4/2/2 8/5/2 7/6/2 3/7/2
+f 6/8/3 2/1/3 1/9/3 5/10/3
+f 6/8/4 8/5/4 4/2/4 2/1/4
+f 16/11/5 15/12/5 19/13/5 20/14/5
+f 9/15/6 13/16/6 14/17/6 10/18/6
+f 10/19/2 14/20/2 15/21/2 11/22/2
+f 11/23/7 15/12/7 16/11/7 12/24/7
+f 13/25/3 9/26/3 12/27/3 16/28/3
+f 20/29/3 17/30/3 13/25/3 16/28/3
+f 13/16/1 17/31/1 18/32/1 14/17/1
+f 15/21/2 14/20/2 18/33/2 19/34/2
+f 20/14/8 19/13/8 21/35/8 22/36/8
+f 18/32/9 17/31/9 23/37/9 24/38/9
+f 22/36/5 21/35/5 25/39/5 26/40/5
+f 24/38/1 23/37/1 27/41/1 28/42/1
+f 25/39/4 28/42/4 27/41/4 26/40/4
+f 17/30/3 20/29/3 22/43/3 26/40/3 27/41/3 23/44/3
+f 18/33/2 24/45/2 28/42/2 25/39/2 21/46/2 19/34/2
+f 32/47/5 36/48/5 33/49/5 29/50/5
+f 34/51/2 30/52/2 29/50/2 33/53/2
+f 35/54/1 31/55/1 30/52/1 34/56/1
+f 36/57/3 32/47/3 31/55/3 35/58/3
+f 32/47/4 29/50/4 30/52/4 31/55/4
+f 37/59/1 39/60/1 40/61/1 38/62/1
+f 41/63/5 43/64/5 44/65/5 42/66/5
+f 37/67/10 38/68/10 41/63/10 42/66/10
+f 39/60/4 44/65/4 43/64/4 40/61/4
+f 52/69/1 51/70/1 55/71/1 56/72/1
+f 45/73/7 49/74/7 50/75/7 46/76/7
+f 46/77/3 50/78/3 51/79/3 47/80/3
+f 47/81/6 51/70/6 52/69/6 48/82/6
+f 49/83/2 45/84/2 48/85/2 52/86/2
+f 56/87/2 53/88/2 49/83/2 52/86/2
+f 49/74/5 53/89/5 54/90/5 50/75/5
+f 51/79/3 50/78/3 54/91/3 55/92/3
+f 56/72/9 55/71/9 57/93/9 58/94/9
+f 54/90/8 53/89/8 59/95/8 60/96/8
+f 58/94/1 57/93/1 61/97/1 62/98/1
+f 60/96/5 59/95/5 63/99/5 64/100/5
+f 61/97/4 64/100/4 63/99/4 62/98/4
+f 53/88/2 56/87/2 58/101/2 62/98/2 63/99/2 59/102/2
+f 54/91/3 60/103/3 64/100/3 61/97/3 57/104/3 55/92/3
+f 69/105/4 72/106/4 71/107/4 70/108/4
+f 65/109/2 69/105/2 70/108/2 66/110/2
+f 66/111/5 70/108/5 71/107/5 67/112/5
+f 67/113/3 71/107/3 72/106/3 68/114/3
+f 69/105/1 65/115/1 68/116/1 72/106/1
+f 76/117/5 80/118/5 77/119/5 73/120/5
+f 78/121/2 74/122/2 73/120/2 77/123/2
+f 79/124/1 75/125/1 74/122/1 78/126/1
+f 80/127/3 76/117/3 75/125/3 79/128/3
+f 76/117/4 73/120/4 74/122/4 75/125/4
+f 85/129/4 88/130/4 87/131/4 86/132/4
+f 81/133/2 85/129/2 86/132/2 82/134/2
+f 82/135/5 86/132/5 87/131/5 83/136/5
+f 83/137/3 87/131/3 88/130/3 84/138/3
+f 85/129/1 81/139/1 84/140/1 88/130/1
+f 90/141/5 89/142/5 91/143/5 92/144/5
+f 92/144/2 91/145/2 95/146/2 96/147/2
+f 94/148/3 93/149/3 89/150/3 90/141/3
+f 94/148/4 90/141/4 92/144/4 96/147/4
+f 101/151/4 104/152/4 103/153/4 102/154/4
+f 97/155/2 101/151/2 102/154/2 98/156/2
+f 98/157/5 102/154/5 103/153/5 99/158/5
+f 99/159/3 103/153/3 104/152/3 100/160/3
+f 101/151/1 97/161/1 100/162/1 104/152/1
+f 109/163/4 112/164/4 111/165/4 110/166/4
+f 105/167/3 109/163/3 110/166/3 106/168/3
+f 106/169/1 110/166/1 111/165/1 107/170/1
+f 107/171/2 111/165/2 112/164/2 108/172/2
+f 109/163/5 105/173/5 108/174/5 112/164/5
+g dtrack_vst2_45_Cube.035_SlopeGravelMaterial
+f 114/175/5 118/176/5 117/177/5
+f 116/178/1 113/179/1 115/180/1
+f 116/181/4 121/182/4 118/183/4 114/184/4 113/185/4
+f 122/186/1 115/180/1 128/187/1 131/188/1
+f 116/181/11 120/189/11 121/182/11
+f 118/183/11 121/182/11 119/190/11
+f 124/191/12 122/192/12 131/193/12 129/194/12
+f 121/195/12 120/196/12 122/192/12 124/191/12
+f 119/197/13 121/198/13 124/199/13 123/200/13
+f 117/177/5 123/201/5 130/202/5 127/203/5
+f 128/204/14 129/205/14 131/206/14
+f 126/207/14 125/208/14 127/209/14 129/205/14 128/204/14
+f 130/210/14 129/205/14 127/209/14
+f 123/200/13 124/199/13 129/211/13 130/212/13
+f 115/180/1 113/179/1 126/213/1 128/187/1
+f 114/175/5 117/177/5 127/203/5 125/214/5
+f 113/215/2 114/216/2 125/217/2 126/218/2
+f 116/178/1 115/180/1 122/186/1 120/219/1
+f 117/177/5 118/176/5 119/220/5 123/201/5
diff --git a/advtrains/models/advtrains_dtrack_vst31.obj b/advtrains/models/advtrains_dtrack_vst31.obj
new file mode 100644
index 0000000..c4cb84f
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst31.obj
@@ -0,0 +1,348 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst31_Cube.000
+v 0.753760 -0.500000 -0.500000
+v 0.753760 -0.500000 0.500000
+v 0.753760 -0.166667 0.500000
+v -0.753760 -0.500000 -0.500000
+v -0.753760 -0.500000 0.500000
+v -0.753760 -0.166667 0.500000
+v 0.329165 -0.308041 0.171599
+v 0.329165 -0.143980 0.171599
+v 0.329165 -0.422440 -0.171599
+v 0.329165 -0.258379 -0.171599
+v 0.462280 -0.308041 0.171599
+v 0.462280 -0.143980 0.171599
+v 0.462280 -0.422440 -0.171599
+v 0.462280 -0.258379 -0.171599
+v 0.412289 -0.126667 0.500000
+v 0.412289 -0.460000 -0.500000
+v 0.560528 -0.460000 -0.500000
+v 0.560528 -0.126667 0.500000
+v 0.474055 -0.106667 0.500000
+v 0.474055 -0.440000 -0.500000
+v 0.498762 -0.440000 -0.500000
+v 0.498762 -0.106667 0.500000
+v 0.474055 -0.056667 0.500000
+v 0.474055 -0.390000 -0.500000
+v 0.498762 -0.390000 -0.500000
+v 0.498762 -0.056667 0.500000
+v 0.529645 -0.385000 -0.500000
+v 0.529645 -0.051667 0.500000
+v 0.443172 -0.051667 0.500000
+v 0.443172 -0.385000 -0.500000
+v 0.529645 -0.360000 -0.500000
+v 0.529645 -0.026667 0.500000
+v 0.443172 -0.026667 0.500000
+v 0.443172 -0.360000 -0.500000
+v 0.741194 -0.198333 0.435000
+v -0.741194 -0.198333 0.435000
+v -0.741194 -0.238333 0.315000
+v 0.741194 -0.238333 0.315000
+v 0.741193 -0.148333 0.435000
+v -0.741194 -0.148333 0.435000
+v -0.741194 -0.188333 0.315000
+v 0.741194 -0.188333 0.315000
+v -0.011733 2.712519 0.500000
+v -0.011732 2.379186 -0.500001
+v -0.011733 2.734147 0.500000
+v -0.011732 2.400814 -0.500000
+v 0.011732 2.379186 -0.500000
+v 0.011732 2.712519 0.500000
+v 0.011732 2.400814 -0.500000
+v 0.011732 2.734148 0.500000
+v -0.412289 -0.460000 -0.500000
+v -0.412289 -0.126667 0.500000
+v -0.560528 -0.126667 0.500000
+v -0.560528 -0.460000 -0.500000
+v -0.474055 -0.440000 -0.500000
+v -0.474055 -0.106666 0.500000
+v -0.498762 -0.106667 0.500000
+v -0.498762 -0.440000 -0.500000
+v -0.474055 -0.390000 -0.500000
+v -0.474055 -0.056666 0.500000
+v -0.498762 -0.056667 0.500000
+v -0.498762 -0.390000 -0.500000
+v -0.529645 -0.051667 0.500000
+v -0.529645 -0.385000 -0.500000
+v -0.443172 -0.385000 -0.500000
+v -0.443172 -0.051666 0.500000
+v -0.529645 -0.026667 0.500000
+v -0.529645 -0.360000 -0.500000
+v -0.443172 -0.360000 -0.500000
+v -0.443172 -0.026666 0.500000
+v -0.741194 -0.488333 -0.435000
+v 0.741194 -0.488333 -0.435000
+v 0.741194 -0.448333 -0.315000
+v -0.741194 -0.448333 -0.315000
+v -0.741193 -0.438333 -0.435000
+v 0.741194 -0.438333 -0.435000
+v 0.741194 -0.398333 -0.315000
+v -0.741194 -0.398333 -0.315000
+v 0.741194 -0.280713 0.187862
+v -0.741194 -0.280713 0.187862
+v -0.741194 -0.320713 0.067862
+v 0.741194 -0.320713 0.067862
+v 0.741193 -0.230713 0.187862
+v -0.741194 -0.230713 0.187862
+v -0.741194 -0.270713 0.067862
+v 0.741194 -0.270713 0.067862
+v -0.741194 -0.405954 -0.187862
+v 0.741194 -0.405954 -0.187862
+v 0.741194 -0.365954 -0.067862
+v -0.741194 -0.365954 -0.067862
+v -0.741193 -0.355954 -0.187862
+v 0.741194 -0.355954 -0.187862
+v 0.741194 -0.315954 -0.067862
+v -0.741194 -0.315954 -0.067862
+v -0.329165 -0.308041 0.171599
+v -0.329165 -0.143980 0.171599
+v -0.329165 -0.422440 -0.171599
+v -0.329165 -0.258379 -0.171599
+v -0.462280 -0.308041 0.171599
+v -0.462280 -0.143980 0.171599
+v -0.462280 -0.422440 -0.171599
+v -0.462280 -0.258379 -0.171599
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 1.1949 -0.0470
+vt -0.3152 -0.0470
+vt -0.3152 -0.3809
+vt 1.1949 -0.3809
+vt -0.3120 1.0015
+vt -0.3120 -0.0002
+vt 1.1981 -0.0002
+vt 1.1981 1.0015
+vt 1.1949 1.0089
+vt 1.5117 0.0586
+vt -0.3152 1.0089
+vt -0.6320 0.0586
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.9487 -0.3162
+vn 1.0000 0.0000 0.0000
+vn -0.2936 0.9069 -0.3023
+vn 0.2936 0.9069 -0.3023
+vn 0.1518 -0.9377 0.3126
+vn -0.1518 -0.9377 0.3126
+vn 0.0000 -0.9487 0.3162
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst31_Cube.000_SlopeRailMaterial
+s off
+f 8/1/1 10/2/1 9/3/1 7/4/1
+f 10/2/2 14/5/2 13/6/2 9/7/2
+f 12/8/3 8/1/3 7/9/3 11/10/3
+f 12/8/4 14/5/4 10/2/4 8/1/4
+f 22/11/5 21/12/5 25/13/5 26/14/5
+f 15/15/6 19/16/6 20/17/6 16/18/6
+f 16/19/2 20/20/2 21/21/2 17/22/2
+f 17/23/7 21/12/7 22/11/7 18/24/7
+f 19/25/3 15/26/3 18/27/3 22/28/3
+f 26/29/3 23/30/3 19/25/3 22/28/3
+f 19/16/1 23/31/1 24/32/1 20/17/1
+f 21/21/2 20/20/2 24/33/2 25/34/2
+f 26/14/8 25/13/8 27/35/8 28/36/8
+f 24/32/9 23/31/9 29/37/9 30/38/9
+f 28/36/5 27/35/5 31/39/5 32/40/5
+f 30/38/1 29/37/1 33/41/1 34/42/1
+f 31/39/4 34/42/4 33/41/4 32/40/4
+f 23/30/3 26/29/3 28/43/3 32/40/3 33/41/3 29/44/3
+f 24/33/2 30/45/2 34/42/2 31/39/2 27/46/2 25/34/2
+f 39/47/4 42/48/4 41/49/4 40/50/4
+f 35/51/3 39/47/3 40/50/3 36/52/3
+f 36/53/1 40/50/1 41/49/1 37/54/1
+f 37/55/2 41/49/2 42/48/2 38/56/2
+f 39/47/5 35/57/5 38/58/5 42/48/5
+f 43/59/1 45/60/1 46/61/1 44/62/1
+f 47/63/5 49/64/5 50/65/5 48/66/5
+f 43/67/10 44/68/10 47/63/10 48/66/10
+f 45/60/4 50/65/4 49/64/4 46/61/4
+f 58/69/1 57/70/1 61/71/1 62/72/1
+f 51/73/7 55/74/7 56/75/7 52/76/7
+f 52/77/3 56/78/3 57/79/3 53/80/3
+f 53/81/6 57/70/6 58/69/6 54/82/6
+f 55/83/2 51/84/2 54/85/2 58/86/2
+f 62/87/2 59/88/2 55/83/2 58/86/2
+f 55/74/5 59/89/5 60/90/5 56/75/5
+f 57/79/3 56/78/3 60/91/3 61/92/3
+f 62/72/9 61/71/9 63/93/9 64/94/9
+f 60/90/8 59/89/8 65/95/8 66/96/8
+f 64/94/1 63/93/1 67/97/1 68/98/1
+f 66/96/5 65/95/5 69/99/5 70/100/5
+f 67/97/4 70/100/4 69/99/4 68/98/4
+f 59/88/2 62/87/2 64/101/2 68/98/2 69/99/2 65/102/2
+f 60/91/3 66/103/3 70/100/3 67/97/3 63/104/3 61/92/3
+f 75/105/4 78/106/4 77/107/4 76/108/4
+f 71/109/2 75/105/2 76/108/2 72/110/2
+f 72/111/5 76/108/5 77/107/5 73/112/5
+f 73/113/3 77/107/3 78/106/3 74/114/3
+f 75/105/1 71/115/1 74/116/1 78/106/1
+f 83/117/4 86/118/4 85/119/4 84/120/4
+f 79/121/3 83/117/3 84/120/3 80/122/3
+f 80/123/1 84/120/1 85/119/1 81/124/1
+f 81/125/2 85/119/2 86/118/2 82/126/2
+f 83/117/5 79/127/5 82/128/5 86/118/5
+f 91/129/4 94/130/4 93/131/4 92/132/4
+f 87/133/2 91/129/2 92/132/2 88/134/2
+f 88/135/5 92/132/5 93/131/5 89/136/5
+f 89/137/3 93/131/3 94/130/3 90/138/3
+f 91/129/1 87/139/1 90/140/1 94/130/1
+f 96/141/5 95/142/5 97/143/5 98/144/5
+f 98/144/2 97/145/2 101/146/2 102/147/2
+f 100/148/3 99/149/3 95/150/3 96/141/3
+f 100/148/4 96/141/4 98/144/4 102/147/4
+g dtrack_vst31_Cube.000_SlopeGravelMaterial
+f 3/151/3 6/152/3 5/153/3 2/154/3
+f 1/155/11 2/156/11 5/157/11 4/158/11
+f 1/159/5 3/151/5 2/160/5
+f 6/152/1 4/161/1 5/162/1
+f 4/161/4 6/152/4 3/151/4 1/159/4
diff --git a/advtrains/models/advtrains_dtrack_vst32.obj b/advtrains/models/advtrains_dtrack_vst32.obj
new file mode 100644
index 0000000..297d46b
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst32.obj
@@ -0,0 +1,372 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst32_Cube.028
+v 0.753760 -0.166667 -0.500000
+v 0.753760 -0.166667 0.500000
+v 0.753760 0.166667 0.500000
+v -0.753760 -0.166667 -0.500000
+v -0.753760 -0.166667 0.500000
+v -0.753760 0.166667 0.500000
+v 0.329165 0.025292 0.171599
+v 0.329165 0.189353 0.171599
+v 0.329165 -0.089107 -0.171599
+v 0.329165 0.074954 -0.171599
+v 0.462280 0.025292 0.171599
+v 0.462280 0.189353 0.171599
+v 0.462280 -0.089107 -0.171599
+v 0.462280 0.074954 -0.171599
+v 0.412289 0.206667 0.500000
+v 0.412289 -0.126667 -0.500000
+v 0.560528 -0.126667 -0.500000
+v 0.560528 0.206667 0.500000
+v 0.474055 0.226667 0.500000
+v 0.474055 -0.106667 -0.500000
+v 0.498762 -0.106667 -0.500000
+v 0.498762 0.226667 0.500000
+v 0.474055 0.276667 0.500000
+v 0.474055 -0.056667 -0.500000
+v 0.498762 -0.056667 -0.500000
+v 0.498762 0.276667 0.500000
+v 0.529645 -0.051667 -0.500000
+v 0.529645 0.281667 0.500000
+v 0.443172 0.281667 0.500000
+v 0.443172 -0.051667 -0.500000
+v 0.529645 -0.026667 -0.500000
+v 0.529645 0.306667 0.500000
+v 0.443172 0.306667 0.500000
+v 0.443172 -0.026667 -0.500000
+v 0.741194 0.135000 0.435000
+v -0.741194 0.135000 0.435000
+v -0.741194 0.095000 0.315000
+v 0.741194 0.095000 0.315000
+v 0.741193 0.185000 0.435000
+v -0.741194 0.185000 0.435000
+v -0.741194 0.145000 0.315000
+v 0.741194 0.145000 0.315000
+v -0.011733 3.045853 0.500000
+v -0.011732 2.712519 -0.500001
+v -0.011733 3.067481 0.500000
+v -0.011732 2.734147 -0.500000
+v 0.011732 2.712519 -0.500000
+v 0.011732 3.045853 0.500000
+v 0.011732 2.734147 -0.500000
+v 0.011732 3.067481 0.500000
+v -0.412289 -0.126667 -0.500000
+v -0.412289 0.206667 0.500000
+v -0.560528 0.206667 0.500000
+v -0.560528 -0.126667 -0.500000
+v -0.474055 -0.106667 -0.500000
+v -0.474055 0.226667 0.500000
+v -0.498762 0.226667 0.500000
+v -0.498762 -0.106667 -0.500000
+v -0.474055 -0.056667 -0.500000
+v -0.474055 0.276667 0.500000
+v -0.498762 0.276667 0.500000
+v -0.498762 -0.056667 -0.500000
+v -0.529645 0.281667 0.500000
+v -0.529645 -0.051667 -0.500000
+v -0.443172 -0.051667 -0.500000
+v -0.443172 0.281667 0.500000
+v -0.529645 0.306667 0.500000
+v -0.529645 -0.026667 -0.500000
+v -0.443172 -0.026667 -0.500000
+v -0.443172 0.306667 0.500000
+v -0.741194 -0.155000 -0.435000
+v 0.741194 -0.155000 -0.435000
+v 0.741194 -0.115000 -0.315000
+v -0.741194 -0.115000 -0.315000
+v -0.741193 -0.105000 -0.435000
+v 0.741194 -0.105000 -0.435000
+v 0.741194 -0.065000 -0.315000
+v -0.741194 -0.065000 -0.315000
+v 0.741194 0.052621 0.187862
+v -0.741194 0.052621 0.187862
+v -0.741194 0.012621 0.067862
+v 0.741194 0.012621 0.067862
+v 0.741193 0.102621 0.187862
+v -0.741194 0.102621 0.187862
+v -0.741194 0.062621 0.067862
+v 0.741194 0.062621 0.067862
+v -0.741194 -0.072621 -0.187862
+v 0.741194 -0.072621 -0.187862
+v 0.741194 -0.032621 -0.067862
+v -0.741194 -0.032621 -0.067862
+v -0.741193 -0.022621 -0.187862
+v 0.741194 -0.022621 -0.187862
+v 0.741194 0.017379 -0.067862
+v -0.741194 0.017379 -0.067862
+v -0.329165 0.025293 0.171599
+v -0.329165 0.189353 0.171599
+v -0.329165 -0.089107 -0.171599
+v -0.329165 0.074954 -0.171599
+v -0.462280 0.025293 0.171599
+v -0.462280 0.189353 0.171599
+v -0.462280 -0.089107 -0.171599
+v -0.462280 0.074954 -0.171599
+v 0.753760 -0.500000 0.500000
+v 0.753760 -0.500000 -0.500000
+v -0.753760 -0.500000 0.500000
+v -0.753760 -0.500000 -0.500000
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 1.2169 -0.0660
+vt -0.3162 -0.0660
+vt -0.3162 -0.4050
+vt 1.2169 -0.4050
+vt -0.3102 0.3256
+vt 1.2229 0.3256
+vt 1.2229 0.6646
+vt -0.3102 0.6646
+vt 1.2169 1.0060
+vt 1.5385 0.0412
+vt -0.3162 1.0060
+vt -0.6378 0.0412
+vt -0.3132 1.0050
+vt -0.3132 -0.0119
+vt 1.2199 -0.0119
+vt 1.2199 1.0050
+vt -0.3075 0.6587
+vt 0.7095 0.6587
+vt 0.7095 0.9977
+vt -0.3075 0.9977
+vt 1.2229 1.0022
+vt -0.3102 1.0022
+vt -0.3102 0.6632
+vt 1.2229 0.6632
+vt 0.7067 0.6635
+vt -0.3102 0.6635
+vt -0.3102 0.3245
+vt 0.7067 0.3245
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.9487 -0.3162
+vn 1.0000 0.0000 0.0000
+vn -0.2936 0.9069 -0.3023
+vn 0.2936 0.9069 -0.3023
+vn 0.1518 -0.9377 0.3126
+vn -0.1518 -0.9377 0.3126
+vn 0.0000 -0.9487 0.3162
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst32_Cube.028_SlopeRailMaterial
+s off
+f 8/1/1 10/2/1 9/3/1 7/4/1
+f 10/2/2 14/5/2 13/6/2 9/7/2
+f 12/8/3 8/1/3 7/9/3 11/10/3
+f 12/8/4 14/5/4 10/2/4 8/1/4
+f 22/11/5 21/12/5 25/13/5 26/14/5
+f 15/15/6 19/16/6 20/17/6 16/18/6
+f 16/19/2 20/20/2 21/21/2 17/22/2
+f 17/23/7 21/12/7 22/11/7 18/24/7
+f 19/25/3 15/26/3 18/27/3 22/28/3
+f 26/29/3 23/30/3 19/25/3 22/28/3
+f 19/16/1 23/31/1 24/32/1 20/17/1
+f 21/21/2 20/20/2 24/33/2 25/34/2
+f 26/14/8 25/13/8 27/35/8 28/36/8
+f 24/32/9 23/31/9 29/37/9 30/38/9
+f 28/36/5 27/35/5 31/39/5 32/40/5
+f 30/38/1 29/37/1 33/41/1 34/42/1
+f 31/39/4 34/42/4 33/41/4 32/40/4
+f 23/30/3 26/29/3 28/43/3 32/40/3 33/41/3 29/44/3
+f 24/33/2 30/45/2 34/42/2 31/39/2 27/46/2 25/34/2
+f 39/47/4 42/48/4 41/49/4 40/50/4
+f 35/51/3 39/47/3 40/50/3 36/52/3
+f 36/53/1 40/50/1 41/49/1 37/54/1
+f 37/55/2 41/49/2 42/48/2 38/56/2
+f 39/47/5 35/57/5 38/58/5 42/48/5
+f 43/59/1 45/60/1 46/61/1 44/62/1
+f 47/63/5 49/64/5 50/65/5 48/66/5
+f 43/67/10 44/68/10 47/63/10 48/66/10
+f 45/60/4 50/65/4 49/64/4 46/61/4
+f 58/69/1 57/70/1 61/71/1 62/72/1
+f 51/73/7 55/74/7 56/75/7 52/76/7
+f 52/77/3 56/78/3 57/79/3 53/80/3
+f 53/81/6 57/70/6 58/69/6 54/82/6
+f 55/83/2 51/84/2 54/85/2 58/86/2
+f 62/87/2 59/88/2 55/83/2 58/86/2
+f 55/74/5 59/89/5 60/90/5 56/75/5
+f 57/79/3 56/78/3 60/91/3 61/92/3
+f 62/72/9 61/71/9 63/93/9 64/94/9
+f 60/90/8 59/89/8 65/95/8 66/96/8
+f 64/94/1 63/93/1 67/97/1 68/98/1
+f 66/96/5 65/95/5 69/99/5 70/100/5
+f 67/97/4 70/100/4 69/99/4 68/98/4
+f 59/88/2 62/87/2 64/101/2 68/98/2 69/99/2 65/102/2
+f 60/91/3 66/103/3 70/100/3 67/97/3 63/104/3 61/92/3
+f 75/105/4 78/106/4 77/107/4 76/108/4
+f 71/109/2 75/105/2 76/108/2 72/110/2
+f 72/111/5 76/108/5 77/107/5 73/112/5
+f 73/113/3 77/107/3 78/106/3 74/114/3
+f 75/105/1 71/115/1 74/116/1 78/106/1
+f 83/117/4 86/118/4 85/119/4 84/120/4
+f 79/121/3 83/117/3 84/120/3 80/122/3
+f 80/123/1 84/120/1 85/119/1 81/124/1
+f 81/125/2 85/119/2 86/118/2 82/126/2
+f 83/117/5 79/127/5 82/128/5 86/118/5
+f 91/129/4 94/130/4 93/131/4 92/132/4
+f 87/133/2 91/129/2 92/132/2 88/134/2
+f 88/135/5 92/132/5 93/131/5 89/136/5
+f 89/137/3 93/131/3 94/130/3 90/138/3
+f 91/129/1 87/139/1 90/140/1 94/130/1
+f 96/141/5 95/142/5 97/143/5 98/144/5
+f 98/144/2 97/145/2 101/146/2 102/147/2
+f 100/148/3 99/149/3 95/150/3 96/141/3
+f 100/148/4 96/141/4 98/144/4 102/147/4
+g dtrack_vst32_Cube.028_SlopeGravelMaterial
+f 3/151/3 6/152/3 5/153/3 2/154/3
+f 4/155/2 1/156/2 104/157/2 106/158/2
+f 1/159/5 3/151/5 2/160/5
+f 6/152/1 4/161/1 5/162/1
+f 4/161/4 6/152/4 3/151/4 1/159/4
+f 104/163/11 103/164/11 105/165/11 106/166/11
+f 5/167/1 4/168/1 106/169/1 105/170/1
+f 2/171/3 5/172/3 105/173/3 103/174/3
+f 1/175/5 2/176/5 103/177/5 104/178/5
diff --git a/advtrains/models/advtrains_dtrack_vst33.obj b/advtrains/models/advtrains_dtrack_vst33.obj
new file mode 100644
index 0000000..8cb5f04
--- /dev/null
+++ b/advtrains/models/advtrains_dtrack_vst33.obj
@@ -0,0 +1,388 @@
+# Blender v2.78 (sub 0) OBJ File: 'rail_redo.blend'
+# www.blender.org
+o dtrack_vst33_Cube.030
+v 0.753760 0.166667 -0.500000
+v 0.753760 0.166667 0.500000
+v 0.753760 0.500000 0.500000
+v -0.753760 0.166667 -0.500000
+v -0.753760 0.166667 0.500000
+v -0.753760 0.500000 0.500000
+v 0.329165 0.358626 0.171599
+v 0.329165 0.522686 0.171599
+v 0.329165 0.244227 -0.171599
+v 0.329165 0.408287 -0.171599
+v 0.462280 0.358626 0.171599
+v 0.462280 0.522686 0.171599
+v 0.462280 0.244227 -0.171599
+v 0.462280 0.408287 -0.171599
+v 0.412289 0.540000 0.500000
+v 0.412289 0.206667 -0.500000
+v 0.560528 0.206667 -0.500000
+v 0.560528 0.540000 0.500000
+v 0.474055 0.560000 0.500000
+v 0.474055 0.226667 -0.500000
+v 0.498762 0.226667 -0.500000
+v 0.498762 0.560000 0.500000
+v 0.474055 0.610000 0.500000
+v 0.474055 0.276667 -0.500000
+v 0.498762 0.276667 -0.500000
+v 0.498762 0.610000 0.500000
+v 0.529645 0.281667 -0.500000
+v 0.529645 0.615000 0.500000
+v 0.443172 0.615000 0.500000
+v 0.443172 0.281667 -0.500000
+v 0.529645 0.306667 -0.500000
+v 0.529645 0.640000 0.500000
+v 0.443172 0.640000 0.500000
+v 0.443172 0.306667 -0.500000
+v 0.741194 0.468333 0.435000
+v -0.741194 0.468333 0.435000
+v -0.741194 0.428333 0.315000
+v 0.741194 0.428333 0.315000
+v 0.741193 0.518333 0.435000
+v -0.741194 0.518333 0.435000
+v -0.741194 0.478333 0.315000
+v 0.741194 0.478333 0.315000
+v -0.011733 3.379186 0.500000
+v -0.011732 3.045852 -0.500001
+v -0.011733 3.400814 0.500000
+v -0.011732 3.067481 -0.500000
+v 0.011732 3.045853 -0.500000
+v 0.011732 3.379186 0.500000
+v 0.011732 3.067481 -0.500000
+v 0.011732 3.400814 0.500000
+v -0.412289 0.206667 -0.500000
+v -0.412289 0.540000 0.500000
+v -0.560528 0.540000 0.500000
+v -0.560528 0.206667 -0.500000
+v -0.474055 0.226667 -0.500000
+v -0.474055 0.560000 0.500000
+v -0.498762 0.560000 0.500000
+v -0.498762 0.226667 -0.500000
+v -0.474055 0.276667 -0.500000
+v -0.474055 0.610000 0.500000
+v -0.498762 0.610000 0.500000
+v -0.498762 0.276667 -0.500000
+v -0.529645 0.615000 0.500000
+v -0.529645 0.281667 -0.500000
+v -0.443172 0.281667 -0.500000
+v -0.443172 0.615000 0.500000
+v -0.529645 0.640000 0.500000
+v -0.529645 0.306667 -0.500000
+v -0.443172 0.306667 -0.500000
+v -0.443172 0.640000 0.500000
+v -0.741194 0.178333 -0.435000
+v 0.741194 0.178333 -0.435000
+v 0.741194 0.218333 -0.315000
+v -0.741194 0.218333 -0.315000
+v -0.741193 0.228333 -0.435000
+v 0.741194 0.228333 -0.435000
+v 0.741194 0.268333 -0.315000
+v -0.741194 0.268333 -0.315000
+v 0.741194 0.385954 0.187862
+v -0.741194 0.385954 0.187862
+v -0.741194 0.345954 0.067862
+v 0.741194 0.345954 0.067862
+v 0.741193 0.435954 0.187862
+v -0.741194 0.435954 0.187862
+v -0.741194 0.395954 0.067862
+v 0.741194 0.395954 0.067862
+v -0.741194 0.260713 -0.187862
+v 0.741194 0.260713 -0.187862
+v 0.741194 0.300713 -0.067862
+v -0.741194 0.300713 -0.067862
+v -0.741193 0.310713 -0.187862
+v 0.741194 0.310713 -0.187862
+v 0.741194 0.350713 -0.067862
+v -0.741194 0.350713 -0.067862
+v -0.329165 0.358626 0.171599
+v -0.329165 0.522687 0.171599
+v -0.329165 0.244227 -0.171599
+v -0.329165 0.408287 -0.171599
+v -0.462280 0.358626 0.171599
+v -0.462280 0.522686 0.171599
+v -0.462280 0.244227 -0.171599
+v -0.462280 0.408287 -0.171599
+v 0.753760 -0.166667 0.500000
+v 0.753760 -0.166667 -0.500000
+v -0.753760 -0.166667 0.500000
+v -0.753760 -0.166667 -0.500000
+v 0.753760 -0.500000 0.500000
+v 0.753760 -0.500000 -0.500000
+v -0.753760 -0.500000 0.500000
+v -0.753760 -0.500000 -0.500000
+vt 0.7427 0.1169
+vt 0.7427 0.3317
+vt 0.6400 0.3317
+vt 0.6400 0.1169
+vt 0.8260 0.3317
+vt 0.8260 0.4343
+vt 0.7427 0.4343
+vt 0.8260 0.1169
+vt 0.7427 0.0142
+vt 0.8260 0.0142
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.6554 0.3627
+vt 0.6690 0.3627
+vt 0.6690 0.9885
+vt 0.6554 0.9885
+vt 0.6972 0.9885
+vt 0.6837 0.9885
+vt 0.6837 0.3627
+vt 0.6972 0.3627
+vt 0.7119 0.3627
+vt 0.7119 0.9885
+vt 0.5691 0.2971
+vt 0.5691 0.9229
+vt 0.5378 0.9229
+vt 0.5378 0.2971
+vt 0.3413 0.2971
+vt 0.3819 0.2971
+vt 0.3819 0.9229
+vt 0.3413 0.9229
+vt 0.4291 0.9855
+vt 0.4678 0.9730
+vt 0.4833 0.9730
+vt 0.5219 0.9855
+vt 0.6097 0.9229
+vt 0.6097 0.2971
+vt 0.4678 0.2470
+vt 0.4291 0.2345
+vt 0.5219 0.2345
+vt 0.4833 0.2470
+vt 0.4833 0.2783
+vt 0.4678 0.2783
+vt 0.4132 0.2971
+vt 0.4132 0.9229
+vt 0.4678 0.9417
+vt 0.4833 0.9417
+vt 0.5182 0.9229
+vt 0.5182 0.2971
+vt 0.4328 0.2971
+vt 0.4328 0.9229
+vt 0.5026 0.9229
+vt 0.5026 0.2971
+vt 0.4485 0.2971
+vt 0.4485 0.9229
+vt 0.5026 0.2814
+vt 0.4485 0.2814
+vt 0.4485 0.9386
+vt 0.5026 0.9386
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.9588 0.9625
+vt 0.8837 0.9625
+vt 0.8837 0.0348
+vt 0.9588 0.0348
+vt 0.9901 0.9625
+vt 0.9901 0.0348
+vt 0.9588 0.0035
+vt 0.8837 0.0035
+vt 0.8524 0.0348
+vt 0.8524 0.9625
+vt 0.9588 0.9938
+vt 0.8837 0.9938
+vt 0.7427 0.1169
+vt 0.6400 0.1169
+vt 0.6400 0.3317
+vt 0.7427 0.3317
+vt 0.7427 0.4343
+vt 0.8260 0.4343
+vt 0.8260 0.3317
+vt 0.8260 0.1169
+vt 0.8260 0.0142
+vt 0.7427 0.0142
+vt 1.2190 -0.0648
+vt -0.3252 -0.0648
+vt -0.3252 -0.4063
+vt 1.2190 -0.4063
+vt 1.2222 1.0112
+vt -0.3220 1.0112
+vt -0.3220 0.6697
+vt 1.2222 0.6697
+vt 1.2190 1.0149
+vt 1.5430 0.0432
+vt -0.3252 1.0149
+vt -0.6491 0.0432
+vt -0.3220 0.3283
+vt 1.2222 0.3283
+vt -0.3240 0.3223
+vt 0.7003 0.3223
+vt 0.7003 0.6637
+vt -0.3240 0.6637
+vt 1.2202 1.0043
+vt -0.3240 1.0043
+vt -0.3240 0.6629
+vt 1.2202 0.6629
+vt 0.6998 1.0039
+vt -0.3245 1.0039
+vt -0.3245 0.6625
+vt 0.6998 0.6625
+vt -0.3246 1.0127
+vt -0.3246 -0.0117
+vt 1.2196 -0.0117
+vt 1.2196 1.0127
+vt 0.7003 1.0051
+vt -0.3240 1.0051
+vt -0.3240 0.3214
+vt 1.2202 0.3214
+vt -0.3245 0.3210
+vt 0.6998 0.3210
+vn -1.0000 0.0000 -0.0000
+vn 0.0000 0.0000 -1.0000
+vn -0.0000 0.0000 1.0000
+vn 0.0000 0.9487 -0.3162
+vn 1.0000 0.0000 0.0000
+vn -0.2936 0.9069 -0.3023
+vn 0.2936 0.9069 -0.3023
+vn 0.1518 -0.9377 0.3126
+vn -0.1518 -0.9377 0.3126
+vn 0.0000 -0.9487 0.3162
+vn 0.0000 -1.0000 0.0000
+g dtrack_vst33_Cube.030_SlopeRailMaterial
+s off
+f 8/1/1 10/2/1 9/3/1 7/4/1
+f 10/2/2 14/5/2 13/6/2 9/7/2
+f 12/8/3 8/1/3 7/9/3 11/10/3
+f 12/8/4 14/5/4 10/2/4 8/1/4
+f 22/11/5 21/12/5 25/13/5 26/14/5
+f 15/15/6 19/16/6 20/17/6 16/18/6
+f 16/19/2 20/20/2 21/21/2 17/22/2
+f 17/23/7 21/12/7 22/11/7 18/24/7
+f 19/25/3 15/26/3 18/27/3 22/28/3
+f 26/29/3 23/30/3 19/25/3 22/28/3
+f 19/16/1 23/31/1 24/32/1 20/17/1
+f 21/21/2 20/20/2 24/33/2 25/34/2
+f 26/14/8 25/13/8 27/35/8 28/36/8
+f 24/32/9 23/31/9 29/37/9 30/38/9
+f 28/36/5 27/35/5 31/39/5 32/40/5
+f 30/38/1 29/37/1 33/41/1 34/42/1
+f 31/39/4 34/42/4 33/41/4 32/40/4
+f 23/30/3 26/29/3 28/43/3 32/40/3 33/41/3 29/44/3
+f 24/33/2 30/45/2 34/42/2 31/39/2 27/46/2 25/34/2
+f 39/47/4 42/48/4 41/49/4 40/50/4
+f 35/51/3 39/47/3 40/50/3 36/52/3
+f 36/53/1 40/50/1 41/49/1 37/54/1
+f 37/55/2 41/49/2 42/48/2 38/56/2
+f 39/47/5 35/57/5 38/58/5 42/48/5
+f 43/59/1 45/60/1 46/61/1 44/62/1
+f 47/63/5 49/64/5 50/65/5 48/66/5
+f 43/67/10 44/68/10 47/63/10 48/66/10
+f 45/60/4 50/65/4 49/64/4 46/61/4
+f 58/69/1 57/70/1 61/71/1 62/72/1
+f 51/73/7 55/74/7 56/75/7 52/76/7
+f 52/77/3 56/78/3 57/79/3 53/80/3
+f 53/81/6 57/70/6 58/69/6 54/82/6
+f 55/83/2 51/84/2 54/85/2 58/86/2
+f 62/87/2 59/88/2 55/83/2 58/86/2
+f 55/74/5 59/89/5 60/90/5 56/75/5
+f 57/79/3 56/78/3 60/91/3 61/92/3
+f 62/72/9 61/71/9 63/93/9 64/94/9
+f 60/90/8 59/89/8 65/95/8 66/96/8
+f 64/94/1 63/93/1 67/97/1 68/98/1
+f 66/96/5 65/95/5 69/99/5 70/100/5
+f 67/97/4 70/100/4 69/99/4 68/98/4
+f 59/88/2 62/87/2 64/101/2 68/98/2 69/99/2 65/102/2
+f 60/91/3 66/103/3 70/100/3 67/97/3 63/104/3 61/92/3
+f 75/105/4 78/106/4 77/107/4 76/108/4
+f 71/109/2 75/105/2 76/108/2 72/110/2
+f 72/111/5 76/108/5 77/107/5 73/112/5
+f 73/113/3 77/107/3 78/106/3 74/114/3
+f 75/105/1 71/115/1 74/116/1 78/106/1
+f 83/117/4 86/118/4 85/119/4 84/120/4
+f 79/121/3 83/117/3 84/120/3 80/122/3
+f 80/123/1 84/120/1 85/119/1 81/124/1
+f 81/125/2 85/119/2 86/118/2 82/126/2
+f 83/117/5 79/127/5 82/128/5 86/118/5
+f 91/129/4 94/130/4 93/131/4 92/132/4
+f 87/133/2 91/129/2 92/132/2 88/134/2
+f 88/135/5 92/132/5 93/131/5 89/136/5
+f 89/137/3 93/131/3 94/130/3 90/138/3
+f 91/129/1 87/139/1 90/140/1 94/130/1
+f 96/141/5 95/142/5 97/143/5 98/144/5
+f 98/144/2 97/145/2 101/146/2 102/147/2
+f 100/148/3 99/149/3 95/150/3 96/141/3
+f 100/148/4 96/141/4 98/144/4 102/147/4
+g dtrack_vst33_Cube.030_SlopeGravelMaterial
+f 3/151/3 6/152/3 5/153/3 2/154/3
+f 4/155/2 1/156/2 104/157/2 106/158/2
+f 1/159/5 3/151/5 2/160/5
+f 6/152/1 4/161/1 5/162/1
+f 4/161/4 6/152/4 3/151/4 1/159/4
+f 106/158/2 104/157/2 108/163/2 110/164/2
+f 5/165/1 4/166/1 106/167/1 105/168/1
+f 2/169/3 5/170/3 105/171/3 103/172/3
+f 1/173/5 2/174/5 103/175/5 104/176/5
+f 108/177/11 107/178/11 109/179/11 110/180/11
+f 105/168/1 106/167/1 110/181/1 109/182/1
+f 103/172/3 105/171/3 109/183/3 107/184/3
+f 104/176/5 103/175/5 107/185/5 108/186/5
diff --git a/advtrains/models/advtrains_modernwagon.b3d b/advtrains/models/advtrains_modernwagon.b3d
new file mode 100644
index 0000000..aacddca
--- /dev/null
+++ b/advtrains/models/advtrains_modernwagon.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_platform_diag.b3d b/advtrains/models/advtrains_platform_diag.b3d
new file mode 100644
index 0000000..9464e63
--- /dev/null
+++ b/advtrains/models/advtrains_platform_diag.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_platform_diag_low.b3d b/advtrains/models/advtrains_platform_diag_low.b3d
new file mode 100644
index 0000000..5a8054b
--- /dev/null
+++ b/advtrains/models/advtrains_platform_diag_low.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_off.b3d b/advtrains/models/advtrains_retrosignal_off.b3d
new file mode 100644
index 0000000..3d231dd
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_off.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_off_30.b3d b/advtrains/models/advtrains_retrosignal_off_30.b3d
new file mode 100644
index 0000000..da258e1
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_off_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_off_45.b3d b/advtrains/models/advtrains_retrosignal_off_45.b3d
new file mode 100644
index 0000000..338224a
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_off_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_off_60.b3d b/advtrains/models/advtrains_retrosignal_off_60.b3d
new file mode 100644
index 0000000..c560ca1
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_off_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_on.b3d b/advtrains/models/advtrains_retrosignal_on.b3d
new file mode 100644
index 0000000..3d19439
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_on.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_on_30.b3d b/advtrains/models/advtrains_retrosignal_on_30.b3d
new file mode 100644
index 0000000..98f8a92
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_on_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_on_45.b3d b/advtrains/models/advtrains_retrosignal_on_45.b3d
new file mode 100644
index 0000000..414e121
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_on_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_retrosignal_on_60.b3d b/advtrains/models/advtrains_retrosignal_on_60.b3d
new file mode 100644
index 0000000..a51529a
--- /dev/null
+++ b/advtrains/models/advtrains_retrosignal_on_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal.b3d b/advtrains/models/advtrains_signal.b3d
new file mode 100644
index 0000000..7f69560
--- /dev/null
+++ b/advtrains/models/advtrains_signal.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_30.b3d b/advtrains/models/advtrains_signal_30.b3d
new file mode 100644
index 0000000..0b949a7
--- /dev/null
+++ b/advtrains/models/advtrains_signal_30.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_45.b3d b/advtrains/models/advtrains_signal_45.b3d
new file mode 100644
index 0000000..ccaebf4
--- /dev/null
+++ b/advtrains/models/advtrains_signal_45.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_60.b3d b/advtrains/models/advtrains_signal_60.b3d
new file mode 100644
index 0000000..cf41e6d
--- /dev/null
+++ b/advtrains/models/advtrains_signal_60.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_wall_l.b3d b/advtrains/models/advtrains_signal_wall_l.b3d
new file mode 100644
index 0000000..b1bcbcf
--- /dev/null
+++ b/advtrains/models/advtrains_signal_wall_l.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_wall_r.b3d b/advtrains/models/advtrains_signal_wall_r.b3d
new file mode 100644
index 0000000..cf26389
--- /dev/null
+++ b/advtrains/models/advtrains_signal_wall_r.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_signal_wall_t.b3d b/advtrains/models/advtrains_signal_wall_t.b3d
new file mode 100644
index 0000000..30e77f6
--- /dev/null
+++ b/advtrains/models/advtrains_signal_wall_t.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_track_cr.b3d b/advtrains/models/advtrains_track_cr.b3d
new file mode 100644
index 0000000..b0f5e4b
--- /dev/null
+++ b/advtrains/models/advtrains_track_cr.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_track_st.b3d b/advtrains/models/advtrains_track_st.b3d
new file mode 100644
index 0000000..10b5d90
--- /dev/null
+++ b/advtrains/models/advtrains_track_st.b3d
Binary files differ
diff --git a/advtrains/models/advtrains_track_st_45.b3d b/advtrains/models/advtrains_track_st_45.b3d
new file mode 100644
index 0000000..32505a1
--- /dev/null
+++ b/advtrains/models/advtrains_track_st_45.b3d
Binary files differ
diff --git a/advtrains/models/trackplane.b3d b/advtrains/models/trackplane.b3d
new file mode 100644
index 0000000..b4728c3
--- /dev/null
+++ b/advtrains/models/trackplane.b3d
Binary files differ
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
new file mode 100644
index 0000000..c664792
--- /dev/null
+++ b/advtrains/nodedb.lua
@@ -0,0 +1,391 @@
+--nodedb.lua
+--database of all nodes that have 'save_in_at_nodedb' field set to true in node definition
+
+
+--serialization format:
+--(2byte z) (2byte y) (2byte x) (2byte contentid)
+--contentid := (14bit nodeid, 2bit param2)
+
+local function int_to_bytes(i)
+ local x=i+32768--clip to positive integers
+ local cH = math.floor(x / 256) % 256;
+ local cL = math.floor(x ) % 256;
+ return(string.char(cH, cL));
+end
+local function bytes_to_int(bytes)
+ local t={string.byte(bytes,1,-1)}
+ local n =
+ t[1] * 256 +
+ t[2]
+ return n-32768
+end
+local function l2b(x)
+ return x%4
+end
+local function u14b(x)
+ return math.floor(x/4)
+end
+local ndb={}
+
+--local variables for performance
+local ndb_nodeids={}
+local ndb_nodes={}
+local ndb_ver
+
+local function ndbget(x,y,z)
+ local ny=ndb_nodes[y]
+ if ny then
+ local nx=ny[x]
+ if nx then
+ return nx[z]
+ end
+ end
+ return nil
+end
+local function ndbset(x,y,z,v)
+ if not ndb_nodes[y] then
+ ndb_nodes[y]={}
+ end
+ if not ndb_nodes[y][x] then
+ ndb_nodes[y][x]={}
+ end
+ ndb_nodes[y][x][z]=v
+end
+
+-- load/save
+
+local path_pre_v4=minetest.get_worldpath()..DIR_DELIM.."advtrains_ndb2"
+--load pre_v4 format
+--nodeids get loaded by advtrains init.lua and passed here
+function ndb.load_data_pre_v4(data)
+ atlog("nodedb: Loading pre v4 format")
+
+ ndb_nodeids = data and data.nodeids or {}
+ ndb_ver = data and data.ver or 0
+ if ndb_ver < 1 then
+ for k,v in pairs(ndb_nodeids) do
+ if v == "advtrains:dtrack_xing4590_st" then
+ cidDepr = k
+ elseif v == "advtrains:dtrack_xing90plusx_45l" then
+ cidNew = k
+ end
+ end
+ end
+ local file, err = io.open(path_pre_v4, "rb")
+ if not file then
+ atwarn("Couldn't load the node database: ", err or "Unknown Error")
+ else
+ -- Note: code duplication because of weird coordinate order in ndb2 format (z,y,x)
+ local cnt=0
+ local hst_z=file:read(2)
+ local hst_y=file:read(2)
+ local hst_x=file:read(2)
+ local cid=file:read(2)
+ while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==2 do
+ if (ndb_ver < 1 and cid == cidDepr) then
+ cid = cidNew
+ end
+ ndbset(bytes_to_int(hst_x), bytes_to_int(hst_y), bytes_to_int(hst_z), bytes_to_int(cid))
+ cnt=cnt+1
+ hst_z=file:read(2)
+ hst_y=file:read(2)
+ hst_x=file:read(2)
+ cid=file:read(2)
+ end
+ atlog("nodedb (ndb2 format): read", cnt, "nodes.")
+ file:close()
+ end
+ ndb_ver = 1
+end
+
+-- the new ndb file format is backported from cellworld, and stores the cids also in the ndb file.
+-- These functions have the form of a serialize_lib atomic load/save callback and are called from avt_save/avt_load.
+function ndb.load_callback(file)
+ -- read version
+ local vers_byte = file:read(1)
+ local version = string.byte(vers_byte)
+ if version~=1 then
+ file:close()
+ error("Doesn't support v4 nodedb file of version "..version)
+ end
+
+ -- read cid mappings
+ local nstr_byte = file:read(2)
+ local nstr = bytes_to_int(nstr_byte)
+ for i = 1,nstr do
+ local stid_byte = file:read(2)
+ local stid = bytes_to_int(stid_byte)
+ local stna = file:read("*l")
+ --atdebug("content id:", stid, "->", stna)
+ ndb_nodeids[stid] = stna
+ end
+ atlog("[nodedb] read", nstr, "node content ids.")
+
+ -- read nodes
+ local cnt=0
+ local hst_x=file:read(2)
+ local hst_y=file:read(2)
+ local hst_z=file:read(2)
+ local cid=file:read(2)
+ local cidi
+ while hst_z and hst_y and hst_x and cid and #hst_z==2 and #hst_y==2 and #hst_x==2 and #cid==2 do
+ cidi = bytes_to_int(cid)
+ -- prevent file corruption already here
+ if not ndb_nodeids[u14b(cidi)] then
+ -- clear the ndb data, to reinitialize it
+ -- in strict loading mode, doesn't matter as starting will be interrupted anyway
+ ndb_nodeids = {}
+ ndb_nodes = {}
+ error("NDB file is corrupted (found entry with invalid cid)")
+ end
+ ndbset(bytes_to_int(hst_x), bytes_to_int(hst_y), bytes_to_int(hst_z), cidi)
+ cnt=cnt+1
+ hst_x=file:read(2)
+ hst_y=file:read(2)
+ hst_z=file:read(2)
+ cid=file:read(2)
+ end
+ atlog("[nodedb] read", cnt, "nodes.")
+ file:close()
+end
+
+--save
+function ndb.save_callback(data, file)
+ --atdebug("storing ndb...")
+ -- write version
+ file:write(string.char(1))
+
+ -- how many cid entries
+ local cnt = 0
+ for _,_ in pairs(ndb_nodeids) do
+ cnt = cnt + 1
+ end
+ -- write cids
+ local nstr = 0
+ file:write(int_to_bytes(cnt))
+ for stid,stna in pairs(ndb_nodeids) do
+ file:write(int_to_bytes(stid))
+ file:write(stna)
+ file:write("\n")
+ nstr = nstr+1
+ end
+ --atdebug("saved cids count ", nstr)
+
+ -- write entries
+ local cnt = 0
+ for y, ny in pairs(ndb_nodes) do
+ for x, nx in pairs(ny) do
+ for z, cid in pairs(nx) do
+ file:write(int_to_bytes(x))
+ file:write(int_to_bytes(y))
+ file:write(int_to_bytes(z))
+ file:write(int_to_bytes(cid))
+ cnt=cnt+1
+ end
+ end
+ end
+ --atdebug("saved nodes count ", cnt)
+ file:close()
+end
+
+
+
+--function to get node. track database is not helpful here.
+function ndb.get_node_or_nil(pos)
+ -- FIX for bug found on linuxworks server:
+ -- a loaded node might get read before the LBM has updated its state, resulting in wrongly set signals and switches
+ -- -> Using the saved node prioritarily.
+ local node = ndb.get_node_raw(pos)
+ if node then
+ return node
+ else
+ --try reading the node from the map
+ return minetest.get_node_or_nil(pos)
+ end
+end
+function ndb.get_node(pos)
+ local n=ndb.get_node_or_nil(pos)
+ if not n then
+ return {name="ignore", param2=0}
+ end
+ return n
+end
+function ndb.get_node_raw(pos)
+ local cid=ndbget(pos.x, pos.y, pos.z)
+ if cid then
+ local nodeid = ndb_nodeids[u14b(cid)]
+ if nodeid then
+ return {name=nodeid, param2 = l2b(cid)}
+ end
+ end
+ return nil
+end
+
+
+function ndb.swap_node(pos, node, no_inval)
+ if advtrains.is_node_loaded(pos) then
+ minetest.swap_node(pos, node)
+ end
+ ndb.update(pos, node)
+end
+
+function ndb.update(pos, pnode)
+ local node = pnode or minetest.get_node_or_nil(pos)
+ if not node or node.name=="ignore" then return end
+ if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].groups.save_in_at_nodedb then
+ local nid
+ for tnid, nname in pairs(ndb_nodeids) do
+ if nname==node.name then
+ nid=tnid
+ end
+ end
+ if not nid then
+ nid=#ndb_nodeids+1
+ ndb_nodeids[nid]=node.name
+ end
+ local resid = (nid * 4) + (l2b(node.param2 or 0))
+ ndbset(pos.x, pos.y, pos.z, resid )
+ --atdebug("nodedb: updating node", pos, "stored nid",nid,"assigned",ndb_nodeids[nid],"resulting cid",resid)
+ advtrains.invalidate_all_paths_ahead(pos)
+ else
+ --at this position there is no longer a node that needs to be tracked.
+ --atdebug("nodedb: updating node", pos, "cleared")
+ ndbset(pos.x, pos.y, pos.z, nil)
+ end
+end
+
+function ndb.clear(pos)
+ ndbset(pos.x, pos.y, pos.z, nil)
+end
+
+
+--get_node with pseudoload. now we only need track data, so we can use the trackdb as second fallback
+--nothing new will be saved inside the trackdb.
+--returns:
+--true, conn1, conn2, rely1, rely2, railheight in case everything's right.
+--false if it's not a rail or the train does not drive on this rail, but it is loaded or
+--nil if the node is neither loaded nor in trackdb
+--the distraction between false and nil will be needed only in special cases.(train initpos)
+function advtrains.get_rail_info_at(pos, drives_on)
+ local rdp=advtrains.round_vector_floor_y(pos)
+
+ local node=ndb.get_node_or_nil(rdp)
+ if not node then return end
+
+ local nodename=node.name
+ if(not advtrains.is_track_and_drives_on(nodename, drives_on)) then
+ return false
+ end
+ local conns, railheight, tracktype=advtrains.get_track_connections(node.name, node.param2)
+
+ return true, conns, railheight
+end
+
+local IGNORE_WORLD = advtrains.IGNORE_WORLD
+
+ndb.run_lbm = function(pos, node)
+ local cid=ndbget(pos.x, pos.y, pos.z)
+ if cid then
+ --if in database, detect changes and apply.
+ local nodeid = ndb_nodeids[u14b(cid)]
+ local param2 = l2b(cid)
+ if not nodeid then
+ --something went wrong
+ atwarn("Node Database corruption, couldn't determine node to set at", pos)
+ ndb.update(pos, node)
+ else
+ if (nodeid~=node.name or param2~=node.param2) then
+ --atprint("nodedb: lbm replaced", pos, "with nodeid", nodeid, "param2", param2, "cid is", cid)
+ local newnode = {name=nodeid, param2 = param2}
+ minetest.swap_node(pos, newnode)
+ local ndef=minetest.registered_nodes[nodeid]
+ if ndef and ndef.advtrains and ndef.advtrains.on_updated_from_nodedb then
+ ndef.advtrains.on_updated_from_nodedb(pos, newnode)
+ end
+ return true
+ end
+ end
+ else
+ --if not in database, take it.
+ --atlog("Node Database:", pos, "was not found in the database, have you used worldedit?")
+ ndb.update(pos, node)
+ end
+ return false
+end
+
+
+minetest.register_lbm({
+ name = "advtrains:nodedb_on_load_update",
+ nodenames = {"group:save_in_at_nodedb"},
+ run_at_every_load = true,
+ run_on_every_load = true,
+ action = ndb.run_lbm,
+ interval=30,
+ chance=1,
+ })
+
+--used when restoring stuff after a crash
+ndb.restore_all = function()
+ --atlog("Updating the map from the nodedb, this may take a while")
+ local t1 = os.clock()
+ local cnt=0
+ local dcnt=0
+ for y, ny in pairs(ndb_nodes) do
+ for x, nx in pairs(ny) do
+ for z, _ in pairs(nx) do
+ local pos={x=x, y=y, z=z}
+ local node=minetest.get_node_or_nil(pos)
+ if node then
+ local ori_ndef=minetest.registered_nodes[node.name]
+ local ndbnode=ndb.get_node_raw(pos)
+ if (ori_ndef and ori_ndef.groups.save_in_at_nodedb) or IGNORE_WORLD then --check if this node has been worldedited, and don't replace then
+ if (ndbnode.name~=node.name or ndbnode.param2~=node.param2) then
+ minetest.swap_node(pos, ndbnode)
+ --atlog("Replaced",node.name,"@",pos,"with",ndbnode.name)
+ cnt=cnt+1
+ end
+ else
+ ndb.clear(pos)
+ dcnt=dcnt+1
+ --atlog("Found ghost node (former",ndbnode and ndbnode.name,") @",pos,"deleting")
+ end
+ end
+ end
+ end
+ end
+ local text="Restore node database: Replaced "..cnt.." nodes, removed "..dcnt.." ghost nodes. (took "..math.floor((os.clock()-t1) * 1000).."ms)"
+ atlog(text)
+ return text
+end
+
+minetest.register_on_dignode(function(pos, oldnode, digger)
+ ndb.clear(pos)
+end)
+
+function ndb.get_nodes()
+ return ndb_nodes
+end
+function ndb.get_nodeids()
+ return ndb_nodeids
+end
+
+
+advtrains.ndb=ndb
+
+local ptime=0
+
+minetest.register_chatcommand("at_sync_ndb",
+ {
+ params = "", -- Short parameter description
+ description = "Write node db back to map and find ghost nodes", -- Full description
+ privs = {train_operator=true},
+ func = function(name, param)
+ if os.time() < ptime+30 and not minetest.get_player_privs(name, "server") then
+ return false, "Please wait at least 30s from the previous execution of /at_restore_ndb!"
+ end
+ local text = ndb.restore_all()
+ ptime=os.time()
+ return true, text
+ end,
+ })
+
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
new file mode 100644
index 0000000..db39991
--- /dev/null
+++ b/advtrains/occupation.lua
@@ -0,0 +1,206 @@
+-- occupation.lua
+--[[
+Collects and manages positions where trains occupy and/or reserve/require space
+
+It turned out that, especially for the TSS, some more, even overlapping zones are required.
+Packing those into a data structure would just become a huge mess!
+Instead, this occupation system will store the path indices of positions in the corresponding.
+train's paths.
+So, the occupation is a reverse lookup of paths.
+Then, a callback system will handle changes in those indices, as follows:
+
+Whenever the train generates new path items (path_get/path_create), their counterpart indices will be filled in here.
+Whenever a path gets invalidated or path items are deleted, their index counterpart is erased from here.
+
+When a train needs to know whether a position is blocked by another train, it will (and is permitted to)
+query the train.index and train.end_index and compare them to the blocked position's index.
+
+Callback system for 3rd-party path checkers:
+advtrains.te_register_on_new_path(func(id, train))
+-- Called when a train's path is re-initalized, either when it was invalidated
+-- or the saves were just loaded
+-- It can be assumed that everything is in the state of when the last run
+-- of on_update was made, but all indices are shifted by an unknown amount.
+
+advtrains.te_register_on_update(func(id, train))
+-- Called each step and after a train moved, its length changed or some other event occured
+-- The path is unmodified, and train.index and train.end_index can be reliably
+-- queried for the new position and length of the train.
+-- note that this function might be called multiple times per step, and this
+-- function being called does not necessarily mean that something has changed.
+-- It is ensured that on_new_path callbacks are executed prior to these callbacks whenever
+-- an invalidation or a reload occured.
+
+advtrains.te_register_on_create(func(id, train))
+-- Called right after a train is created, right after the initial new_path callback
+advtrains.te_register_on_remove(func(id, train))
+-- Called right before a train is deleted
+
+
+All callbacks are allowed to save certain values inside the train table, but they must ensure that
+those are reinitialized in the on_new_path callback. The on_new_path callback must explicitly
+set ALL OF those values to nil or to a new updated value, and must not rely on their existence.
+
+]]--
+local o = {}
+
+local occ = {}
+local occ_chg = {}
+
+
+local function occget(p)
+ local t = occ[p.y]
+ if not t then
+ occ[p.y] = {}
+ t = occ[p.y]
+ end
+ local s = t
+ t = t[p.x]
+ if not t then
+ s[p.x] = {}
+ t = s[p.x]
+ end
+ return t[p.z]
+end
+local function occgetcreate(p)
+ local t = occ[p.y]
+ if not t then
+ occ[p.y] = {}
+ t = occ[p.y]
+ end
+ local s = t
+ t = t[p.x]
+ if not t then
+ s[p.x] = {}
+ t = s[p.x]
+ end
+ s = t
+ t = t[p.z]
+ if not t then
+ s[p.z] = {}
+ t = s[p.z]
+ end
+ return t
+end
+
+
+function o.set_item(train_id, pos, idx)
+ local t = occgetcreate(pos)
+ local i = 1
+ while t[i] do
+ if t[i]==train_id then
+ break
+ end
+ i = i + 2
+ end
+ t[i] = train_id
+ t[i+1] = idx
+end
+
+
+function o.clear_item(train_id, pos)
+ local t = occget(pos)
+ if not t then return end
+ local i = 1
+ local moving = false
+ while t[i] do
+ if t[i]==train_id then
+ if moving then
+ -- if, for some occasion, there should be a duplicate entry, erase this one too
+ atwarn("Duplicate occupation entry at",pos,"for train",train_id,":",t)
+ i = i - 2
+ end
+ moving = true
+ end
+ if moving then
+ t[i] = t[i+2]
+ t[i+1] = t[i+3]
+ end
+ i = i + 2
+ end
+end
+
+-- Checks whether some other train (apart from train_id) has it's 0 zone here
+function o.check_collision(pos, train_id)
+ local npos = advtrains.round_vector_floor_y(pos)
+ local t = occget(npos)
+ if not t then return end
+ local i = 1
+ while t[i] do
+ local ti = t[i]
+ if ti~=train_id then
+ local idx = t[i+1]
+ local train = advtrains.trains[ti]
+
+ --atdebug("checking train",t[i],"index",idx,"<>",train.index,train.end_index)
+ if train and idx >= train.end_index and idx <= train.index then
+ --atdebug("collides.")
+ return train -- return train it collided with so we can couple when shunting is enabled
+ end
+ end
+ i = i + 2
+ end
+ return false
+end
+
+-- Gets a mapping of train id's to indexes of trains that share this path item with this train
+-- The train itself will not be included.
+-- If the requested index position is off-track, returns {}.
+-- returns (table with train_id->index), position
+function o.get_occupations(train, index)
+ local ppos, ontrack = advtrains.path_get(train, index)
+ if not ontrack then
+ atlog("Train",train.id,"get_occupations requested off-track",index)
+ return {}, ppos
+ end
+ local pos = advtrains.round_vector_floor_y(ppos)
+ local t = occget(pos)
+ if not t then return {} end
+ local r = {}
+ local i = 1
+ local train_id = train.id
+ while t[i] do
+ if t[i]~=train_id then
+ r[t[i]] = t[i+1]
+ end
+ i = i + 2
+ end
+ return r, pos
+end
+-- Gets a mapping of train id's to indexes of trains that stand or drive over
+-- returns (table with train_id->index)
+function o.get_trains_at(ppos)
+ local pos = advtrains.round_vector_floor_y(ppos)
+ local t = occget(pos)
+ if not t then return {} end
+ local r = {}
+ local i = 1
+ while t[i] do
+ local train = advtrains.trains[t[i]]
+ local idx = t[i+1]
+ if train.end_index - 0.5 <= idx and idx <= train.index + 0.5 then
+ r[t[i]] = idx
+ end
+ i = i + 2
+ end
+ return r
+end
+
+-- Gets a mapping of train id's to indexes of trains that have a path
+-- generated over this node
+-- returns (table with train_id->index)
+function o.get_trains_over(ppos)
+ local pos = advtrains.round_vector_floor_y(ppos)
+ local t = occget(pos)
+ if not t then return {} end
+ local r = {}
+ local i = 1
+ while t[i] do
+ local idx = t[i+1]
+ r[t[i]] = idx
+ i = i + 2
+ end
+ return r
+end
+
+advtrains.occ = o
diff --git a/advtrains/p_mesecon_iface.lua b/advtrains/p_mesecon_iface.lua
new file mode 100644
index 0000000..0eef96a
--- /dev/null
+++ b/advtrains/p_mesecon_iface.lua
@@ -0,0 +1,58 @@
+-- p_mesecon_iface.lua
+-- Mesecons interface by overriding the switch
+
+if not mesecon then return end
+
+minetest.override_item("mesecons_switch:mesecon_switch_off", {
+ groups = {
+ dig_immediate=2,
+ save_in_at_nodedb=1,
+ },
+ on_rightclick = function (pos, node)
+ advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
+ mesecon.receptor_on(pos)
+ minetest.sound_play("mesecons_switch", {pos=pos})
+ end,
+ advtrains = {
+ getstate = "off",
+ setstate = function(pos, node, newstate)
+ if newstate=="on" then
+ advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_on", param2=node.param2})
+ if advtrains.is_node_loaded(pos) then
+ mesecon.receptor_on(pos)
+ end
+ end
+ end,
+ on_updated_from_nodedb = function(pos, node)
+ mesecon.receptor_off(pos)
+ end,
+ },
+})
+
+minetest.override_item("mesecons_switch:mesecon_switch_on", {
+ groups = {
+ dig_immediate=2,
+ save_in_at_nodedb=1,
+ not_in_creative_inventory=1,
+ },
+ on_rightclick = function (pos, node)
+ advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
+ mesecon.receptor_off(pos)
+ minetest.sound_play("mesecons_switch", {pos=pos})
+ end,
+ advtrains = {
+ getstate = "on",
+ setstate = function(pos, node, newstate)
+ if newstate=="off" then
+ advtrains.ndb.swap_node(pos, {name="mesecons_switch:mesecon_switch_off", param2=node.param2})
+ if advtrains.is_node_loaded(pos) then
+ mesecon.receptor_off(pos)
+ end
+ end
+ end,
+ fallback_state = "off",
+ on_updated_from_nodedb = function(pos, node)
+ mesecon.receptor_on(pos)
+ end,
+ },
+})
diff --git a/advtrains/passive.lua b/advtrains/passive.lua
new file mode 100644
index 0000000..fe4790c
--- /dev/null
+++ b/advtrains/passive.lua
@@ -0,0 +1,121 @@
+-- passive.lua
+-- API to passive components, as described in passive_api.txt of advtrains_luaautomation
+-- This has been moved to the advtrains core in turn with the interlocking system,
+-- to prevent a dependency on luaautomation.
+
+local deprecation_warned = {}
+
+function advtrains.getstate(parpos, pnode)
+ local pos
+ if atlatc then
+ pos = atlatc.pcnaming.resolve_pos(parpos)
+ else
+ pos = advtrains.round_vector_floor_y(parpos)
+ end
+ if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
+ debug.sethook()
+ error("Invalid position supplied to getstate")
+ end
+ local node=pnode or advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ local st
+ if ndef and ndef.advtrains and ndef.advtrains.getstate then
+ st=ndef.advtrains.getstate
+ elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
+ if not deprecation_warned[node.name] then
+ minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
+ end
+ st=ndef.luaautomation.getstate
+ else
+ return nil
+ end
+ if type(st)=="function" then
+ return st(pos, node)
+ else
+ return st
+ end
+end
+
+function advtrains.setstate(parpos, newstate, pnode)
+ local pos
+ if atlatc then
+ pos = atlatc.pcnaming.resolve_pos(parpos)
+ else
+ pos = advtrains.round_vector_floor_y(parpos)
+ end
+ if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
+ debug.sethook()
+ error("Invalid position supplied to getstate")
+ end
+ local node=pnode or advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ local st
+ if ndef and ndef.advtrains and ndef.advtrains.setstate then
+ st=ndef.advtrains.setstate
+ elseif ndef and ndef.luaautomation and ndef.luaautomation.setstate then
+ if not deprecation_warned[node.name] then
+ minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
+ end
+ st=ndef.luaautomation.setstate
+ else
+ return nil
+ end
+
+ if advtrains.get_train_at_pos(pos) then
+ return false
+ end
+
+ if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
+ return false
+ end
+
+ st(pos, node, newstate)
+ return true
+end
+
+function advtrains.is_passive(parpos, pnode)
+ local pos
+ if atlatc then
+ pos = atlatc.pcnaming.resolve_pos(parpos)
+ else
+ pos = advtrains.round_vector_floor_y(parpos)
+ end
+ if type(pos)~="table" or (not pos.x or not pos.y or not pos.z) then
+ debug.sethook()
+ error("Invalid position supplied to getstate")
+ end
+ local node=pnode or advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.getstate then
+ return true
+ elseif ndef and ndef.luaautomation and ndef.luaautomation.getstate then
+ if not deprecation_warned[node.name] then
+ minetest.log("warning", node.name.." uses deprecated definition of ATLATC functions in the 'luaautomation' field. Please move them to the 'advtrains' field!")
+ end
+ return true
+ else
+ return false
+ end
+end
+
+-- switches a node back to fallback state, if defined. Doesn't support pcnaming.
+function advtrains.set_fallback_state(pos, pnode)
+ local node=pnode or advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ local st
+ if ndef and ndef.advtrains and ndef.advtrains.setstate
+ and ndef.advtrains.fallback_state then
+ if advtrains.get_train_at_pos(pos) then
+ return false
+ end
+
+ if advtrains.interlocking and advtrains.interlocking.route.has_route_lock(minetest.pos_to_string(pos)) then
+ return false
+ end
+
+ ndef.advtrains.setstate(pos, node, ndef.advtrains.fallback_state)
+ return true
+ end
+
+
+end
diff --git a/advtrains/path.lua b/advtrains/path.lua
new file mode 100644
index 0000000..714781a
--- /dev/null
+++ b/advtrains/path.lua
@@ -0,0 +1,419 @@
+-- path.lua
+-- Functions for pathpredicting, put in a separate file.
+
+-- Naming conventions:
+-- 'index' - An index of the train.path table.
+-- 'offset' - A value in meters that determines how far on the path to walk relative to a certain index
+-- 'n' - Referring or pointing towards the 'next' path item, the one with index+1
+-- 'p' - Referring or pointing towards the 'prev' path item, the one with index-1
+-- 'f' - Referring to the positive end of the path (the end with the higher index)
+-- 'b' - Referring to the negative end of the path (the end with the lower index)
+
+-- New path structure of trains:
+--Tables:
+-- path - path positions. 'indices' are relative to this. At the moment, at.round_vector_floor_y(path[i])
+-- is the node this item corresponds to, however, this will change in the future.
+-- path_node - (reserved)
+-- path_cn - Connid of the current node that points towards path[i+1]
+-- path_cp - Connid of the current node that points towards path[i-1]
+-- When the day comes on that path!=node, these will only be set if this index represents a transition between rail nodes
+-- path_dist - The total distance of this path element from path element 0
+-- path_dir - The direction of this path item's transition to the next path item, which is the angle of conns[path_cn[i]].c
+-- path_speed- Populated by the LZB system. The maximum speed (velocity) permitted in the moment this path item is passed.
+-- (this saves brake distance calculations every step to determine LZB control). nil means no limit.
+--Variables:
+-- path_ext_f/b - how far path[i] is set
+-- path_trk_f/b - how far the path extends along a track. beyond those values, paths are generated in a straight line.
+-- path_req_f/b - how far path items were requested in the last step
+--
+--Distance and index:
+-- There is an important difference between the path index and the actual distance on the track: The distance between two path items can be larger than 1,
+-- but the corresponding index increment is still 1.
+-- Indexes in advtrains can be fractional values. If they are, it means that the actual position is interpolated between the 2 adjacent path items.
+-- If you need to proceed along the path by a specific actual distance, it does NOT work to simply add it to the index. You should use the path_get_index_by_offset() function.
+
+-- creates the path data structure, reconstructing the train from a position and a connid
+-- Important! train.drives_on must exist while calling this method
+-- returns: true - successful
+-- nil - node not yet available/unloaded, please wait
+-- false - node definitely gone, remove train
+function advtrains.path_create(train, pos, connid, rel_index)
+ local posr = advtrains.round_vector_floor_y(pos)
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, train.drives_on)
+ if not node_ok then
+ return node_ok
+ end
+ local mconnid = advtrains.get_matching_conn(connid, #conns)
+ train.index = rel_index
+ train.path = { [0] = { x=posr.x, y=posr.y+rhe, z=posr.z } }
+ train.path_cn = { [0] = connid }
+ train.path_cp = { [0] = mconnid }
+ train.path_dist = { [0] = 0 }
+
+ train.path_dir = {
+ [0] = advtrains.conn_angle_median(conns[mconnid].c, conns[connid].c)
+ }
+
+ train.path_speed = { }
+
+ train.path_ext_f=0
+ train.path_ext_b=0
+ train.path_trk_f=0
+ train.path_trk_b=0
+ train.path_req_f=0
+ train.path_req_b=0
+
+ advtrains.occ.set_item(train.id, posr, 0)
+ return true
+end
+
+-- Sets position and connid to properly restore after a crash, e.g. in order
+-- to save the train or to invalidate its path
+-- Assumes that the train is in clean state
+-- if invert ist true, setrestore will use the end index
+function advtrains.path_setrestore(train, invert)
+ local idx = train.index
+ if invert then
+ idx = train.end_index
+ end
+
+ local pos, connid, frac = advtrains.path_getrestore(train, idx, invert, true)
+
+ train.last_pos = pos
+ train.last_connid = connid
+ train.last_frac = frac
+end
+-- Get restore position, connid and frac (in this order) for a train that will originate at the passed index
+-- If invert is set, it will return path_cp and multiply frac by -1, in order to reverse the train there.
+function advtrains.path_getrestore(train, index, invert)
+ local idx = index
+ local cns = train.path_cn
+
+ if invert then
+ cns = train.path_cp
+ end
+
+ local fli = atfloor(index)
+ advtrains.path_get(train, fli)
+ if fli > train.path_trk_f then
+ fli = train.path_trk_f
+ end
+ if fli < train.path_trk_b then
+ fli = train.path_trk_b
+ end
+ return advtrains.path_get(train, fli),
+ cns[fli],
+ (idx - fli) * (invert and -1 or 1)
+end
+
+-- Invalidates a path
+-- this is supposed to clear stuff from the occupation tables
+-- This function throws a warning whenever any code calls it while the train steps are run, since that must not happen.
+-- The ignore_lock parameter can be used to ignore this, however, it should then be accompanied by a call to train_ensure_init
+-- before returning from the calling function.
+function advtrains.path_invalidate(train, ignore_lock)
+ if advtrains.lock_path_inval and not ignore_lock then
+ atwarn("Train ",train.train_id,": Illegal path invalidation has occured during train step:")
+ atwarn(debug.traceback())
+ end
+
+ if train.path then
+ for i,p in pairs(train.path) do
+ advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(p))
+ end
+ end
+ train.path = nil
+ train.path_dist = nil
+ train.path_cp = nil
+ train.path_cn = nil
+ train.path_dir = nil
+ train.path_speed = nil
+ train.path_ext_f=0
+ train.path_ext_b=0
+ train.path_trk_f=0
+ train.path_trk_b=0
+ train.path_req_f=0
+ train.path_req_b=0
+
+ train.dirty = true
+ --atdebug(train.id, "Path invalidated")
+end
+
+-- Keeps the path intact, but invalidates all path nodes from the specified index (inclusive)
+-- onwards. This has the advantage that we don't need to recalculate the whole path, and we can do it synchronously.
+function advtrains.path_invalidate_ahead(train, start_idx, ignore_when_passed)
+ if not train.path then
+ -- the path wasn't even initialized. Nothing to do
+ return
+ end
+
+ local idx = atfloor(start_idx)
+ --atdebug("Invalidate_ahead:",train.id,"start_index",start_idx,"cur_idx",train.index)
+
+ if(idx <= train.index - 0.5) then
+ if ignore_when_passed then
+ --atdebug("ignored passed")
+ return
+ end
+ advtrains.path_print(train, atwarn)
+ error("Train "+train.id+": Cannot path_invalidate_ahead start_idx="+idx+" as train has already passed!")
+ end
+
+ -- leave current node in path, it won't change. What might change is the path onward from here (e.g. switch)
+ local i = idx + 1
+ while train.path[i] do
+ advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
+ i = i+1
+ end
+ train.path_ext_f=idx
+ train.path_trk_f=math.min(idx, train.path_trk_f)
+
+ -- callbacks called anyway for current node, because of LZB
+ advtrains.run_callbacks_invahead(train.id, train, idx)
+end
+
+-- Prints a path using the passed print function
+-- This function should be 'atprint', 'atlog', 'atwarn' or 'atdebug', because it needs to use print_concat_table
+function advtrains.path_print(train, printf)
+ printf("path_print: tid =",train.id," index =",train.index," end_index =",train.end_index," vel =",train.velocity)
+ if not train.path then
+ printf("path_print: Path is invalidated/inexistant.")
+ return
+ end
+ printf("i: CP Position Dir CN Dist Speed")
+ for i = train.path_ext_b, train.path_ext_f do
+ if i==train.path_trk_b then
+ printf("--Back on-track border here--")
+ end
+ printf(i,": ",train.path_cp[i]," ",train.path[i]," ",train.path_dir[i]," ",train.path_cn[i]," ",train.path_dist[i]," ",train.path_speed[i])
+ if i==train.path_trk_f then
+ printf("--Front on-track border here--")
+ end
+ end
+end
+
+-- Function to get path entry at a position. This function will automatically calculate more of the path when required.
+-- returns: pos, on_track
+function advtrains.path_get(train, index)
+ if not train.path then
+ error("For train "..train.id..": path_get called but there's no path set yet!")
+ end
+ if index ~= atfloor(index) then
+ error("For train "..train.id..": Called path_get() but index="..index.." is not a round number")
+ end
+
+ local pef = train.path_ext_f
+ -- generate forward (front of train, positive)
+ while index > pef do
+ local pos = train.path[pef]
+ local connid = train.path_cn[pef]
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
+ if pef == train.path_trk_f then
+ node_ok, this_conns = advtrains.get_rail_info_at(pos)
+ if not node_ok then error("For train "..train.id..": Path item "..pef.." on-track but not a valid node!") end
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ end
+ pef = pef + 1
+ if adj_pos then
+ advtrains.occ.set_item(train.id, adj_pos, pef)
+
+ -- If we have split points, notify accordingly
+ local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
+ if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then
+ --atdebug(id,"has split points restored at",adj_pos)
+ mconnid = 3
+ end
+
+ adj_pos.y = adj_pos.y + nextrail_y
+ train.path_cp[pef] = adj_connid
+ train.path_cn[pef] = mconnid
+ train.path_dir[pef] = advtrains.conn_angle_median(next_conns[adj_connid].c, next_conns[mconnid].c)
+ train.path_trk_f = pef
+ else
+ -- off-track fallback behavior
+ adj_pos = advtrains.pos_add_angle(pos, train.path_dir[pef-1])
+ --atdebug("Offtrack overgenerating(front) at",adj_pos,"index",peb,"trkf",train.path_trk_f)
+ train.path_dir[pef] = train.path_dir[pef-1]
+ end
+ train.path[pef] = adj_pos
+ train.path_dist[pef] = train.path_dist[pef-1] + vector.distance(pos, adj_pos)
+ end
+ train.path_ext_f = pef
+
+
+ local peb = train.path_ext_b
+ -- generate backward (back of train, negative)
+ while index < peb do
+ local pos = train.path[peb]
+ local connid = train.path_cp[peb]
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
+ if peb == train.path_trk_b then
+ node_ok, this_conns = advtrains.get_rail_info_at(pos)
+ if not node_ok then error("For train "..train.id..": Path item "..peb.." on-track but not a valid node!") end
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ end
+ peb = peb - 1
+ if adj_pos then
+ advtrains.occ.set_item(train.id, adj_pos, peb)
+
+ -- If we have split points, notify accordingly
+ local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
+ if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then
+ -- atdebug(id,"has split points restored at",adj_pos)
+ mconnid = 3
+ end
+
+ adj_pos.y = adj_pos.y + nextrail_y
+ train.path_cn[peb] = adj_connid
+ train.path_cp[peb] = mconnid
+ train.path_dir[peb] = advtrains.conn_angle_median(next_conns[mconnid].c, next_conns[adj_connid].c)
+ train.path_trk_b = peb
+ else
+ -- off-track fallback behavior
+ adj_pos = advtrains.pos_add_angle(pos, train.path_dir[peb+1] + math.pi)
+ --atdebug("Offtrack overgenerating(back) at",adj_pos,"index",peb,"trkb",train.path_trk_b)
+ train.path_dir[peb] = train.path_dir[peb+1]
+ end
+ train.path[peb] = adj_pos
+ train.path_dist[peb] = train.path_dist[peb+1] - vector.distance(pos, adj_pos)
+ end
+ train.path_ext_b = peb
+
+ if index < train.path_req_b then
+ train.path_req_b = index
+ end
+ if index > train.path_req_f then
+ train.path_req_f = index
+ end
+
+ return train.path[index], (index<=train.path_trk_f and index>=train.path_trk_b)
+
+end
+
+-- interpolated position to fractional index given, and angle based on path_dir
+-- returns: pos, angle(yaw), p_floor, p_ceil
+function advtrains.path_get_interpolated(train, index)
+ local i_floor = atfloor(index)
+ local i_ceil = i_floor + 1
+ local frac = index - i_floor
+ local p_floor = advtrains.path_get(train, i_floor)
+ local p_ceil = advtrains.path_get(train, i_ceil)
+ -- Note: minimal code duplication to path_get_adjacent, for performance
+
+ local a_floor = train.path_dir[i_floor]
+ local a_ceil = train.path_dir[i_ceil]
+
+ local ang = advtrains.minAngleDiffRad(a_floor, a_ceil)
+
+ return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac)), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil
+end
+-- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them
+-- returns: pos_floor, pos_ceil, fraction
+function advtrains.path_get_adjacent(train, index)
+ local i_floor = atfloor(index)
+ local i_ceil = i_floor + 1
+ local frac = index - i_floor
+ local p_floor = advtrains.path_get(train, i_floor)
+ local p_ceil = advtrains.path_get(train, i_ceil)
+ return p_floor, p_ceil, frac
+end
+
+local function n_interpolate(s, e, f)
+ return s + (e-s)*f
+end
+
+-- This function determines the index resulting from moving along the path by 'offset' meters
+-- starting from 'index'. See also the comment on the top of the file.
+function advtrains.path_get_index_by_offset(train, index, offset)
+ local advtrains_path_get = advtrains.path_get
+
+ -- Step 1: determine my current absolute pos on the path
+ local start_index_f = atfloor(index)
+ local end_index_f = start_index_f + 1
+ local c_idx = atfloor(index + offset)
+ local c_idx_f = c_idx + 1
+
+ local frac = index - start_index_f
+
+ advtrains_path_get(train, math.min(start_index_f, end_index_f, c_idx, c_idx_f))
+ advtrains_path_get(train, math.max(start_index_f, end_index_f, c_idx, c_idx_f))
+
+ local dist1, dist2 = train.path_dist[start_index_f], train.path_dist[start_index_f+1]
+ local start_dist = dist1 + (dist2-dist1)*frac
+
+ -- Step 2: determine the total end distance and estimate the index we'd come out
+ local end_dist = start_dist + offset
+
+ local c_idx = atfloor(index + offset)
+
+ -- Step 3: move forward/backward to find real index
+ -- We assume here that the distance between 2 path items is never smaller than 1.
+ -- Our estimated index is therefore either exact or too far over, and we're going to go back
+ -- towards the origin. It is therefore sufficient to query path_get a single time
+
+ -- How we'll adjust c_idx
+ -- Desired position: -------#------
+ -- Path items : --|--|--|--|--
+ -- c_idx : ^
+
+ while train.path_dist[c_idx] < end_dist do
+ c_idx = c_idx + 1
+ end
+
+ while train.path_dist[c_idx] > end_dist do
+ c_idx = c_idx - 1
+ end
+
+ -- Step 4: now c_idx points to the place shown above. Find out the fractional part.
+
+ dist1, dist2 = train.path_dist[c_idx], train.path_dist[c_idx+1]
+
+ frac = (end_dist - dist1) / (dist2 - dist1)
+
+ assert(frac>=0 and frac<1, frac)
+
+ return c_idx + frac
+end
+
+local PATH_CLEAR_KEEP = 4
+
+function advtrains.path_clear_unused(train)
+ local i
+ for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do
+ advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
+ train.path[i] = nil
+ train.path_dist[i-1] = nil
+ train.path_cp[i] = nil
+ train.path_cn[i] = nil
+ train.path_dir[i] = nil
+ train.path_ext_b = i + 1
+ end
+
+ --[[ Why exactly are we clearing path from the front? This doesn't make sense!
+ for i = train.path_ext_f,train.path_req_f + PATH_CLEAR_KEEP,-1 do
+ advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i]))
+ train.path[i] = nil
+ train.path_dist[i] = nil
+ train.path_cp[i] = nil
+ train.path_cn[i] = nil
+ train.path_dir[i+1] = nil
+ train.path_ext_f = i - 1
+ end ]]
+ train.path_trk_b = math.max(train.path_trk_b, train.path_ext_b)
+ --train.path_trk_f = math.min(train.path_trk_f, train.path_ext_f)
+
+ train.path_req_f = math.ceil(train.index)
+ train.path_req_b = math.floor(train.end_index or train.index)
+end
+
+-- Scan the path of the train for position, without querying the occupation table
+-- returns index, or nil if pos is not on the path
+function advtrains.path_lookup(train, pos)
+ local cp = advtrains.round_vector_floor_y(pos)
+ for i = train.path_ext_b, train.path_ext_f do
+ if vector.equals(advtrains.round_vector_floor_y(train.path[i]), cp) then
+ return i
+ end
+ end
+ return nil
+end
diff --git a/advtrains/protection.lua b/advtrains/protection.lua
new file mode 100644
index 0000000..7474977
--- /dev/null
+++ b/advtrains/protection.lua
@@ -0,0 +1,197 @@
+-- advtrains
+-- protection.lua: privileges and rail protection, and some helpers
+
+
+-- Privileges to control TRAIN DRIVING/COUPLING
+minetest.register_privilege("train_operator", {
+ description = "Without this privilege, a player can't do anything about trains, neither place or remove them nor drive or couple them (but he can build tracks if he has track_builder)",
+ give_to_singleplayer= true,
+});
+
+minetest.register_privilege("train_admin", {
+ description = "Player may drive, place or remove any trains from/to anywhere, regardless of owner, whitelist or protection",
+ give_to_singleplayer= true,
+});
+
+-- Privileges to control TRACK BUILDING
+minetest.register_privilege("track_builder", {
+ description = "Player can place and/or dig rails not protected from him. If he also has protection_bypass, he can place/dig any rails",
+ give_to_singleplayer= true,
+});
+
+-- Privileges to control OPERATING TURNOUTS/SIGNALS
+minetest.register_privilege("railway_operator", {
+ description = "Player can operate turnouts and signals not protected from him. If he also has protection_bypass, he can operate any turnouts/signals",
+ give_to_singleplayer= true,
+});
+
+-- there is a configuration option "allow_build_only_owner". If this is active, a player having track_builder can only build rails and operate signals/turnouts in an area explicitly belonging to him
+-- (checked using a dummy player called "*dummy*" (which is not an allowed player name))
+
+
+-- Protection ranges
+local npr_r = tonumber(minetest.settings:get("advtrains_prot_range_side")) or 1
+local npr_vr = tonumber(minetest.settings:get("advtrains_prot_range_up")) or 3
+local npr_vrd = tonumber(minetest.settings:get("advtrains_prot_range_down")) or 1
+
+local boo = minetest.settings:get_bool("advtrains_allow_build_to_owner")
+
+--[[
+Protection/privilege concept:
+Tracks:
+ Protected 1 node all around a rail and 4 nodes upward (maybe make this dynamically determined by the rail...)
+ if track_builder privilege:
+ if not protected from* player:
+ if allow_build_only_owner:
+ if unprotected:
+ deny
+ else:
+ allow
+ deny
+Wagons in general:
+ Players can only place/destroy wagons if they have train_operator
+Wagon driving controls:
+ The former seat_access tables are unnecessary, instead there is a whitelist for the driving stands
+ on player trying to access a driver stand:
+ if is owner or is on whitelist:
+ allow
+ else:
+ deny
+Wagon coupling:
+ Derived from the privileges for driving stands. The whitelist is shared (and also settable on non-driverstand wagons)
+ for each of the two bordering wagons:
+ if is owner or is on whitelist:
+ allow
+
+*"protected from" means the player is not allowed to do things, while "protected by" means that the player is (one of) the owner(s) of this area
+
+]]--
+
+-- temporarily prevent scanning for neighboring rail nodes recursively
+local nocheck
+
+local old_is_protected = minetest.is_protected
+
+-- Check if the node we are about to check is in the range of a track that is protected from a player
+minetest.is_protected = function(pos, pname)
+
+ -- old_is_protected:
+ -- If an earlier callback decided that pos is protected, we wouldn't have been called
+ -- if a later callback decides it, get that here.
+ -- this effectively puts this function into a final-choice position
+ local oprot = old_is_protected(pos, pname)
+ if oprot then
+ return true
+ end
+
+ if nocheck or pname=="" then
+ return false
+ end
+
+ -- Special exception: to allow seamless rail connections between 2 separately protected
+ -- networks, rails itself are not affected by the radius setting. So, if the node here is
+ -- a rail, we skip the check and just use check_track_protection on same pos.
+ local node = minetest.get_node(pos)
+ if minetest.get_item_group(node.name, "advtrains_track") > 0 then
+ -- by here, we know that no other protection callback has this protected, we can safely pass "false".
+ -- hope this doesn't lead to bugs!
+ return not advtrains.check_track_protection(pos, pname, nil, false)
+ end
+
+ local nodes = minetest.find_nodes_in_area(
+ {x = pos.x - npr_r, y = pos.y - npr_vr, z = pos.z - npr_r},
+ {x = pos.x + npr_r, y = pos.y + npr_vrd, z = pos.z + npr_r},
+ {"group:advtrains_track"})
+ for _,npos in ipairs(nodes) do
+ if not advtrains.check_track_protection(npos, pname, pos) then
+ return true
+ end
+ end
+ nocheck=false
+ return false
+end
+
+-- Check whether the player is permitted to modify this track
+-- Shall be called only for nodes that are or are about to become tracks.
+-- The range check from is_track_near_protected is disabled here.
+-- this splits in 1. track_builder privilege and 2. is_protected
+-- also respects the allow_build_to_owner property.
+--WARN: true means here that the action is allowed!
+function advtrains.check_track_protection(pos, pname, near, prot_p)
+ -- Parameter "near" is an optional position, the original node that the player
+ -- was about to affect, while "pos" represents the checked rail node
+ -- if "near" is not set, pos is the same node.
+ local nears = near and "near " or ""
+ local apos = near or pos
+
+ -- note that having protection_bypass implicitly implies having track_builder, because else it would be possible to dig rails
+ -- (only checked by is_protected, which is not respected) but not place them.
+ -- We won't impose restrictions on protection_bypass owners.
+ if minetest.check_player_privs(pname, {protection_bypass = true}) then
+ return true
+ end
+
+ nocheck = true
+ local priv = minetest.check_player_privs(pname, {track_builder = true})
+
+ -- note: is_protected above already checks the is_protected value against the current player, so checking it again is useless.
+ local prot = prot_p
+ if prot==nil then
+ prot = advtrains.is_protected(pos, pname)
+ end
+ local dprot = minetest.is_protected(pos, "*dummy*")
+ nocheck = false
+
+ --atdebug("CTP: ",pos,pname,near,prot_p,"priv=",priv,"prot=",prot,"dprot=",dprot)
+
+ if not priv and (not boo or prot or not dprot) then
+ minetest.chat_send_player(pname, "You are not allowed to build "..nears.."tracks without track_builder privilege")
+ minetest.log("action", pname.." tried to modify terrain "..nears.."track at "..minetest.pos_to_string(apos).." but is not permitted to (no privilege)")
+ return false
+ end
+ if prot then
+ minetest.chat_send_player(pname, "You are not allowed to build "..nears.."tracks at protected position!")
+ minetest.record_protection_violation(pos, pname)
+ minetest.log("action", pname.." tried to modify "..nears.."track at "..minetest.pos_to_string(apos).." but position is protected!")
+ return false
+ end
+ return true
+end
+
+--WARN: true means here that the action is allowed!
+function advtrains.check_driving_couple_protection(pname, owner, whitelist)
+ if minetest.check_player_privs(pname, {train_admin = true}) then
+ return true
+ end
+ if not minetest.check_player_privs(pname, {train_operator = true}) then
+ return false
+ end
+ if not owner or owner == pname then
+ return true
+ end
+ if whitelist and string.find(" "..whitelist.." ", " "..pname.." ", nil, true) then
+ return true
+ end
+ return false
+end
+function advtrains.check_turnout_signal_protection(pos, pname)
+ nocheck=true
+ if not minetest.check_player_privs(pname, {railway_operator = true}) then
+ if boo and not advtrains.is_protected(pos, pname) and minetest.is_protected(pos, "*dummy*") then
+ nocheck=false
+ return true
+ else
+ minetest.chat_send_player(pname, "You are not allowed to operate turnouts and signals (missing railway_operator privilege)")
+ minetest.log("action", pname.." tried to operate turnout/signal at "..minetest.pos_to_string(pos).." but does not have railway_operator")
+ nocheck=false
+ return false
+ end
+ end
+ if advtrains.is_protected(pos, pname) then
+ minetest.record_protection_violation(pos, pname)
+ nocheck=false
+ return false
+ end
+ nocheck=false
+ return true
+end
diff --git a/advtrains/settingtypes.txt b/advtrains/settingtypes.txt
new file mode 100644
index 0000000..6acff80
--- /dev/null
+++ b/advtrains/settingtypes.txt
@@ -0,0 +1,58 @@
+# Display train and wagon ID in the infotext of trains.
+# Useful when working with LuaATC or while debugging.
+advtrains_show_ids (Show ID's in infotext) bool false
+
+# Enable the debug ring buffer
+# This has no effect on the user experience, except decreased performance. Debug outputs are saved in a ring buffer to be printed when an error occurs.
+# You probably want to leave this setting set to false.
+advtrains_enable_debugging (Enable debugging) bool false
+
+# Enable the logging of certain events related to advtrains
+# Logs are saved in the world directory as advtrains.log
+# This setting is useful for multiplayer servers
+advtrains_enable_logging (Enable logging) bool false
+
+# If this is active, any player can do the following things inside (and only inside) an area that is explicitly protected by him
+# (checked using a dummy player called "*dummy*" (which is not an allowed player name)):
+# - build tracks and near tracks without the track_builder privilege
+# - operate turnouts and signals without the railway_operator privilege
+advtrains_allow_build_to_owner (Allow building/operating to privilegeless area owner) bool false
+
+# Track protection range (horizontal)
+# Players without the 'track_builder' privilege can not build within a box around any tracks determined by these range settings
+# This setting is to be read as "r-0.5", so a value of 1 means a diameter of 3, a value of 2 a diameter of 5 a.s.o.
+# The spanned area is a square. Fractional values are not supported.
+advtrains_prot_range_side (Track protection range [horizontal]) int 1 0 10
+
+# Track protection range (up)
+# Players without the 'track_builder' privilege can not build within a box around any tracks determined by these range settings
+# This setting determines the upper y bound of the box, a value of 3 means that the rail and 3 nodes above it are protected
+advtrains_prot_range_up (Track protection range [up]) int 3 0 10
+
+# Track protection range (down)
+# Players without the 'track_builder' privilege can not build within a box around any tracks determined by these range settings
+# This setting determines the lower y bound of the box, a value of 1 means that the rail and 1 node below it are protected
+advtrains_prot_range_down (Track protection range [down]) int 1 0 10
+
+# Determine what effect "being overrun by a train" has.
+# none: No damage is dealt at all.
+# drop: Player is killed, all items are dropped as items on the tracks.
+# normal: Player is killed, game-defined behavior is applied as if the player died by other means.
+advtrains_overrun_mode (Overrun mode) enum drop none,drop,normal
+
+# Wagon entity loading/unloading range, in nodes
+# When a wagon is within this range to a player, it is loaded
+# When a wagon leaves this range + 32 nodes, it is unloaded
+# If unset, defaults to active_block_range*16
+advtrains_wagon_load_range (Wagon Entity Load/Unload Range) int 96 32 512
+
+# Simulation DTime Limit after which slow-down becomes effective
+# When the dtime value (time since last server step) is higher than this value,
+# advtrains applies a global slow-down factor to the dtime and to the velocity and
+# acceleration of wagons to decrease server load.
+# A value of 0 (default) disables this behavior.
+advtrains_dtime_limit (DTime Limit for slow-down) float 0.2 0 5
+
+# Time interval in seconds in which advtrains stores its save data to disk
+# Nevertheless, advtrains saves all data when shutting down the server.
+advtrains_save_interval (Save Interval) int 60 20 3600
diff --git a/advtrains/signals.lua b/advtrains/signals.lua
new file mode 100644
index 0000000..59612cd
--- /dev/null
+++ b/advtrains/signals.lua
@@ -0,0 +1,348 @@
+--advtrains by orwell96
+--signals.lua
+
+local mrules_wallsignal = advtrains.meseconrules
+
+local function can_dig_func(pos)
+ if advtrains.interlocking then
+ return advtrains.interlocking.signal_can_dig(pos)
+ end
+ return true
+end
+local function after_dig_func(pos)
+ if advtrains.interlocking then
+ return advtrains.interlocking.signal_after_dig(pos)
+ end
+ return true
+end
+
+local function aspect(b)
+return {
+ main = (not b) and 0, -- b ? false : 0
+ shunt = false,
+ proceed_as_main = true,
+ dst = false,
+ info = {}
+}
+end
+
+local suppasp = {
+ main = {0, false},
+ dst = {false},
+ shunt = nil,
+ proceed_as_main = true,
+ info = {
+ call_on = false,
+ dead_end = false,
+ w_speed = nil,
+ }
+}
+
+for r,f in pairs({on={as="off", ls="green", als="red"}, off={as="on", ls="red", als="green"}}) do
+
+ advtrains.trackplacer.register_tracktype("advtrains:retrosignal", "")
+ advtrains.trackplacer.register_tracktype("advtrains:signal", "")
+
+ for rotid, rotation in ipairs({"", "_30", "_45", "_60"}) do
+ local crea=1
+ if rotid==1 and r=="off" then crea=0 end
+
+ minetest.register_node("advtrains:retrosignal_"..r..rotation, {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 2, 1/4},
+ },
+ mesh = "advtrains_retrosignal_"..r..rotation..".b3d",
+ tiles = {"advtrains_retrosignal.png"},
+ inventory_image="advtrains_retrosignal_inv.png",
+ drop="advtrains:retrosignal_off",
+ description=attrans("Lampless Signal (@1)", attrans(r..rotation)),
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ not_in_creative_inventory=crea,
+ save_in_at_nodedb=1,
+ advtrains_signal = 2,
+ },
+ mesecons = {effector = {
+ rules=advtrains.meseconrules,
+ ["action_"..f.as] = function (pos, node)
+ advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ }},
+ on_rightclick=function(pos, node, player)
+ local pname = player:get_player_name()
+ local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if sigd then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ elseif advtrains.interlocking and player:get_player_control().aux1 then
+ advtrains.interlocking.show_ip_form(pos, pname)
+ elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_"..f.as..rotation, param2 = node.param2}, true)
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ end,
+ -- new signal API
+ advtrains = {
+ set_aspect = function(pos, node, asp)
+ if asp.main ~= 0 then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_on"..rotation, param2 = node.param2}, true)
+ else
+ advtrains.ndb.swap_node(pos, {name = "advtrains:retrosignal_off"..rotation, param2 = node.param2}, true)
+ end
+ end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
+ end,
+ supported_aspects = suppasp,
+ },
+ can_dig = can_dig_func,
+ after_dig_node = after_dig_func,
+ })
+ advtrains.trackplacer.add_worked("advtrains:retrosignal", r, rotation, nil)
+
+ minetest.register_node("advtrains:signal_"..r..rotation, {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 2, 1/4},
+ },
+ mesh = "advtrains_signal"..rotation..".b3d",
+ tiles = {"advtrains_signal_"..r..".png"},
+ inventory_image="advtrains_signal_inv.png",
+ drop="advtrains:signal_off",
+ description=attrans("Signal (@1)", attrans(r..rotation)),
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ not_in_creative_inventory=crea,
+ save_in_at_nodedb=1,
+ advtrains_signal = 2,
+ },
+ light_source = 1,
+ sunlight_propagates=true,
+ mesecons = {effector = {
+ rules=advtrains.meseconrules,
+ ["action_"..f.as] = function (pos, node)
+ advtrains.setstate(pos, f.als, node)
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ }},
+ on_rightclick=function(pos, node, player)
+ local pname = player:get_player_name()
+ local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if sigd then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ elseif advtrains.interlocking and player:get_player_control().aux1 then
+ advtrains.interlocking.show_ip_form(pos, pname)
+ elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.setstate(pos, f.als, node)
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ end,
+ -- new signal API
+ advtrains = {
+ set_aspect = function(pos, node, asp)
+ if asp.main ~= 0 then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_on"..rotation, param2 = node.param2}, true)
+ else
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_off"..rotation, param2 = node.param2}, true)
+ end
+ end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
+ end,
+ supported_aspects = suppasp,
+ getstate = f.ls,
+ setstate = function(pos, node, newstate)
+ if newstate == f.als then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_"..f.as..rotation, param2 = node.param2}, true)
+ end
+ end,
+ },
+ can_dig = can_dig_func,
+ after_dig_node = after_dig_func,
+ })
+ advtrains.trackplacer.add_worked("advtrains:signal", r, rotation, nil)
+ end
+
+ local crea=1
+ if r=="off" then crea=0 end
+
+ --tunnel signals. no rotations.
+ for loc, sbox in pairs({l={-1/2, -1/2, -1/4, 0, 1/2, 1/4}, r={0, -1/2, -1/4, 1/2, 1/2, 1/4}, t={-1/2, 0, -1/4, 1/2, 1/2, 1/4}}) do
+ minetest.register_node("advtrains:signal_wall_"..loc.."_"..r, {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = sbox,
+ },
+ mesh = "advtrains_signal_wall_"..loc..".b3d",
+ tiles = {"advtrains_signal_wall_"..r..".png"},
+ drop="advtrains:signal_wall_"..loc.."_off",
+ description=attrans("Wallmounted Signal ("..loc..")"),
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ not_in_creative_inventory=crea,
+ save_in_at_nodedb=1,
+ advtrains_signal = 2,
+ },
+ light_source = 1,
+ sunlight_propagates=true,
+ mesecons = {effector = {
+ rules = mrules_wallsignal,
+ ["action_"..f.as] = function (pos, node)
+ advtrains.setstate(pos, f.als, node)
+ end
+ }},
+ on_rightclick=function(pos, node, player)
+ local pname = player:get_player_name()
+ local sigd = advtrains.interlocking and advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if sigd then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ elseif advtrains.interlocking and player:get_player_control().aux1 then
+ advtrains.interlocking.show_ip_form(pos, pname)
+ elseif advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.setstate(pos, f.als, node)
+ end
+ end,
+ -- new signal API
+ advtrains = {
+ set_aspect = function(pos, node, asp)
+ if asp.main ~= 0 then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_on", param2 = node.param2}, true)
+ else
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_off", param2 = node.param2}, true)
+ end
+ end,
+ get_aspect = function(pos, node)
+ return aspect(r=="on")
+ end,
+ supported_aspects = suppasp,
+ getstate = f.ls,
+ setstate = function(pos, node, newstate)
+ if newstate == f.als then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:signal_wall_"..loc.."_"..f.as, param2 = node.param2}, true)
+ end
+ end,
+ },
+ can_dig = can_dig_func,
+ after_dig_node = after_dig_func,
+ })
+ end
+end
+
+-- level crossing
+-- german version (Andrew's Cross)
+minetest.register_node("advtrains:across_off", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/2, 1/4, 1.5, 0},
+ },
+ mesh = "advtrains_across.obj",
+ tiles = {"advtrains_across.png"},
+ drop="advtrains:across_off",
+ description=attrans("Andrew's Cross"),
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ save_in_at_nodedb=1,
+ not_in_creative_inventory=nil,
+ },
+ light_source = 1,
+ sunlight_propagates=true,
+ mesecons = {effector = {
+ rules = advtrains.meseconrules,
+ action_on = function (pos, node)
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
+ end
+ }},
+ advtrains = {
+ getstate = "off",
+ setstate = function(pos, node, newstate)
+ if newstate == "on" then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
+ end
+ end,
+ },
+ on_rightclick=function(pos, node, player)
+ if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_on", param2 = node.param2}, true)
+ end
+ end,
+})
+minetest.register_node("advtrains:across_on", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/2, 1/4, 1.5, 0},
+ },
+ mesh = "advtrains_across.obj",
+ tiles = {{name="advtrains_across_anim.png", animation={type="vertical_frames", aspect_w=64, aspect_h=64, length=1.0}}},
+ drop="advtrains:across_off",
+ description=attrans("Andrew's Cross (on) (you hacker you)"),
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ save_in_at_nodedb=1,
+ not_in_creative_inventory=1,
+ },
+ light_source = 1,
+ sunlight_propagates=true,
+ mesecons = {effector = {
+ rules = advtrains.meseconrules,
+ action_off = function (pos, node)
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
+ end
+ }},
+ advtrains = {
+ getstate = "on",
+ setstate = function(pos, node, newstate)
+ if newstate == "off" then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
+ end
+ end,
+ fallback_state = "off",
+ },
+ on_rightclick=function(pos, node, player)
+ if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.ndb.swap_node(pos, {name = "advtrains:across_off", param2 = node.param2}, true)
+ end
+ end,
+})
+
+minetest.register_abm(
+ {
+ label = "Sound for Level Crossing",
+ nodenames = {"advtrains:across_on"},
+ interval = 3,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ minetest.sound_play("advtrains_crossing_bell", {
+ pos = pos,
+ gain = 1.0, -- default
+ max_hear_distance = 16, -- default, uses an euclidean metric
+ })
+ end,
+ }
+)
diff --git a/advtrains/sounds/advtrains_crossing_bell.ogg b/advtrains/sounds/advtrains_crossing_bell.ogg
new file mode 100644
index 0000000..74df669
--- /dev/null
+++ b/advtrains/sounds/advtrains_crossing_bell.ogg
Binary files differ
diff --git a/advtrains/textures/advtrains_across.png b/advtrains/textures/advtrains_across.png
new file mode 100755
index 0000000..da65a61
--- /dev/null
+++ b/advtrains/textures/advtrains_across.png
Binary files differ
diff --git a/advtrains/textures/advtrains_across_anim.png b/advtrains/textures/advtrains_across_anim.png
new file mode 100755
index 0000000..584d023
--- /dev/null
+++ b/advtrains/textures/advtrains_across_anim.png
Binary files differ
diff --git a/advtrains/textures/advtrains_boiler.png b/advtrains/textures/advtrains_boiler.png
new file mode 100755
index 0000000..8c7ff0d
--- /dev/null
+++ b/advtrains/textures/advtrains_boiler.png
Binary files differ
diff --git a/advtrains/textures/advtrains_chimney.png b/advtrains/textures/advtrains_chimney.png
new file mode 100755
index 0000000..285a1a6
--- /dev/null
+++ b/advtrains/textures/advtrains_chimney.png
Binary files differ
diff --git a/advtrains/textures/advtrains_copytool.png b/advtrains/textures/advtrains_copytool.png
new file mode 100644
index 0000000..a8ea557
--- /dev/null
+++ b/advtrains/textures/advtrains_copytool.png
Binary files differ
diff --git a/advtrains/textures/advtrains_couple.png b/advtrains/textures/advtrains_couple.png
new file mode 100755
index 0000000..eda3704
--- /dev/null
+++ b/advtrains/textures/advtrains_couple.png
Binary files differ
diff --git a/advtrains/textures/advtrains_cpl_lock.png b/advtrains/textures/advtrains_cpl_lock.png
new file mode 100644
index 0000000..a25aaf4
--- /dev/null
+++ b/advtrains/textures/advtrains_cpl_lock.png
Binary files differ
diff --git a/advtrains/textures/advtrains_cpl_unlock.png b/advtrains/textures/advtrains_cpl_unlock.png
new file mode 100644
index 0000000..f58d00a
--- /dev/null
+++ b/advtrains/textures/advtrains_cpl_unlock.png
Binary files differ
diff --git a/advtrains/textures/advtrains_discouple.png b/advtrains/textures/advtrains_discouple.png
new file mode 100755
index 0000000..5c064c3
--- /dev/null
+++ b/advtrains/textures/advtrains_discouple.png
Binary files differ
diff --git a/advtrains/textures/advtrains_driver_cab.png b/advtrains/textures/advtrains_driver_cab.png
new file mode 100755
index 0000000..331bcc9
--- /dev/null
+++ b/advtrains/textures/advtrains_driver_cab.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_atc_placer.png b/advtrains/textures/advtrains_dtrack_atc_placer.png
new file mode 100755
index 0000000..31c2b30
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_atc_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_bumper_placer.png b/advtrains/textures/advtrains_dtrack_bumper_placer.png
new file mode 100755
index 0000000..27191fe
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_bumper_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_detector_placer.png b/advtrains/textures/advtrains_dtrack_detector_placer.png
new file mode 100755
index 0000000..e6c6ad6
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_detector_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_load_placer.png b/advtrains/textures/advtrains_dtrack_load_placer.png
new file mode 100755
index 0000000..427c011
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_load_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_placer.png b/advtrains/textures/advtrains_dtrack_placer.png
new file mode 100755
index 0000000..7bef8a9
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_rail.png b/advtrains/textures/advtrains_dtrack_rail.png
new file mode 100755
index 0000000..bd0c217
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_rail.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared.png b/advtrains/textures/advtrains_dtrack_shared.png
new file mode 100755
index 0000000..736c7db
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared_atc.png b/advtrains/textures/advtrains_dtrack_shared_atc.png
new file mode 100755
index 0000000..1f83c37
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared_atc.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared_detector_off.png b/advtrains/textures/advtrains_dtrack_shared_detector_off.png
new file mode 100755
index 0000000..724d907
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared_detector_off.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared_detector_on.png b/advtrains/textures/advtrains_dtrack_shared_detector_on.png
new file mode 100755
index 0000000..7bb29d6
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared_detector_on.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared_load.png b/advtrains/textures/advtrains_dtrack_shared_load.png
new file mode 100755
index 0000000..5fd0d7a
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared_load.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_shared_unload.png b/advtrains/textures/advtrains_dtrack_shared_unload.png
new file mode 100755
index 0000000..e9fc5bd
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_shared_unload.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_slopeplacer.png b/advtrains/textures/advtrains_dtrack_slopeplacer.png
new file mode 100755
index 0000000..1d456b0
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_slopeplacer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_dtrack_unload_placer.png b/advtrains/textures/advtrains_dtrack_unload_placer.png
new file mode 100755
index 0000000..486861e
--- /dev/null
+++ b/advtrains/textures/advtrains_dtrack_unload_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_hud_arrow.png b/advtrains/textures/advtrains_hud_arrow.png
new file mode 100644
index 0000000..71d75b0
--- /dev/null
+++ b/advtrains/textures/advtrains_hud_arrow.png
Binary files differ
diff --git a/advtrains/textures/advtrains_hud_atc.png b/advtrains/textures/advtrains_hud_atc.png
new file mode 100644
index 0000000..e033653
--- /dev/null
+++ b/advtrains/textures/advtrains_hud_atc.png
Binary files differ
diff --git a/advtrains/textures/advtrains_hud_bg.png b/advtrains/textures/advtrains_hud_bg.png
new file mode 100644
index 0000000..aa168d4
--- /dev/null
+++ b/advtrains/textures/advtrains_hud_bg.png
Binary files differ
diff --git a/advtrains/textures/advtrains_hud_lzb.png b/advtrains/textures/advtrains_hud_lzb.png
new file mode 100644
index 0000000..e1b5f70
--- /dev/null
+++ b/advtrains/textures/advtrains_hud_lzb.png
Binary files differ
diff --git a/advtrains/textures/advtrains_hud_shunt.png b/advtrains/textures/advtrains_hud_shunt.png
new file mode 100644
index 0000000..f4d27a5
--- /dev/null
+++ b/advtrains/textures/advtrains_hud_shunt.png
Binary files differ
diff --git a/advtrains/textures/advtrains_platform.png b/advtrains/textures/advtrains_platform.png
new file mode 100755
index 0000000..5ba9663
--- /dev/null
+++ b/advtrains/textures/advtrains_platform.png
Binary files differ
diff --git a/advtrains/textures/advtrains_platform_diag.png b/advtrains/textures/advtrains_platform_diag.png
new file mode 100644
index 0000000..6e262e2
--- /dev/null
+++ b/advtrains/textures/advtrains_platform_diag.png
Binary files differ
diff --git a/advtrains/textures/advtrains_retrosignal.png b/advtrains/textures/advtrains_retrosignal.png
new file mode 100755
index 0000000..141198d
--- /dev/null
+++ b/advtrains/textures/advtrains_retrosignal.png
Binary files differ
diff --git a/advtrains/textures/advtrains_retrosignal_inv.png b/advtrains/textures/advtrains_retrosignal_inv.png
new file mode 100755
index 0000000..1036594
--- /dev/null
+++ b/advtrains/textures/advtrains_retrosignal_inv.png
Binary files differ
diff --git a/advtrains/textures/advtrains_signal_inv.png b/advtrains/textures/advtrains_signal_inv.png
new file mode 100755
index 0000000..ed64ed9
--- /dev/null
+++ b/advtrains/textures/advtrains_signal_inv.png
Binary files differ
diff --git a/advtrains/textures/advtrains_signal_off.png b/advtrains/textures/advtrains_signal_off.png
new file mode 100755
index 0000000..8046e52
--- /dev/null
+++ b/advtrains/textures/advtrains_signal_off.png
Binary files differ
diff --git a/advtrains/textures/advtrains_signal_on.png b/advtrains/textures/advtrains_signal_on.png
new file mode 100755
index 0000000..5228bb3
--- /dev/null
+++ b/advtrains/textures/advtrains_signal_on.png
Binary files differ
diff --git a/advtrains/textures/advtrains_signal_wall_off.png b/advtrains/textures/advtrains_signal_wall_off.png
new file mode 100755
index 0000000..3e7b1e1
--- /dev/null
+++ b/advtrains/textures/advtrains_signal_wall_off.png
Binary files differ
diff --git a/advtrains/textures/advtrains_signal_wall_on.png b/advtrains/textures/advtrains_signal_wall_on.png
new file mode 100755
index 0000000..b628c7e
--- /dev/null
+++ b/advtrains/textures/advtrains_signal_wall_on.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_cr.png b/advtrains/textures/advtrains_track_cr.png
new file mode 100755
index 0000000..40f0cc5
--- /dev/null
+++ b/advtrains/textures/advtrains_track_cr.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_cr_45.png b/advtrains/textures/advtrains_track_cr_45.png
new file mode 100755
index 0000000..54966b3
--- /dev/null
+++ b/advtrains/textures/advtrains_track_cr_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_placer.png b/advtrains/textures/advtrains_track_placer.png
new file mode 100755
index 0000000..03e17ed
--- /dev/null
+++ b/advtrains/textures/advtrains_track_placer.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_st.png b/advtrains/textures/advtrains_track_st.png
new file mode 100755
index 0000000..5ad7e4f
--- /dev/null
+++ b/advtrains/textures/advtrains_track_st.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_st_45.png b/advtrains/textures/advtrains_track_st_45.png
new file mode 100755
index 0000000..63b4c96
--- /dev/null
+++ b/advtrains/textures/advtrains_track_st_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swlcr.png b/advtrains/textures/advtrains_track_swlcr.png
new file mode 100755
index 0000000..d9b5c0b
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swlcr.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swlcr_45.png b/advtrains/textures/advtrains_track_swlcr_45.png
new file mode 100755
index 0000000..f098fc9
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swlcr_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swlst.png b/advtrains/textures/advtrains_track_swlst.png
new file mode 100755
index 0000000..314bd2d
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swlst.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swlst_45.png b/advtrains/textures/advtrains_track_swlst_45.png
new file mode 100755
index 0000000..765d0ec
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swlst_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swrcr.png b/advtrains/textures/advtrains_track_swrcr.png
new file mode 100755
index 0000000..f74e1bc
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swrcr.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swrcr_45.png b/advtrains/textures/advtrains_track_swrcr_45.png
new file mode 100755
index 0000000..fa432aa
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swrcr_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swrst.png b/advtrains/textures/advtrains_track_swrst.png
new file mode 100755
index 0000000..06ea29e
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swrst.png
Binary files differ
diff --git a/advtrains/textures/advtrains_track_swrst_45.png b/advtrains/textures/advtrains_track_swrst_45.png
new file mode 100755
index 0000000..be477b7
--- /dev/null
+++ b/advtrains/textures/advtrains_track_swrst_45.png
Binary files differ
diff --git a/advtrains/textures/advtrains_trackworker.png b/advtrains/textures/advtrains_trackworker.png
new file mode 100755
index 0000000..b50bcae
--- /dev/null
+++ b/advtrains/textures/advtrains_trackworker.png
Binary files differ
diff --git a/advtrains/textures/advtrains_wagon_placeholder.png b/advtrains/textures/advtrains_wagon_placeholder.png
new file mode 100644
index 0000000..383c181
--- /dev/null
+++ b/advtrains/textures/advtrains_wagon_placeholder.png
Binary files differ
diff --git a/advtrains/textures/advtrains_wheel.png b/advtrains/textures/advtrains_wheel.png
new file mode 100755
index 0000000..fb72879
--- /dev/null
+++ b/advtrains/textures/advtrains_wheel.png
Binary files differ
diff --git a/advtrains/textures/drwho_screwdriver.png b/advtrains/textures/drwho_screwdriver.png
new file mode 100755
index 0000000..b50bcae
--- /dev/null
+++ b/advtrains/textures/drwho_screwdriver.png
Binary files differ
diff --git a/advtrains/trackdb_legacy.lua b/advtrains/trackdb_legacy.lua
new file mode 100644
index 0000000..99349e8
--- /dev/null
+++ b/advtrains/trackdb_legacy.lua
@@ -0,0 +1,27 @@
+--trackdb_legacy.lua
+--loads the (old) track database. the only use for this is to provide data for rails that haven't been written into the ndb database.
+--nothing will be saved.
+--if the user thinks that he has loaded every track in his world at least once, he can delete the track database.
+
+--trackdb[[y][x][z]={conn1, conn2, rely1, rely2, railheight}
+
+
+--trackdb keeps its own save file.
+advtrains.fpath_tdb=minetest.get_worldpath().."/advtrains_trackdb2"
+local file, err = io.open(advtrains.fpath_tdb, "r")
+if not file then
+ atprint("Not loading a trackdb file.")
+else
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ advtrains.trackdb=tbl
+ atprint("Loaded trackdb file.")
+ end
+ file:close()
+end
+
+
+
+
+
+
diff --git a/advtrains/trackplacer.lua b/advtrains/trackplacer.lua
new file mode 100644
index 0000000..356df15
--- /dev/null
+++ b/advtrains/trackplacer.lua
@@ -0,0 +1,432 @@
+--trackplacer.lua
+--holds code for the track-placing system. the default 'track' item will be a craftitem that places rails as needed. this will neither place or change switches nor place vertical rails.
+
+--all new trackplacer code
+local tp={
+ tracks={}
+}
+
+function tp.register_tracktype(nnprefix, n_suffix)
+ if tp.tracks[nnprefix] then return end--due to the separate registration of slopes and flats for the same nnpref, definition would be overridden here. just don't.
+ tp.tracks[nnprefix]={
+ default=n_suffix,
+ single_conn={},
+ single_conn_1={},
+ single_conn_2={},
+ double_conn={},
+ double_conn_1={},
+ double_conn_2={},
+ --keys:conn1_conn2 (example:1_4)
+ --values:{name=x, param2=x}
+ twcycle={},
+ twrotate={},--indexed by suffix, list, tells order of rotations
+ modify={},
+ }
+end
+function tp.add_double_conn(nnprefix, suffix, rotation, conns)
+ local nodename=nnprefix.."_"..suffix..rotation
+ for i=0,3 do
+ tp.tracks[nnprefix].double_conn[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].double_conn[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].double_conn_1[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].double_conn_2[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i}
+ end
+ tp.tracks[nnprefix].modify[nodename]=true
+end
+function tp.add_single_conn(nnprefix, suffix, rotation, conns)
+ local nodename=nnprefix.."_"..suffix..rotation
+ for i=0,3 do
+ tp.tracks[nnprefix].single_conn[((conns.conn1+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].single_conn[((conns.conn2+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].single_conn_1[((conns.conn1+4*i)%16)]={name=nodename, param2=i}
+ tp.tracks[nnprefix].single_conn_2[((conns.conn2+4*i)%16)]={name=nodename, param2=i}
+ end
+ tp.tracks[nnprefix].modify[nodename]=true
+end
+
+
+function tp.add_worked(nnprefix, suffix, rotation, cycle_follows)
+ tp.tracks[nnprefix].twcycle[suffix]=cycle_follows
+ if not tp.tracks[nnprefix].twrotate[suffix] then tp.tracks[nnprefix].twrotate[suffix]={} end
+ table.insert(tp.tracks[nnprefix].twrotate[suffix], rotation)
+end
+
+
+--[[
+ rewrite algorithm.
+ selection criteria: these will never be changed or even selected:
+ - tracks being already connected on both sides
+ - tracks that are already connected on one side but are not bendable to the desired position
+ the following situations can occur:
+ 1. there are two more than two rails around
+ 1.1 there is one or more subset(s) that can be directly connected
+ -> choose the first possibility
+ 2.2 not
+ -> choose the first one and orient straight
+ 2. there's exactly 1 rail around
+ -> choose and orient straight
+ 3. there's no rail around
+ -> set straight
+]]
+
+local function istrackandbc(pos_p, conn)
+ local tpos = pos_p
+ local cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
+ if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
+ local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
+ return advtrains.conn_matches_to(conn, cconns)
+ end
+ --try the same 1 node below
+ tpos = {x=tpos.x, y=tpos.y-1, z=tpos.z}
+ cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c))
+ if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then
+ local cconns=advtrains.get_track_connections(cnode.name, cnode.param2)
+ return advtrains.conn_matches_to(conn, cconns)
+ end
+ return false
+end
+
+function tp.find_already_connected(pos)
+ local dnode=minetest.get_node(pos)
+ local dconns=advtrains.get_track_connections(dnode.name, dnode.param2)
+ local found_conn
+ for connid, conn in ipairs(dconns) do
+ if istrackandbc(pos, conn) then
+ if found_conn then --we found one in previous iteration
+ return true, true --signal that it's connected
+ else
+ found_conn = conn.c
+ end
+ end
+ end
+ return found_conn
+end
+function tp.rail_and_can_be_bent(originpos, conn)
+ local pos=advtrains.dirCoordSet(originpos, conn)
+ local newdir=(conn+8)%16
+ local node=minetest.get_node(pos)
+ if not advtrains.is_track_and_drives_on(node.name, advtrains.all_tracktypes) then
+ return false
+ end
+ local ndef=minetest.registered_nodes[node.name]
+ local nnpref = ndef and ndef.at_nnpref
+ if not nnpref then return false end
+ local tr=tp.tracks[nnpref]
+ if not tr then return false end
+ if not tr.modify[node.name] then
+ --we actually can use this rail, but only if it already points to the desired direction.
+ if advtrains.is_track_and_drives_on(node.name, advtrains.all_tracktypes) then
+ local cconns=advtrains.get_track_connections(node.name, node.param2)
+ return advtrains.conn_matches_to(conn, cconns)
+ end
+ end
+ -- If the rail is not allowed to be modified, also only use if already in desired direction
+ if not advtrains.can_dig_or_modify_track(pos) then
+ local cconns=advtrains.get_track_connections(node.name, node.param2)
+ return advtrains.conn_matches_to(conn, cconns)
+ end
+ --rail at other end?
+ local adj1, adj2=tp.find_already_connected(pos)
+ if adj1 and adj2 then
+ return false--dont destroy existing track
+ elseif adj1 and not adj2 then
+ if tr.double_conn[adj1.."_"..newdir] then
+ return true--if exists, connect new rail and old end
+ end
+ return false
+ else
+ if tr.single_conn[newdir] then--just rotate old rail to right orientation
+ return true
+ end
+ return false
+ end
+end
+function tp.bend_rail(originpos, conn)
+ local pos=advtrains.dirCoordSet(originpos, conn)
+ local newdir=advtrains.oppd(conn)
+ local node=minetest.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ local nnpref = ndef and ndef.at_nnpref
+ if not nnpref then return false end
+ local tr=tp.tracks[nnpref]
+ if not tr then return false end
+ --is rail already connected? no need to bend.
+ local conns=advtrains.get_track_connections(node.name, node.param2)
+ if advtrains.conn_matches_to(conn, conns) then
+ return
+ end
+ --rail at other end?
+ local adj1, adj2=tp.find_already_connected(pos)
+ if adj1 and adj2 then
+ return false--dont destroy existing track
+ elseif adj1 and not adj2 then
+ if tr.double_conn[adj1.."_"..newdir] then
+ advtrains.ndb.swap_node(pos, tr.double_conn[adj1.."_"..newdir])
+ return true--if exists, connect new rail and old end
+ end
+ return false
+ else
+ if tr.single_conn[newdir] then--just rotate old rail to right orientation
+ advtrains.ndb.swap_node(pos, tr.single_conn[newdir])
+ return true
+ end
+ return false
+ end
+end
+function tp.placetrack(pos, nnpref, placer, itemstack, pointed_thing, yaw)
+ --1. find all rails that are likely to be connected
+ local tr=tp.tracks[nnpref]
+ local p_rails={}
+ local p_railpos={}
+ for i=0,15 do
+ if tp.rail_and_can_be_bent(pos, i, nnpref) then
+ p_rails[#p_rails+1]=i
+ p_railpos[i] = pos
+ else
+ local upos = {x=pos.x, y=pos.y-1, z=pos.z}
+ if tp.rail_and_can_be_bent(upos, i, nnpref) then
+ p_rails[#p_rails+1]=i
+ p_railpos[i] = upos
+ end
+ end
+ end
+
+ -- try double_conn
+ if #p_rails > 1 then
+ --iterate subsets
+ for k1, conn1 in ipairs(p_rails) do
+ for k2, conn2 in ipairs(p_rails) do
+ if k1~=k2 then
+ local dconn1 = tr.double_conn_1
+ local dconn2 = tr.double_conn_2
+ if not (advtrains.yawToDirection(yaw, conn1, conn2) == conn1) then
+ dconn1 = tr.double_conn_2
+ dconn2 = tr.double_conn_1
+ end
+ -- Checks are made this way round so that dconn1 has priority (this will make arrows of atc rails
+ -- point in the right direction)
+ local using
+ if (dconn2[conn1.."_"..conn2]) then
+ using = dconn2[conn1.."_"..conn2]
+ end
+ if (dconn1[conn1.."_"..conn2]) then
+ using = dconn1[conn1.."_"..conn2]
+ end
+ if using then
+ -- has found a fitting rail in either direction
+ -- if not, continue loop
+ tp.bend_rail(p_railpos[conn1], conn1, nnpref)
+ tp.bend_rail(p_railpos[conn2], conn2, nnpref)
+ advtrains.ndb.swap_node(pos, using)
+ local nname=using.name
+ if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
+ minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
+ end
+ return
+ end
+ end
+ end
+ end
+ end
+ -- try single_conn
+ if #p_rails > 0 then
+ for ix, p_rail in ipairs(p_rails) do
+ local sconn1 = tr.single_conn_1
+ local sconn2 = tr.single_conn_2
+ if not (advtrains.yawToDirection(yaw, p_rail, (p_rail+8)%16) == p_rail) then
+ sconn1 = tr.single_conn_2
+ sconn2 = tr.single_conn_1
+ end
+ if sconn1[p_rail] then
+ local using = sconn1[p_rail]
+ tp.bend_rail(p_railpos[p_rail], p_rail, nnpref)
+ advtrains.ndb.swap_node(pos, using)
+ local nname=using.name
+ if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
+ minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
+ end
+ return
+ end
+ if sconn2[p_rail] then
+ local using = sconn2[p_rail]
+ tp.bend_rail(p_railpos[p_rail], p_rail, nnpref)
+ advtrains.ndb.swap_node(pos, using)
+ local nname=using.name
+ if minetest.registered_nodes[nname] and minetest.registered_nodes[nname].after_place_node then
+ minetest.registered_nodes[nname].after_place_node(pos, placer, itemstack, pointed_thing)
+ end
+ return
+ end
+ end
+ end
+ --use default
+ minetest.set_node(pos, {name=nnpref.."_"..tr.default})
+ if minetest.registered_nodes[nnpref.."_"..tr.default] and minetest.registered_nodes[nnpref.."_"..tr.default].after_place_node then
+ minetest.registered_nodes[nnpref.."_"..tr.default].after_place_node(pos, placer, itemstack, pointed_thing)
+ end
+end
+
+
+function tp.register_track_placer(nnprefix, imgprefix, dispname, def)
+ minetest.register_craftitem(":"..nnprefix.."_placer",{
+ description = dispname,
+ inventory_image = imgprefix.."_placer.png",
+ wield_image = imgprefix.."_placer.png",
+ groups={advtrains_trackplacer=1, digtron_on_place=1},
+ liquids_pointable = def.liquids_pointable,
+ on_place = function(itemstack, placer, pointed_thing)
+ local name = placer:get_player_name()
+ if not name then
+ return itemstack, false
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.above
+ local upos=vector.subtract(pointed_thing.above, {x=0, y=1, z=0})
+ if not advtrains.check_track_protection(pos, name) then
+ return itemstack, false
+ end
+ if minetest.registered_nodes[minetest.get_node(pos).name] and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then
+ if def.suitable_substrate then
+ s = def.suitable_substrate(upos)
+ else
+ s = minetest.registered_nodes[minetest.get_node(upos).name] and minetest.registered_nodes[minetest.get_node(upos).name].walkable
+ end
+ if s then
+-- minetest.chat_send_all(nnprefix)
+ local yaw = placer:get_look_horizontal()
+ tp.placetrack(pos, nnprefix, placer, itemstack, pointed_thing, yaw)
+ if not advtrains.is_creative(name) then
+ itemstack:take_item()
+ end
+ end
+ end
+ end
+ return itemstack, true
+ end,
+ })
+end
+
+
+
+minetest.register_craftitem("advtrains:trackworker",{
+ description = attrans("Track Worker Tool\n\nLeft-click: change rail type (straight/curve/switch)\nRight-click: rotate rail/bumper/signal/etc."),
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "advtrains_trackworker.png",
+ wield_image = "advtrains_trackworker.png",
+ stack_max = 1,
+ on_place = function(itemstack, placer, pointed_thing)
+ local name = placer:get_player_name()
+ if not name then
+ return
+ end
+ local has_aux1_down = placer:get_player_control().aux1
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ if not advtrains.check_track_protection(pos, name) then
+ return
+ end
+ local node=minetest.get_node(pos)
+
+ --if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
+
+ local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
+ --atdebug(node.name.."\npattern recognizes:"..nnprefix.." / "..suffix.." / "..rotation)
+ --atdebug("nntab: ",tp.tracks[nnprefix])
+ if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twrotate[suffix] then
+ nnprefix, suffix=string.match(node.name, "^(.+)_([^_]+)$")
+ rotation = ""
+ if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twrotate[suffix] then
+ minetest.chat_send_player(placer:get_player_name(), attrans("This node can't be rotated using the trackworker!"))
+ return
+ end
+ end
+
+ -- check if the node is modify-protected
+ if advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then
+ -- is a track, we can query
+ local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
+ if not can_modify then
+ local str = attrans("This track can not be rotated!")
+ if reason then
+ str = str .. " " .. reason
+ end
+ minetest.chat_send_player(placer:get_player_name(), str)
+ return
+ end
+ end
+
+ if has_aux1_down then
+ --feature: flip the node by 180°
+ --i've always wanted this!
+ advtrains.ndb.swap_node(pos, {name=node.name, param2=(node.param2+2)%4})
+ return
+ end
+
+ local modext=tp.tracks[nnprefix].twrotate[suffix]
+
+ if rotation==modext[#modext] then --increase param2
+ advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[1], param2=(node.param2+1)%4})
+ return
+ else
+ local modpos
+ for k,v in pairs(modext) do
+ if v==rotation then modpos=k end
+ end
+ if not modpos then
+ minetest.chat_send_player(placer:get_player_name(), attrans("This node can't be rotated using the trackworker!"))
+ return
+ end
+ advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..suffix..modext[modpos+1], param2=node.param2})
+ end
+ end
+ end,
+ on_use=function(itemstack, user, pointed_thing)
+ local name = user:get_player_name()
+ if not name then
+ return
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ local node=minetest.get_node(pos)
+ if not advtrains.check_track_protection(pos, name) then
+ return
+ end
+
+ --if not advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then return end
+ if advtrains.get_train_at_pos(pos) then return end
+ local nnprefix, suffix, rotation=string.match(node.name, "^(.+)_([^_]+)(_[^_]+)$")
+ --atdebug(node.name.."\npattern recognizes:"..nodeprefix.." / "..railtype.." / "..rotation)
+ if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twcycle[suffix] then
+ nnprefix, suffix=string.match(node.name, "^(.+)_([^_]+)$")
+ rotation = ""
+ if not tp.tracks[nnprefix] or not tp.tracks[nnprefix].twcycle[suffix] then
+ minetest.chat_send_player(user:get_player_name(), attrans("This node can't be changed using the trackworker!"))
+ return
+ end
+ end
+
+ -- check if the node is modify-protected
+ if advtrains.is_track_and_drives_on(minetest.get_node(pos).name, advtrains.all_tracktypes) then
+ -- is a track, we can query
+ local can_modify, reason = advtrains.can_dig_or_modify_track(pos)
+ if not can_modify then
+ local str = attrans("This track can not be changed!")
+ if reason then
+ str = str .. " " .. reason
+ end
+ minetest.chat_send_player(user:get_player_name(), str)
+ return
+ end
+ end
+
+ local nextsuffix=tp.tracks[nnprefix].twcycle[suffix]
+ advtrains.ndb.swap_node(pos, {name=nnprefix.."_"..nextsuffix..rotation, param2=node.param2})
+
+ else
+ atprint(name, dump(tp.tracks))
+ end
+ end,
+})
+
+--putting into right place
+advtrains.trackplacer=tp
diff --git a/advtrains/tracks.lua b/advtrains/tracks.lua
new file mode 100644
index 0000000..33a7999
--- /dev/null
+++ b/advtrains/tracks.lua
@@ -0,0 +1,751 @@
+--advtrains by orwell96, see readme.txt
+
+--dev-time settings:
+--EDIT HERE
+--If the old non-model rails on straight tracks should be replaced by the new...
+--false: no
+--true: yes
+advtrains.register_replacement_lbms=false
+
+--[[TracksDefinition
+nodename_prefix
+texture_prefix
+description
+common={}
+straight={}
+straight45={}
+curve={}
+curve45={}
+lswitchst={}
+lswitchst45={}
+rswitchst={}
+rswitchst45={}
+lswitchcr={}
+lswitchcr45={}
+rswitchcr={}
+rswitchcr45={}
+vert1={
+ --you'll probably want to override mesh here
+}
+vert2={
+ --you'll probably want to override mesh here
+}
+]]--
+advtrains.all_tracktypes={}
+
+--definition preparation
+local function conns(c1, c2, r1, r2) return {{c=c1, y=r1}, {c=c2, y=r2}} end
+local function conns3(c1, c2, c3, r1, r2, r3) return {{c=c1, y=r1}, {c=c2, y=r2}, {c=c3, y=r3}} end
+
+advtrains.ap={}
+advtrains.ap.t_30deg_flat={
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,7),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,7),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swl",
+ },
+ swlcr={
+ conns = conns3(0,7,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swl",
+ },
+ swrst={
+ conns = conns3(0,8,9),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ switchprefix = "swr",
+ },
+ swrcr={
+ conns = conns3(0,9,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ switchprefix = "swr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_yturnout={
+ regstep=1,
+ variant={
+ l={
+ conns = conns3(0,7,9),
+ desc = "Y-turnout (left)",
+ switchalt = "r",
+ switchmc = "off",
+ switchst = "l",
+ switchprefix = "",
+ },
+ r={
+ conns = conns3(0,9,7),
+ desc = "Y-turnout (right)",
+ switchalt = "l",
+ switchmc = "on",
+ switchst = "r",
+ switchprefix = "",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_s3way={
+ regstep=1,
+ variant={
+ l={
+ conns = { {c=0}, {c=7}, {c=8}, {c=9}, {c=0} },
+ desc = "3-way turnout (left)",
+ switchalt = "s",
+ switchst="l",
+ switchprefix = "",
+ },
+ s={
+ conns = { {c=0}, {c=8}, {c=7}, {c=9}, {c=0} },
+ desc = "3-way turnout (straight)",
+ switchalt ="r",
+ switchst = "s",
+ switchprefix = "",
+ },
+ r={
+ conns = { {c=0}, {c=9}, {c=8}, {c=7}, {c=0} },
+ desc = "3-way turnout (right)",
+ switchalt = "l",
+ switchst="r",
+ switchprefix = "",
+ }
+ },
+ regtp=true,
+ tpdefault="l",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_30deg_slope={
+ regstep=1,
+ variant={
+ vst1={conns = conns(8,0,0,0.5), rail_y = 0.25, desc = "steep uphill 1/2", slope=true},
+ vst2={conns = conns(8,0,0.5,1), rail_y = 0.75, desc = "steep uphill 2/2", slope=true},
+ vst31={conns = conns(8,0,0,0.33), rail_y = 0.16, desc = "uphill 1/3", slope=true},
+ vst32={conns = conns(8,0,0.33,0.66), rail_y = 0.5, desc = "uphill 2/3", slope=true},
+ vst33={conns = conns(8,0,0.66,1), rail_y = 0.83, desc = "uphill 3/3", slope=true},
+ },
+ regsp=true,
+ slopeplacer={
+ [2]={"vst1", "vst2"},
+ [3]={"vst31", "vst32", "vst33"},
+ max=3,--highest entry
+ },
+ slopeplacer_45={
+ [2]={"vst1_45", "vst2_45"},
+ max=2,
+ },
+ rotation={"", "_30", "_45", "_60"},
+ trackworker={},
+ increativeinv={},
+}
+advtrains.ap.t_30deg_straightonly={
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_30deg_straightonly_noplacer={
+ regstep=1,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_45deg={
+ regstep=2,
+ variant={
+ st={
+ conns = conns(0,8),
+ desc = "straight",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "cr",
+ },
+ cr={
+ conns = conns(0,6),
+ desc = "curve",
+ tpdouble = true,
+ trackworker = "swlst",
+ },
+ swlst={
+ conns = conns3(0,8,6),
+ desc = "left switch (straight)",
+ trackworker = "swrst",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swlcr={
+ conns = conns3(0,6,8),
+ desc = "left switch (curve)",
+ trackworker = "swrcr",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ swrst={
+ conns = conns3(0,8,10),
+ desc = "right switch (straight)",
+ trackworker = "st",
+ switchalt = "cr",
+ switchmc = "on",
+ switchst = "st",
+ },
+ swrcr={
+ conns = conns3(0,10,8),
+ desc = "right switch (curve)",
+ trackworker = "st",
+ switchalt = "st",
+ switchmc = "off",
+ switchst = "cr",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ trackworker={
+ ["swrcr"]="st",
+ ["swrst"]="st",
+ ["cr"]="swlst",
+ ["swlcr"]="swrcr",
+ ["swlst"]="swrst",
+ },
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_perpcrossing={
+ regstep = 1,
+ variant={
+ st={
+ conns = { {c=0}, {c=8}, {c=4}, {c=12} },
+ desc = "perpendicular crossing",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "st",
+ },
+ },
+ regtp=true,
+ tpdefault="st",
+ rotation={"", "_30", "_45", "_60"},
+}
+advtrains.ap.t_90plusx_crossing={
+ regstep = 1,
+ variant={
+ ["30l"]={
+ conns = { {c=0}, {c=8}, {c=1}, {c=9} },
+ desc = "30/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45l"
+ },
+ ["45l"]={
+ conns = { {c=0}, {c=8}, {c=2}, {c=10} },
+ desc = "45/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60l",
+ },
+ ["60l"]={
+ conns = { {c=0}, {c=8}, {c=3}, {c=11}},
+ desc = "60/90 degree crossing (left)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "60r",
+ },
+ ["60r"]={
+ conns = { {c=0}, {c=8}, {c=5}, {c=13} },
+ desc = "60/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "45r"
+ },
+ ["45r"]={
+ conns = { {c=0}, {c=8}, {c=6}, {c=14} },
+ desc = "45/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30r",
+ },
+ ["30r"]={
+ conns = { {c=0}, {c=8}, {c=7}, {c=15}},
+ desc = "30/90 degree crossing (right)",
+ tpdouble = true,
+ tpsingle = true,
+ trackworker = "30l",
+ },
+ },
+ regtp=true,
+ tpdefault="30l",
+ rotation={""},
+ trackworker = {
+ ["30l"] = "45l",
+ ["45l"] = "60l",
+ ["60l"] = "60r",
+ ["60r"] = "45r",
+ ["45r"] = "30r",
+ ["30r"] = "30l",
+ }
+}
+
+advtrains.ap.t_diagonalcrossing = {
+ regstep=1,
+ variant={
+ ["30l45r"]={
+ conns = {{c=1}, {c=9}, {c=6}, {c=14}},
+ desc = "30left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l30l",
+ },
+ ["60l30l"]={
+ conns = {{c=3}, {c=11}, {c=1}, {c=9}},
+ desc = "30left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l45r"
+ },
+ ["60l45r"]={
+ conns = {{c=3}, {c=11}, {c=6}, {c=14}},
+ desc = "60left-45right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60l60r"
+ },
+ ["60l60r"]={
+ conns = {{c=3}, {c=11}, {c=5}, {c=13}},
+ desc = "60left-60right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r45l",
+ },
+ --If 60l60r had a mirror image, it would be here, but it's symmetric.
+ -- 60l60r is also equivalent to 30l30r but rotated 90 degrees.
+ ["60r45l"]={
+ conns = {{c=5}, {c=13}, {c=2}, {c=10}},
+ desc = "60right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="60r30r",
+ },
+ ["60r30r"]={
+ conns = {{c=5}, {c=13}, {c=7}, {c=15}},
+ desc = "60right-30right diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30r45l",
+ },
+ ["30r45l"]={
+ conns = {{c=7}, {c=15}, {c=2}, {c=10}},
+ desc = "30right-45left diagonal crossing",
+ tpdouble=true,
+ tpsingle=true,
+ trackworker="30l45r",
+ },
+
+ },
+ regtp=true,
+ tpdefault="30l45r",
+ rotation={""},
+ trackworker = {
+ ["30l45r"] = "60l30l",
+ ["60l30l"] = "60l45r",
+ ["60l45r"] = "60l60r",
+ ["60l60r"] = "60r45l",
+ ["60r45l"] = "60r30r",
+ ["60r30r"] = "30r45l",
+ ["30r45l"] = "30l45r",
+ }
+}
+
+advtrains.trackpresets = advtrains.ap
+
+--definition format: ([] optional)
+--[[{
+ nodename_prefix
+ texture_prefix
+ [shared_texture]
+ models_prefix
+ models_suffix (with dot)
+ [shared_model]
+ formats={
+ st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
+ (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
+ }
+ common={} change something on common rail appearance
+}
+[18.12.17] Note on new connection system:
+In order to support real rail crossing nodes and finally make the trackplacer respect switches, I changed the connection system.
+There can be a variable number of connections available. These are specified as tuples {c=<connection>, y=<rely>}
+The table "at_conns" consists of {<conn1>, <conn2>...}
+the "at_rail_y" property holds the value that was previously called "railheight"
+Depending on the number of connections:
+2 conns: regular rail
+3 conns: switch:
+ - when train passes in at conn1, will move out of conn2
+ - when train passes in at conn2 or conn3, will move out of conn1
+4 conns: cross (or cross switch, depending on arrangement of conns):
+ - conn1 <> conn2
+ - conn3 <> conn4
+]]
+
+-- Notify the user if digging the rail is not allowed
+local function can_dig_callback(pos, player)
+ local ok, reason = advtrains.can_dig_or_modify_track(pos)
+ if not ok and player then
+ minetest.chat_send_player(player:get_player_name(), attrans("This track can not be removed!") .. " " .. reason)
+ end
+ return ok
+end
+
+function advtrains.register_tracks(tracktype, def, preset)
+ advtrains.trackplacer.register_tracktype(def.nodename_prefix, preset.tpdefault)
+ if preset.regtp then
+ advtrains.trackplacer.register_track_placer(def.nodename_prefix, def.texture_prefix, def.description, def)
+ end
+ if preset.regsp then
+ advtrains.slope.register_placer(def, preset)
+ end
+ for suffix, var in pairs(preset.variant) do
+ for rotid, rotation in ipairs(preset.rotation) do
+ if not def.formats[suffix] or def.formats[suffix][rotid] then
+ local img_suffix = suffix..rotation
+ local ndef = advtrains.merge_tables({
+ description=def.description.."("..(var.desc or "any")..rotation..")",
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
+ },
+
+ mesh = def.shared_model or (def.models_prefix.."_"..img_suffix..def.models_suffix),
+ tiles = {def.shared_texture or (def.texture_prefix.."_"..img_suffix..".png"), def.second_texture},
+
+ groups = {
+ attached_node = advtrains.IGNORE_WORLD and 0 or 1,
+ advtrains_track=1,
+ ["advtrains_track_"..tracktype]=1,
+ save_in_at_nodedb=1,
+ dig_immediate=2,
+ not_in_creative_inventory=1,
+ not_blocking_trains=1,
+ },
+
+ can_dig = can_dig_callback,
+ after_dig_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ after_place_node=function(pos)
+ advtrains.ndb.update(pos)
+ end,
+ at_nnpref = def.nodename_prefix,
+ at_suffix = suffix,
+ at_rotation = rotation,
+ at_rail_y = var.rail_y
+ }, def.common or {})
+
+ if preset.regtp then
+ ndef.drop = def.nodename_prefix.."_placer"
+ end
+ if preset.regsp and var.slope then
+ ndef.drop = def.nodename_prefix.."_slopeplacer"
+ end
+
+ --connections
+ ndef.at_conns = advtrains.rotate_conn_by(var.conns, (rotid-1)*preset.regstep)
+
+ local ndef_avt_table
+
+ if var.switchalt and var.switchst then
+ local switchfunc=function(pos, node, newstate)
+ newstate = newstate or var.switchalt -- support for 3 (or more) state switches
+ -- this code is only called from the internal setstate function, which
+ -- ensures that it is safe to switch the turnout
+ if newstate~=var.switchst then
+ advtrains.ndb.swap_node(pos, {name=def.nodename_prefix.."_"..(var.switchprefix or "")..newstate..rotation, param2=node.param2})
+ advtrains.invalidate_all_paths(pos)
+ end
+ end
+ ndef.on_rightclick = function(pos, node, player)
+ if advtrains.check_turnout_signal_protection(pos, player:get_player_name()) then
+ advtrains.setstate(pos, newstate, node)
+ advtrains.log("Switch", player:get_player_name(), pos)
+ end
+ end
+ if var.switchmc then
+ ndef.mesecons = {effector = {
+ ["action_"..var.switchmc] = function(pos, node)
+ advtrains.setstate(pos, nil, node)
+ end,
+ rules=advtrains.meseconrules
+ }}
+ end
+ ndef_avt_table = {
+ getstate = var.switchst,
+ setstate = switchfunc,
+ }
+ end
+
+ local adef={}
+ if def.get_additional_definiton then
+ adef=def.get_additional_definiton(def, preset, suffix, rotation)
+ end
+ ndef = advtrains.merge_tables(ndef, adef)
+
+ -- insert getstate/setstate functions after merging the additional definitions
+ if ndef_avt_table then
+ ndef.advtrains = advtrains.merge_tables(ndef.advtrains or {}, ndef_avt_table)
+ end
+
+ minetest.register_node(":"..def.nodename_prefix.."_"..suffix..rotation, ndef)
+ --trackplacer
+ if preset.regtp then
+ local tpconns = {conn1=ndef.at_conns[1].c, conn2=ndef.at_conns[2].c}
+ if var.tpdouble then
+ advtrains.trackplacer.add_double_conn(def.nodename_prefix, suffix, rotation, tpconns)
+ end
+ if var.tpsingle then
+ advtrains.trackplacer.add_single_conn(def.nodename_prefix, suffix, rotation, tpconns)
+ end
+ end
+ advtrains.trackplacer.add_worked(def.nodename_prefix, suffix, rotation, var.trackworker)
+ end
+ end
+ end
+ advtrains.all_tracktypes[tracktype]=true
+end
+
+function advtrains.is_track_and_drives_on(nodename, drives_on_p)
+ local drives_on = drives_on_p
+ if not drives_on then drives_on = advtrains.all_tracktypes end
+ local hasentry = false
+ for _,_ in pairs(drives_on) do
+ hasentry=true
+ end
+ if not hasentry then drives_on = advtrains.all_tracktypes end
+
+ if not minetest.registered_nodes[nodename] then
+ return false
+ end
+ local nodedef=minetest.registered_nodes[nodename]
+ for k,v in pairs(drives_on) do
+ if nodedef.groups["advtrains_track_"..k] then
+ return true
+ end
+ end
+ return false
+end
+
+function advtrains.get_track_connections(name, param2)
+ local nodedef=minetest.registered_nodes[name]
+ if not nodedef then atprint(" get_track_connections couldn't find nodedef for nodename "..(name or "nil")) return nil end
+ local noderot=param2
+ if not param2 then noderot=0 end
+ if noderot > 3 then atprint(" get_track_connections: rail has invaild param2 of "..noderot) noderot=0 end
+
+ local tracktype
+ for k,_ in pairs(nodedef.groups) do
+ local tt=string.match(k, "^advtrains_track_(.+)$")
+ if tt then
+ tracktype=tt
+ end
+ end
+ return advtrains.rotate_conn_by(nodedef.at_conns, noderot*AT_CMAX/4), (nodedef.at_rail_y or 0), tracktype
+end
+
+-- Function called when a track is about to be dug or modified by the trackworker
+-- Returns either true (ok) or false,"translated string describing reason why it isn't allowed"
+function advtrains.can_dig_or_modify_track(pos)
+ if advtrains.get_train_at_pos(pos) then
+ return false, attrans("Position is occupied by a train.")
+ end
+ -- interlocking: tcb, signal IP a.s.o.
+ if advtrains.interlocking then
+ -- TCB?
+ if advtrains.interlocking.db.get_tcb(pos) then
+ return false, attrans("There's a Track Circuit Break here.")
+ end
+ -- signal ip?
+ if advtrains.interlocking.db.is_ip_at(pos, true) then -- is_ip_at with purge parameter
+ return false, attrans("There's a Signal Influence Point here.")
+ end
+ end
+ return true
+end
+
+-- slope placer. Defined in register_tracks.
+--crafted with rail and gravel
+local sl={}
+function sl.register_placer(def, preset)
+ minetest.register_craftitem(":"..def.nodename_prefix.."_slopeplacer",{
+ description = attrans("@1 Slope", def.description),
+ inventory_image = def.texture_prefix.."_slopeplacer.png",
+ wield_image = def.texture_prefix.."_slopeplacer.png",
+ groups={},
+ on_place = sl.create_slopeplacer_on_place(def, preset)
+ })
+end
+--(itemstack, placer, pointed_thing)
+function sl.create_slopeplacer_on_place(def, preset)
+ return function(istack, player, pt)
+ if not pt.type=="node" then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
+ return istack
+ end
+ local pos=pt.above
+ if not pos then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: not pointing at node"))
+ return istack
+ end
+ local node=minetest.get_node(pos)
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to then
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: space occupied!"))
+ return istack
+ end
+ if not advtrains.check_track_protection(pos, player:get_player_name()) then
+ minetest.record_protection_violation(pos, player:get_player_name())
+ return istack
+ end
+ --determine player orientation (only horizontal component)
+ --get_look_horizontal may not be available
+ local yaw=player.get_look_horizontal and player:get_look_horizontal() or (player:get_look_yaw() - math.pi/2)
+
+ --rounding unit vectors is a nice way for selecting 1 of 8 directions since sin(30°) is 0.5.
+ dirvec={x=math.floor(math.sin(-yaw)+0.5), y=0, z=math.floor(math.cos(-yaw)+0.5)}
+ --translate to direction to look up inside the preset table
+ local param2, rot45=({
+ [-1]={
+ [-1]=2,
+ [0]=3,
+ [1]=3,
+ },
+ [0]={
+ [-1]=2,
+ [1]=0,
+ },
+ [1]={
+ [-1]=1,
+ [0]=1,
+ [1]=0,
+ },
+ })[dirvec.x][dirvec.z], dirvec.x~=0 and dirvec.z~=0
+ local lookup=preset.slopeplacer
+ if rot45 then lookup=preset.slopeplacer_45 end
+
+ --go unitvector forward and look how far the next node is
+ local step=1
+ while step<=lookup.max do
+ local node=minetest.get_node(vector.add(pos, dirvec))
+ --next node solid?
+ if not minetest.registered_nodes[node.name] or not minetest.registered_nodes[node.name].buildable_to or advtrains.is_protected(pos, player:get_player_name()) then
+ --do slopes of this distance exist?
+ if lookup[step] then
+ if minetest.settings:get_bool("creative_mode") or istack:get_count()>=step then
+ --start placing
+ local placenodes=lookup[step]
+ while step>0 do
+ minetest.set_node(pos, {name=def.nodename_prefix.."_"..placenodes[step], param2=param2})
+ if not minetest.settings:get_bool("creative_mode") then
+ istack:take_item()
+ end
+ step=step-1
+ pos=vector.subtract(pos, dirvec)
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: Not enough slope items left (@1 required)", step))
+ end
+ else
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: There's no slope of length @1",step))
+ end
+ return istack
+ end
+ step=step+1
+ pos=vector.add(pos, dirvec)
+ end
+ minetest.chat_send_player(player:get_player_name(), attrans("Can't place: no supporting node at upper end."))
+ return itemstack
+ end
+end
+
+advtrains.slope=sl
+
+--END code, BEGIN definition
+--definition format: ([] optional)
+--[[{
+ nodename_prefix
+ texture_prefix
+ [shared_texture]
+ models_prefix
+ models_suffix (with dot)
+ [shared_model]
+ formats={
+ st,cr,swlst,swlcr,swrst,swrcr,vst1,vst2
+ (each a table with indices 0-3, for if to register a rail with this 'rotation' table entry. nil is assumed as 'all', set {} to not register at all)
+ }
+ common={} change something on common rail appearance
+}]]
+
+
+
+
+
+
+
+
+
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
new file mode 100644
index 0000000..6e69455
--- /dev/null
+++ b/advtrains/trainhud.lua
@@ -0,0 +1,332 @@
+--trainhud.lua: holds all the code for train controlling
+
+advtrains.hud = {}
+advtrains.hhud = {}
+
+minetest.register_on_leaveplayer(function(player)
+advtrains.hud[player:get_player_name()] = nil
+advtrains.hhud[player:get_player_name()] = nil
+end)
+
+local mletter={[1]="F", [-1]="R", [0]="N"}
+
+function advtrains.on_control_change(pc, train, flip)
+ local maxspeed = train.max_speed or 10
+ if pc.sneak then
+ if pc.up then
+ train.tarvelocity = maxspeed
+ end
+ if pc.down then
+ train.tarvelocity = 0
+ end
+ if pc.left then
+ train.tarvelocity = 4
+ end
+ if pc.right then
+ train.tarvelocity = 8
+ end
+ --[[if pc.jump then
+ train.brake = true
+ --0: released, 1: brake and pressed, 2: released and brake, 3: pressed and brake
+ if not train.brake_hold_state or train.brake_hold_state==0 then
+ train.brake_hold_state = 1
+ elseif train.brake_hold_state==2 then
+ train.brake_hold_state = 3
+ end
+ elseif train.brake_hold_state==1 then
+ train.brake_hold_state = 2
+ elseif train.brake_hold_state==3 then
+ train.brake = false
+ train.brake_hold_state = 0
+ end]]
+ --shift+use:see wagons.lua
+ else
+ local act=false
+ if pc.jump then
+ train.ctrl_user = 1
+ act=true
+ end
+ -- If atc command set, only "Jump" key can clear command. To prevent accidental control.
+ if train.tarvelocity or train.atc_command then
+ return
+ end
+ if pc.up then
+ train.ctrl_user=4
+ act=true
+ end
+ if pc.down then
+ if train.velocity>0 then
+ if pc.jump then
+ train.ctrl_user = 0
+ else
+ train.ctrl_user = 2
+ end
+ act=true
+ else
+ advtrains.invert_train(train.id)
+ advtrains.atc.train_reset_command(train)
+ end
+ end
+ if pc.left then
+ if train.door_open ~= 0 then
+ train.door_open = 0
+ else
+ train.door_open = -1
+ end
+ end
+ if pc.right then
+ if train.door_open ~= 0 then
+ train.door_open = 0
+ else
+ train.door_open = 1
+ end
+ end
+ if not act then
+ train.ctrl_user = nil
+ end
+ end
+end
+function advtrains.update_driver_hud(pname, train, flip)
+ local inside=train.text_inside or ""
+ local ft, ht = advtrains.hud_train_format(train, flip)
+ advtrains.set_trainhud(pname, inside.."\n"..ft, ht)
+end
+function advtrains.clear_driver_hud(pname)
+ advtrains.set_trainhud(pname, "")
+end
+
+function advtrains.set_trainhud(name, text, driver)
+ local hud = advtrains.hud[name]
+ local player=minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+ local driverhud = {
+ hud_elem_type = "image",
+ name = "ADVTRAINS_DRIVER",
+ position = {x=0.5, y=1},
+ offset = {x=0,y=-170},
+ text = driver or "",
+ alignment = {x=0,y=-1},
+ scale = {x=1,y=1},}
+ if not hud then
+ hud = {["driver"]={}}
+ advtrains.hud[name] = hud
+ hud.id = player:hud_add({
+ hud_elem_type = "text",
+ name = "ADVTRAINS",
+ number = 0xFFFFFF,
+ position = {x=0.5, y=1},
+ offset = {x=0, y=-300},
+ text = text,
+ scale = {x=200, y=60},
+ alignment = {x=0, y=-1},
+ })
+ hud.oldText=text
+ hud.driver = player:hud_add(driverhud)
+ else
+ if hud.oldText ~= text then
+ player:hud_change(hud.id, "text", text)
+ hud.oldText=text
+ end
+ if hud.driver then
+ player:hud_change(hud.driver, "text", driver or "")
+ elseif driver then
+ hud.driver = player:hud_add(driverhud)
+ end
+ end
+end
+
+function advtrains.set_help_hud(name, text)
+ local hud = advtrains.hhud[name]
+ local player=minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+ if not hud then
+ hud = {}
+ advtrains.hhud[name] = hud
+ hud.id = player:hud_add({
+ hud_elem_type = "text",
+ name = "ADVTRAINS_HELP",
+ number = 0xFFFFFF,
+ position = {x=1, y=0.3},
+ offset = {x=0, y=0},
+ text = text,
+ scale = {x=200, y=60},
+ alignment = {x=1, y=0},
+ })
+ hud.oldText=text
+ return
+ elseif hud.oldText ~= text then
+ player:hud_change(hud.id, "text", text)
+ hud.oldText=text
+ end
+end
+
+--train.lever:
+--Speed control lever in train, for new train control system.
+--[[
+Value Disp Control Meaning
+0 BB S+Space Emergency Brake
+1 B Space Normal Brake
+2 - S Roll
+3 o <none> Stay at speed
+4 + W Accelerate
+]]
+
+function advtrains.hud_train_format(train, flip)
+ if not train then return "","" end
+ local sformat = string.format -- this appears to be faster than (...):format
+
+ local max = train.max_speed or 10
+ local res = train.speed_restriction
+ local vel = advtrains.abs_ceil(train.velocity)
+ local vel_kmh=advtrains.abs_ceil(advtrains.ms_to_kmh(train.velocity))
+
+ local tlev=train.lever or 1
+ if train.velocity==0 and not train.active_control then tlev=1 end
+ if train.hud_lzb_effect_tmr then
+ tlev=1
+ end
+
+ local ht = {"[combine:440x110:0,0=(advtrains_hud_bg.png^[resize\\:440x110)"}
+ local st = {}
+ if train.debug then st = {train.debug} end
+
+ -- seven-segment display
+ local function sevenseg(digit, x, y, w, h, m)
+ --[[
+ -1-
+ 2 3
+ -4-
+ 5 6
+ -7-
+ ]]
+ local segs = {
+ {h, 0, w, h},
+ {0, h, h, w},
+ {w+h, h, h, w},
+ {h, w+h, w, h},
+ {0, w+2*h, h, w},
+ {w+h, w+2*h, h, w},
+ {h, 2*(w+h), w, h}}
+ local trans = {
+ [0] = {true, true, true, false, true, true, true},
+ [1] = {false, false, true, false, false, true, false},
+ [2] = {true, false, true, true, true, false, true},
+ [3] = {true, false, true, true, false, true, true},
+ [4] = {false, true, true, true, false, true, false},
+ [5] = {true, true, false, true, false, true, true},
+ [6] = {true, true, false, true, true, true, true},
+ [7] = {true, false, true, false, false, true, false},
+ [8] = {true, true, true, true, true, true, true},
+ [9] = {true, true, true, true, false, true, true}}
+ local ent = trans[digit or 10]
+ if not ent then return end
+ for i = 1, 7, 1 do
+ if ent[i] then
+ local s = segs[i]
+ ht[#ht+1] = sformat("%d,%d=(advtrains_hud_bg.png^[resize\\:%dx%d^%s)",x+s[1], y+s[2], s[3], s[4], m)
+ end
+ end
+ end
+
+ -- lever
+ ht[#ht+1] = "275,10=(advtrains_hud_bg.png^[colorize\\:cyan^[resize\\:5x18)"
+ ht[#ht+1] = "275,28=(advtrains_hud_bg.png^[colorize\\:white^[resize\\:5x18)"
+ ht[#ht+1] = "275,46=(advtrains_hud_bg.png^[colorize\\:orange^[resize\\:5x36)"
+ ht[#ht+1] = "275,82=(advtrains_hud_bg.png^[colorize\\:red^[resize\\:5x18)"
+ ht[#ht+1] = "292,16=(advtrains_hud_bg.png^[colorize\\:darkslategray^[resize\\:6x78)"
+ ht[#ht+1] = sformat("280,%s=(advtrains_hud_bg.png^[colorize\\:gray^[resize\\:30x18)",18*(4-tlev)+10)
+ -- reverser
+ ht[#ht+1] = sformat("245,10=(advtrains_hud_arrow.png^[transformFY%s)", flip and "" or "^[multiply\\:cyan")
+ ht[#ht+1] = sformat("245,85=(advtrains_hud_arrow.png%s)", flip and "^[multiply\\:orange" or "")
+ ht[#ht+1] = "250,35=(advtrains_hud_bg.png^[colorize\\:darkslategray^[resize\\:5x40)"
+ ht[#ht+1] = sformat("240,%s=(advtrains_hud_bg.png^[resize\\:25x15^[colorize\\:gray)", flip and 65 or 30)
+ -- train control/safety indication
+ if train.tarvelocity or train.atc_command then
+ ht[#ht+1] = "10,10=(advtrains_hud_atc.png^[resize\\:30x30^[multiply\\:cyan)"
+ end
+ if train.hud_lzb_effect_tmr then
+ ht[#ht+1] = "50,10=(advtrains_hud_lzb.png^[resize\\:30x30^[multiply\\:red)"
+ end
+ if train.is_shunt then
+ ht[#ht+1] = "90,10=(advtrains_hud_shunt.png^[resize\\:30x30^[multiply\\:orange)"
+ end
+ -- door
+ ht[#ht+1] = "187,10=(advtrains_hud_bg.png^[resize\\:26x30^[colorize\\:white)"
+ ht[#ht+1] = "189,12=(advtrains_hud_bg.png^[resize\\:22x11)"
+ ht[#ht+1] = sformat("170,10=(advtrains_hud_bg.png^[resize\\:15x30^[colorize\\:%s)", train.door_open==-1 and "white" or "darkslategray")
+ ht[#ht+1] = "172,12=(advtrains_hud_bg.png^[resize\\:11x11)"
+ ht[#ht+1] = sformat("215,10=(advtrains_hud_bg.png^[resize\\:15x30^[colorize\\:%s)", train.door_open==1 and "white" or "darkslategray")
+ ht[#ht+1] = "217,12=(advtrains_hud_bg.png^[resize\\:11x11)"
+ -- speed indication(s)
+ sevenseg(math.floor(vel/10), 320, 10, 30, 10, "[colorize\\:red\\:255")
+ sevenseg(vel%10, 380, 10, 30, 10, "[colorize\\:red\\:255")
+ for i = 1, vel, 1 do
+ ht[#ht+1] = sformat("%d,65=(advtrains_hud_bg.png^[resize\\:8x20^[colorize\\:white)", i*11-1)
+ end
+ for i = max+1, 20, 1 do
+ ht[#ht+1] = sformat("%d,65=(advtrains_hud_bg.png^[resize\\:8x20^[colorize\\:darkslategray)", i*11-1)
+ end
+ if res and res > 0 then
+ ht[#ht+1] = sformat("%d,60=(advtrains_hud_bg.png^[resize\\:3x30^[colorize\\:red\\:255)", 7+res*11)
+ end
+ if train.tarvelocity then
+ ht[#ht+1] = sformat("%d,85=(advtrains_hud_arrow.png^[multiply\\:cyan^[transformFY^[makealpha\\:#000000)", 1+train.tarvelocity*11)
+ end
+ local lzb = train.lzb
+ if lzb and lzb.checkpoints then
+ local oc = lzb.checkpoints
+ for i = 1, #oc do
+ local spd = oc[i].speed
+ local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil
+ if c then
+ ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
+ ht[#ht+1] = sformat("130,35=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
+ if spd and spd~=0 then
+ ht[#ht+1] = sformat("%d,50=(advtrains_hud_arrow.png^[multiply\\:red^[makealpha\\:#000000)", 1+spd*11)
+ end
+ local floor = math.floor
+ local dist = floor(((oc[i].index or train.index)-train.index))
+ dist = math.max(0, math.min(999, dist))
+ for j = 1, 3, 1 do
+ sevenseg(floor((dist/10^(3-j))%10), 119+j*11, 18, 4, 2, "[colorize\\:"..c)
+ end
+ break
+ end
+ end
+ end
+
+ if res and res == 0 then
+ st[#st+1] = attrans("OVERRUN RED SIGNAL! Examine situation and reverse train to move again.")
+ end
+
+ if train.atc_command then
+ st[#st+1] = sformat("ATC: %s%s", train.atc_delay and advtrains.abs_ceil(train.atc_delay).."s " or "", train.atc_command or "")
+ end
+
+ return table.concat(st,"\n"), table.concat(ht,":")
+end
+
+local _, texture = advtrains.hud_train_format { -- dummy train object to demonstrate the train hud
+ max_speed = 15, speed_restriction = 15, velocity = 15, tarvelocity = 12,
+ active_control = true, lever = 3, ctrl = {lzb = true}, is_shunt = true,
+ door_open = 1, lzb = {oncoming = {{spd=6, idx=125.7}}}, index = 0,
+}
+
+minetest.register_node("advtrains:hud_demo",{
+ description = "Train HUD demonstration",
+ tiles = {texture},
+ groups = {cracky = 3, not_in_creative_inventory = 1}
+})
+
+minetest.register_craft {
+ output = "advtrains:hud_demo",
+ recipe = {
+ {"default:paper", "default:paper", "default:paper"},
+ {"default:paper", "advtrains:trackworker", "default:paper"},
+ {"default:paper", "default:paper", "default:paper"},
+ }
+}
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
new file mode 100644
index 0000000..c9c7e76
--- /dev/null
+++ b/advtrains/trainlogic.lua
@@ -0,0 +1,1389 @@
+--trainlogic.lua
+--controls train entities stuff about connecting/disconnecting/colliding trains and other things
+
+local setting_overrun_mode = minetest.settings:get("advtrains_overrun_mode")
+
+local benchmark=false
+local bm={}
+local bmlt=0
+local bmsteps=0
+local bmstepint=200
+atprintbm=function(action, ta)
+ if not benchmark then return end
+ local t=(os.clock()-ta)*1000
+ if not bm[action] then
+ bm[action]=t
+ else
+ bm[action]=bm[action]+t
+ end
+ bmlt=bmlt+t
+end
+function endstep()
+ if not benchmark then return end
+ bmsteps=bmsteps-1
+ if bmsteps<=0 then
+ bmsteps=bmstepint
+ for key, value in pairs(bm) do
+ minetest.chat_send_all(key.." "..(value/bmstepint).." ms avg.")
+ end
+ minetest.chat_send_all("Total time consumed by all advtrains actions per step: "..(bmlt/bmstepint).." ms avg.")
+ bm={}
+ bmlt=0
+ end
+end
+
+--acceleration for lever modes (trainhud.lua), per wagon
+local t_accel_all={
+ [0] = -10,
+ [1] = -3,
+ [11] = -2, -- calculation base for LZB
+ [2] = -0.5,
+ [4] = 0.5,
+}
+--acceleration per engine
+local t_accel_eng={
+ [0] = 0,
+ [1] = 0,
+ [11] = 0,
+ [2] = 0,
+ [4] = 1.5,
+}
+
+local VLEVER_EMERG = 0
+local VLEVER_BRAKE = 1
+local VLEVER_LZBCALC = 11
+local VLEVER_ROLL = 2
+local VLEVER_HOLD = 3
+local VLEVER_ACCEL = 4
+
+-- How far in front of a whole index with LZB 0 restriction the train should come to a halt
+-- value must be between 0 and 0.5, exclusively
+local LZB_ZERO_APPROACH_DIST = 0.1
+-- Speed the train temporarily approaches the stop point with
+local LZB_ZERO_APPROACH_SPEED = 0.2
+
+
+
+tp_player_tmr = 0
+
+advtrains.mainloop_trainlogic=function(dtime, stepno)
+ --build a table of all players indexed by pts. used by damage and door system.
+ advtrains.playersbypts={}
+ for _, player in pairs(minetest.get_connected_players()) do
+ if not advtrains.player_to_train_mapping[player:get_player_name()] then
+ --players in train are not subject to damage
+ local ptspos=minetest.pos_to_string(vector.round(player:get_pos()))
+ advtrains.playersbypts[ptspos]=player
+ end
+ end
+
+ if tp_player_tmr<=0 then
+ -- teleport players to their train every 2 seconds
+ for _, player in pairs(minetest.get_connected_players()) do
+ advtrains.tp_player_to_train(player)
+ end
+ tp_player_tmr = 2
+ else
+ tp_player_tmr = tp_player_tmr - dtime
+ end
+ --regular train step
+ --[[ structure:
+ 1. make trains calculate their occupation windows when needed (a)
+ 2. when occupation tells us so, restore the occupation tables (a)
+ 4. make trains move and update their new occupation windows and write changes
+ to occupation tables (b)
+ 5. make trains do other stuff (c)
+ ]]--
+ local t=os.clock()
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ --atprint("=== Step",stepno,"===")
+ advtrains.train_ensure_init(k, v)
+ end
+
+ advtrains.lock_path_inval = true
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ advtrains.train_step_b(k, v, dtime)
+ end
+
+ for k,v in pairs(advtrains.trains) do
+ advtrains.atprint_context_tid=k
+ advtrains.train_step_c(k, v, dtime)
+ end
+
+ advtrains.lock_path_inval = false
+
+ advtrains.atprint_context_tid=nil
+
+ atprintbm("trainsteps", t)
+ endstep()
+end
+
+function advtrains.tp_player_to_train(player)
+ local pname = player:get_player_name()
+ local id=advtrains.player_to_train_mapping[pname]
+ if id then
+ local train=advtrains.trains[id]
+ if not train then advtrains.player_to_train_mapping[pname]=nil return end
+ --set the player to the train position.
+ --minetest will emerge the area and load the objects, which then will call reattach_all().
+ --because player is in mapping, it will not be subject to dying.
+ player:set_pos(train.last_pos)
+ end
+end
+minetest.register_on_joinplayer(function(player)
+ advtrains.hud[player:get_player_name()] = nil
+ advtrains.hhud[player:get_player_name()] = nil
+ --independent of this, cause all wagons of the train which are loaded to reattach their players
+ --needed because already loaded wagons won't call reattach_all()
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
+ wagon:reattach_all()
+ end
+ end
+end)
+
+
+minetest.register_on_dieplayer(function(player)
+ local pname=player:get_player_name()
+ local id=advtrains.player_to_train_mapping[pname]
+ if id then
+ local train=advtrains.trains[id]
+ if not train then advtrains.player_to_train_mapping[pname]=nil return end
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
+ --when player dies, detach him from the train
+ --call get_off_plr on every wagon since we don't know which one he's on.
+ wagon:get_off_plr(pname)
+ end
+ end
+ end
+end)
+
+--[[
+
+Zone diagram of a train (copy from occupation.lua!):
+ |___| |___| --> Direction of travel
+ oo oo+oo oo
+=|=======|===|===========|===|=======|===================|========|===
+ |SafetyB|CpB| Train |CpF|SafetyF| Brake |Aware |
+[1] [2] [3] [4] [5] [6] [7] [8]
+This mapping from indices in occwindows to zone ids is contained in WINDOW_ZONE_IDS
+
+
+The occupation system has been abandoned. The constants will still be used
+to determine the couple distance
+(because of the reverse lookup, the couple system simplifies a lot...)
+
+]]--
+-- unless otherwise stated, in meters.
+local SAFETY_ZONE = 10
+local COUPLE_ZONE = 2 --value in index positions!
+local BRAKE_SPACE = 10
+local AWARE_ZONE = 10
+local WINDOW_ZONE_IDS = {
+ 2, -- 1 - SafetyB
+ 4, -- 2 - CpB
+ 1, -- 3 - Train
+ 5, -- 4 - CpF
+ 3, -- 5 - SafetyF
+ 6, -- 6 - Brake
+ 7, -- 7 - Aware
+}
+
+
+-- If a variable does not exist in the table, it is assigned the default value
+local function assertdef(tbl, var, def)
+ if not tbl[var] then
+ tbl[var] = def
+ end
+end
+
+function advtrains.get_acceleration(train, lever)
+ local acc_all = t_accel_all[lever]
+ if not acc_all then return 0 end
+
+ local acc_eng = t_accel_eng[lever]
+ local nwagons = #train.trainparts
+ if nwagons == 0 then
+ -- empty train! avoid division through zero
+ return -1
+ end
+ local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
+ return acc
+end
+
+-- Small local util function to recalculate train's end index
+local function recalc_end_index(train)
+ train.end_index = advtrains.path_get_index_by_offset(train, train.index, -train.trainlen)
+end
+
+-- Occupation Callback system
+-- see occupation.lua
+-- signature is advtrains.te_register_on_<?>(function(id, train) ... end)
+
+local function mkcallback(name)
+ local callt = {}
+ advtrains["te_register_on_"..name] = function(func)
+ assertt(func, "function")
+ table.insert(callt, func)
+ end
+ return callt, function(id, train, param1, param2, param3)
+ for _,f in ipairs(callt) do
+ f(id, train, param1, param2, param3)
+ end
+ end
+end
+
+local callbacks_new_path, run_callbacks_new_path = mkcallback("new_path")
+local callbacks_invahead
+callbacks_invahead, advtrains.run_callbacks_invahead = mkcallback("invalidate_ahead") -- (id, train, start_idx)
+local callbacks_update, run_callbacks_update = mkcallback("update")
+local callbacks_create, run_callbacks_create = mkcallback("create")
+local callbacks_remove, run_callbacks_remove = mkcallback("remove")
+
+
+-- train_ensure_init: responsible for creating a state that we can work on, after one of the following events has happened:
+-- - the train's path got cleared
+-- - save files were loaded
+-- Additionally, this gets called outside the step cycle to initialize and/or remove a train, then occ_write_mode is set.
+function advtrains.train_ensure_init(id, train)
+ if not train then
+ atwarn("train_ensure_init: Called with id =",id,"but a nil train!")
+ atwarn(debug.traceback())
+ return nil
+ end
+
+ train.dirty = true
+ if train.no_step then
+ --atprint("in ensure_init: no_step set, train step ignored!")
+ return nil
+ end
+
+ assertdef(train, "velocity", 0)
+ --assertdef(train, "tarvelocity", 0)
+ assertdef(train, "acceleration", 0)
+ assertdef(train, "id", id)
+
+
+ if not train.drives_on or not train.max_speed then
+ --atprint("in ensure_init: missing properties, updating!")
+ advtrains.update_trainpart_properties(id)
+ end
+
+ --restore path
+ if not train.path then
+ --atprint("in ensure_init: Needs restoring path...")
+ if not train.last_pos then
+ atlog("Train",id,": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
+ train.no_step = true
+ return nil
+ end
+ if not train.last_connid then
+ atwarn("Train",id,": Restoring path: no last_connid set! Will assume 1")
+ train.last_connid = 1
+ --[[
+ Why this fix was necessary:
+ Issue: Migration problems on Grand Theft Auto Minetest
+ 1. Run of this code, warning printed.
+ 2. path_create failed with result==nil (there was an unloaded node, wait_for_path set)
+ 3. in consequence, the supposed call to path_setrestore does not happen
+ 4. train.last_connid is still unset
+ 5. next step, warning is printed again
+ Result: log flood.
+ ]]
+ end
+
+ local result = advtrains.path_create(train, train.last_pos, train.last_connid or 1, train.last_frac or 0)
+
+ --atprint("in ensure_init: path_create result ",result)
+
+ if result==false then
+ atlog("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to place a rail at this position and restart the server.")
+ train.no_step = true
+ return nil
+ elseif result==nil then
+ if not train.wait_for_path then
+ atlog("Train",id,": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")
+ end
+ train.wait_for_path = true
+ return false
+ end
+ -- by now, we should have a working initial path
+ train.wait_for_path = false
+
+ advtrains.update_trainpart_properties(id)
+ recalc_end_index(train)
+
+ --atdebug("Train",id,": Successfully restored path at",train.last_pos," connid",train.last_connid," frac",train.last_frac)
+
+ -- run on_new_path callbacks
+ run_callbacks_new_path(id, train)
+ end
+
+ train.dirty = false -- TODO einbauen!
+ return true
+end
+
+local function v_target_apply(v_targets, lever, vel)
+ v_targets[lever] = v_targets[lever] and math.min(v_targets[lever], vel) or vel
+end
+
+function advtrains.train_step_b(id, train, dtime)
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ -- in this code, we check variables such as path_trk_? and path_dist. We need to ensure that the path is known for the whole 'Train' zone
+ advtrains.path_get(train, atfloor(train.index + 2))
+ advtrains.path_get(train, atfloor(train.end_index - 1))
+
+ -- run pre-move hooks
+ -- TODO: if more pre-move hooks are added, make a separate callback hook
+ advtrains.lzb_look_ahead(id, train)
+
+ --[[ again, new velocity control:
+ There are two heterogenous means of control:
+ -> set a fixed acceleration and ignore speed (user)
+ -> steer towards a target speed, distance doesn't matter
+ -> needs to specify the maximum acceleration/deceleration values they are willing to accelerate/brake with
+ -> Reach a target speed after a certain distance (LZB, handled specially)
+
+ ]]--
+
+ --- 3. handle velocity influences ---
+
+ local v0 = train.velocity
+ local sit_v_cap = train.max_speed -- Maximum speed in current situation (multiple limit factors)
+ -- The desired speed change issued by the active control (user or atc)
+ local ctrl_v_tar -- desired speed which should not be crossed by braking or accelerating
+ local ctrl_accelerating = false -- whether the train should accelerate
+ local ctrl_braking = false -- whether the train should brake
+ local ctrl_lever -- the lever value to use to calculate the acceleration
+ -- the final speed change after applying LZB
+ local v_cap -- absolute maximum speed
+ local v_tar -- desired speed which should not be crossed by braking or accelerating
+ local accelerating = false-- whether the train should accelerate
+ local braking = false -- whether the train should brake
+ local lever -- the lever value to use to calculate the acceleration
+ local train_moves = (v0 > 0)
+
+ if train.recently_collided_with_env then
+ if not train_moves then
+ train.recently_collided_with_env=nil--reset status when stopped
+ end
+ --atprint("in train_step_b: applying collided_with_env")
+ sit_v_cap = 0
+ elseif train.locomotives_in_train==0 then
+ --atprint("in train_step_b: applying no_locomotives")
+ sit_v_cap = 0
+ -- interlocking speed restriction
+ elseif train.speed_restriction then
+ --atprint("in train_step_b: applying interlocking speed restriction",train.speed_restriction)
+ sit_v_cap = train.speed_restriction
+ end
+
+ --apply off-track handling:
+ local front_off_track = train.index>train.path_trk_f
+ local back_off_track=train.end_index<train.path_trk_b
+ train.off_track = front_off_track or back_off_track
+
+ if back_off_track and (not v_cap or v_cap > 1) then
+ --atprint("in train_step_b: applying back_off_track")
+ sit_v_cap = 1
+ elseif front_off_track then
+ --atprint("in train_step_b: applying front_off_track")
+ sit_v_cap = 0
+ end
+
+
+ --interpret ATC command and apply auto-lever control when not actively controlled
+ local userc = train.ctrl_user
+ if userc then
+ --atprint("in train_step_b: ctrl_user active",userc)
+ advtrains.atc.train_reset_command(train)
+
+ if userc >= VLEVER_ACCEL then
+ ctrl_accelerating = true
+ else
+ ctrl_braking = true
+ end
+ ctrl_lever = userc
+ else
+ if train.atc_command then
+ if (not train.atc_delay or train.atc_delay<=0) and not train.atc_wait_finish then
+ advtrains.atc.execute_atc_command(id, train)
+ else
+ train.atc_delay=train.atc_delay-dtime
+ end
+ elseif train.atc_delay then
+ train.atc_delay = nil
+ end
+
+ local braketar = train.atc_brake_target
+ local emerg = false -- atc_brake_target==-1 means emergency brake (BB command)
+ if braketar == -1 then
+ braketar = 0
+ emerg = true
+ end
+ --atprint("in train_step_b: ATC: brake state braketar=",braketar,"emerg=",emerg)
+ if braketar and braketar>=v0 then
+ --atprint("in train_step_b: ATC: brake target cleared")
+ train.atc_brake_target=nil
+ braketar = nil
+ end
+ --if train.tarvelocity and train.velocity==train.tarvelocity then
+ -- train.tarvelocity = nil
+ --end
+ if train.atc_wait_finish then
+ if not train.atc_brake_target and (not train.tarvelocity or train.velocity==train.tarvelocity) then
+ train.atc_wait_finish=nil
+ end
+ end
+
+ if train.tarvelocity and train.tarvelocity>v0 then
+ --atprint("in train_step_b: applying ATC ACCEL", train.tarvelocity)
+ ctrl_accelerating = true
+ ctrl_lever = VLEVER_ACCEL
+ elseif train.tarvelocity and train.tarvelocity<v0 then
+ ctrl_braking = true
+
+ if (braketar and braketar<v0) then
+ if emerg then
+ --atprint("in train_step_b: applying ATC EMERG", train.tarvelocity)
+ ctrl_lever = VLEVER_EMERG
+ else
+ --atprint("in train_step_b: applying ATC BRAKE", train.tarvelocity)
+ ctrl_v_tar = braketar
+ ctrl_lever = VLEVER_BRAKE
+ end
+ else
+ --atprint("in train_step_b: applying ATC ROLL", train.tarvelocity)
+ ctrl_v_tar = train.tarvelocity
+ ctrl_lever = VLEVER_ROLL
+ end
+ end
+ end
+
+ --- 2b. look at v_target, determine the effective v_target and desired acceleration ---
+ --atprint("in train_step_b: Resulting control before LZB: accelerating",ctrl_accelerating,"braking",ctrl_braking,"lever", ctrl_lever, "target", ctrl_v_tar)
+ --train.debug = dump({tv_target,tv_lever})
+
+ --atprint("in train_step_b: Current index",train.index,"end",train.end_index,"vel",v0)
+ --- 3a. calculate the acceleration required to reach the speed restriction in path_speed (LZB) ---
+ -- Iterates over the path nodes we WOULD pass if we were continuing with the current speed
+ -- and determines the MINIMUM of path_speed in this range.
+ -- Then, determines acceleration so that we can reach this 'overridden' target speed in this step (but short-circuited)
+ local lzb_next_zero_barrier -- if defined, train should not pass this point as it's a 0-LZB
+ local new_index_curr_tv -- pre-calculated new train index in lzb check
+ local lzb_v_cap -- the maximum speed that LZB dictates
+
+ local dst_curr_v = v0 * dtime
+ new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
+ local i = atfloor(train.index)
+ local psp
+ while true do
+ psp = train.path_speed[i]
+ if psp then
+ lzb_v_cap = lzb_v_cap and math.min(lzb_v_cap, psp) or psp
+ if psp == 0 and not lzb_next_zero_barrier then
+ --atprint("in train_step_b: Found zero barrier: ",i)
+ lzb_next_zero_barrier = i - LZB_ZERO_APPROACH_DIST
+ end
+ end
+ if i > new_index_curr_tv then
+ break
+ end
+ i = i + 1
+ end
+
+ if lzb_next_zero_barrier and train.index < lzb_next_zero_barrier then
+ lzb_v_cap = LZB_ZERO_APPROACH_SPEED
+ end
+
+ --atprint("in train_step_b: LZB calculation yields newindex=",new_index_curr_tv,"lzbtarget=",lzb_v_cap,"zero_barr=",lzb_next_zero_barrier,"")
+
+ -- LZB HUD: decrement timer and delete when 0
+ if train.hud_lzb_effect_tmr then
+ if train.hud_lzb_effect_tmr <=0 then
+ train.hud_lzb_effect_tmr = nil
+ else
+ train.hud_lzb_effect_tmr = train.hud_lzb_effect_tmr - 1
+ end
+ end
+
+ -- We now need to bring ctrl_*, sit_v_cap and lzb_v_cap together to determine the final controls.
+ local v_cap = sit_v_cap -- always defined, by default train.max_speed
+ if lzb_v_cap and lzb_v_cap < v_cap then
+ v_cap = lzb_v_cap
+ lever = VLEVER_BRAKE -- actually irrelevant, acceleration is not considered anyway unless v_tar is also set.
+ -- display LZB control override in the HUD
+ if lzb_v_cap <= v0 then
+ train.hud_lzb_effect_tmr = 1
+ -- This is to signal the HUD that LZB is active. This works as a timer to avoid HUD blinking
+ end
+ end
+
+ v_tar = ctrl_v_tar
+ -- if v_cap is smaller than the current speed, we need to brake in all cases.
+ if v_cap < v0 then
+ braking = true
+ lever = VLEVER_BRAKE
+ -- set v_tar to v_cap to not slow down any further than required.
+ -- unless control wants us to brake too, then we use control's v_tar.
+ if not ctrl_v_tar or ctrl_v_tar > v_cap then
+ v_tar = v_cap
+ end
+ else -- else, use what the ctrl says
+ braking = ctrl_braking
+ accelerating = ctrl_accelerating and not braking
+ lever = ctrl_lever
+ end
+ train.lever = lever
+
+ --atprint("in train_step_b: final control: accelerating",accelerating,"braking",braking,"lever", lever, "target", v_tar)
+
+ -- reset train acceleration when holding speed
+ if not braking and not accelerating then
+ train.acceleration = 0
+ end
+
+ --- 3b. if braking, modify the velocity BEFORE the movement
+ if braking then
+ local dv = advtrains.get_acceleration(train, lever) * dtime
+ local v1 = v0 + dv
+ if v_tar and v1 < v_tar then
+ --atprint("in train_step_b: Braking: Hit v_tar!")
+ v1 = v_tar
+ end
+ if v1 > v_cap then
+ --atprint("in train_step_b: Braking: Hit v_cap!")
+ v1 = v_cap
+ end
+ if v1 < 0 then
+ --atprint("in train_step_b: Braking: Hit 0!")
+ v1 = 0
+ end
+
+ train.acceleration = (v1 - v0) / dtime
+ train.velocity = v1
+ --atprint("in train_step_b: Braking: New velocity",v1," (yields acceleration",train.acceleration,")")
+ -- make saved new_index_curr_tv invalid because speed has changed
+ new_index_curr_tv = nil
+ end
+
+ --- 4. move train ---
+ -- if we have calculated the new end index before, don't do that again
+ if not new_index_curr_tv then
+ local dst_curr_v = train.velocity * dtime
+ new_index_curr_tv = advtrains.path_get_index_by_offset(train, train.index, dst_curr_v)
+ --atprint("in train_step_b: movement calculation (re)done, yields newindex=",new_index_curr_tv)
+ else
+ --atprint("in train_step_b: movement calculation reusing from LZB newindex=",new_index_curr_tv)
+ end
+
+ -- if the zeroappr mechanism has hit, go no further than zeroappr index
+ if lzb_next_zero_barrier and new_index_curr_tv > lzb_next_zero_barrier then
+ --atprint("in train_step_b: Zero barrier hit, clipping to newidx_tv=",new_index_curr_tv, "zb_idx=",lzb_next_zero_barrier)
+ new_index_curr_tv = lzb_next_zero_barrier
+ end
+ train.index = new_index_curr_tv
+
+ recalc_end_index(train)
+ --atprint("in train_step_b: New index",train.index,"end",train.end_index,"vel",train.velocity)
+
+ --- 4a. if accelerating, modify the velocity AFTER the movement
+ if accelerating then
+ local dv = advtrains.get_acceleration(train, lever) * dtime
+ local v1 = v0 + dv
+ if v_tar and v1 > v_tar then
+ --atprint("in train_step_b: Accelerating: Hit v_tar!")
+ v1 = v_tar
+ end
+ if v1 > v_cap then
+ --atprint("in train_step_b: Accelerating: Hit v_cap!")
+ v1 = v_cap
+ end
+
+ train.acceleration = (v1 - v0) / dtime
+ train.velocity = v1
+ --atprint("in train_step_b: Accelerating: New velocity",v1," (yields acceleration",train.acceleration,")")
+ end
+end
+
+function advtrains.train_step_c(id, train, dtime)
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ -- all location/extent-critical actions have been done.
+ -- calculate the new occupation window
+ run_callbacks_update(id, train)
+
+ -- Return if something(TM) damaged the path
+ if train.no_step or train.wait_for_path or not train.path then return end
+
+ advtrains.path_clear_unused(train)
+
+ advtrains.path_setrestore(train)
+
+ -- less important stuff
+
+ train.check_trainpartload=(train.check_trainpartload or 0)-dtime
+ if train.check_trainpartload<=0 then
+ advtrains.spawn_wagons(id)
+ train.check_trainpartload=2
+ end
+
+ --- 8. check for collisions with other trains and damage players ---
+
+ local train_moves=(train.velocity~=0)
+
+ --- Check whether this train can be coupled to another, and set couple entities accordingly
+ if not train.was_standing and not train_moves then
+ advtrains.train_check_couples(train)
+ end
+ train.was_standing = not train_moves
+
+ if train_moves then
+
+ local collided = false
+ local coll_grace=1
+ local collindex = advtrains.path_get_index_by_offset(train, train.index, -coll_grace)
+ local collpos = advtrains.path_get(train, atround(collindex))
+ if collpos then
+ local rcollpos=advtrains.round_vector_floor_y(collpos)
+ local is_loaded_area = advtrains.is_node_loaded(rcollpos)
+ for x=-train.extent_h,train.extent_h do
+ for z=-train.extent_h,train.extent_h do
+ local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
+ --- 8a Check collision ---
+ if not collided then
+
+ local col_tr = advtrains.occ.check_collision(testpos, id)
+ if col_tr then
+ advtrains.train_check_couples(train)
+ train.velocity = 0
+ advtrains.atc.train_reset_command(train)
+ collided = true
+ end
+
+ --- 8b damage players ---
+ if is_loaded_area and train.velocity > 3 and (setting_overrun_mode=="drop" or setting_overrun_mode=="normal") then
+ local testpts = minetest.pos_to_string(testpos)
+ local player=advtrains.playersbypts[testpts]
+ if player and player:get_hp()>0 and advtrains.is_damage_enabled(player:get_player_name()) then
+ --atdebug("damage found",player:get_player_name())
+ if setting_overrun_mode=="drop" then
+ --instantly kill player
+ --drop inventory contents first, to not to spawn bones
+ local player_inv=player:get_inventory()
+ for i=1,player_inv:get_size("main") do
+ minetest.add_item(testpos, player_inv:get_stack("main", i))
+ end
+ for i=1,player_inv:get_size("craft") do
+ minetest.add_item(testpos, player_inv:get_stack("craft", i))
+ end
+ -- empty lists main and craft
+ player_inv:set_list("main", {})
+ player_inv:set_list("craft", {})
+ end
+ player:set_hp(0)
+ end
+ end
+ end
+ end
+ end
+ --- 8c damage other objects ---
+ if is_loaded_area then
+ local objs = minetest.get_objects_inside_radius(rcollpos, 2)
+ for _,obj in ipairs(objs) do
+ if not obj:is_player() and obj:get_armor_groups().fleshy and obj:get_armor_groups().fleshy > 0
+ and obj:get_luaentity() and obj:get_luaentity().name~="signs:text" then
+ obj:punch(obj, 1, { full_punch_interval = 1.0, damage_groups = {fleshy = 1000}, }, nil)
+ end
+ end
+ end
+ end
+ end
+end
+
+-- Default occupation callbacks for node callbacks
+-- (remember, train.end_index is set separately because callbacks are
+-- asserted to rely on this)
+
+local function mknodecallback(name)
+ local callt = {}
+ advtrains["tnc_register_on_"..name] = function(func, prio)
+ assertt(func, "function")
+ if prio then
+ table.insert(callt, 1, func)
+ else
+ table.insert(callt, func)
+ end
+ end
+ return callt, function(pos, id, train, index, paramx1, paramx2, paramx3)
+ for _,f in ipairs(callt) do
+ f(pos, id, train, index, paramx1, paramx2, paramx3)
+ end
+ end
+end
+
+-- enter/leave-node callbacks
+-- signature is advtrains.tnc_register_on_enter/leave(function(pos, id, train, index) ... end)
+local callbacks_enter_node, run_callbacks_enter_node = mknodecallback("enter")
+local callbacks_leave_node, run_callbacks_leave_node = mknodecallback("leave")
+
+-- Node callback for approaching
+-- Might be called multiple times, whenever path is recalculated. Also called for the first node the train is standing on, then has_entered is true.
+-- signature is function(pos, id, train, index, has_entered, lzbdata)
+-- has_entered: true if the "enter" callback has already been executed for this train in this location
+-- lzbdata: arbitrary data (shared between all callbacks), deleted when LZB is restarted.
+-- These callbacks are called in order of distance as train progresses along tracks, so lzbdata can be used to
+-- keep track of a train's state once it passes this point
+local callbacks_approach_node, run_callbacks_approach_node = mknodecallback("approach")
+
+
+local function tnc_call_enter_callback(pos, train_id, train, index)
+ --atdebug("tnc enter",pos,train_id)
+ local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
+ local mregnode=minetest.registered_nodes[node.name]
+ if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_enter then
+ mregnode.advtrains.on_train_enter(pos, train_id, train, index)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_enter_node(pos, train_id, train, index)
+
+ -- check for split points
+ if mregnode and mregnode.at_conns and #mregnode.at_conns == 3 and train.path_cp[index] == 3 then
+ -- train came from connection 3 of a switch, so it split points.
+ if not train.points_split then
+ train.points_split = {}
+ end
+ train.points_split[advtrains.encode_pos(pos)] = true
+ --atdebug(train_id,"split points at",pos)
+ end
+end
+local function tnc_call_leave_callback(pos, train_id, train, index)
+ --atdebug("tnc leave",pos,train_id)
+ local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
+ local mregnode=minetest.registered_nodes[node.name]
+ if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_leave then
+ mregnode.advtrains.on_train_leave(pos, train_id, train, index)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_leave_node(pos, train_id, train, index)
+
+ -- split points do not matter anymore. clear them
+ if train.points_split then
+ if train.points_split[advtrains.encode_pos(pos)] then
+ train.points_split[advtrains.encode_pos(pos)] = nil
+ --atdebug(train_id,"has passed split points at",pos)
+ end
+ -- any entries left?
+ for _,_ in pairs(train.points_split) do
+ return
+ end
+ train.points_split = nil
+ end
+ -- WARNING possibly unreachable place!
+end
+
+function advtrains.tnc_call_approach_callback(pos, train_id, train, index, lzbdata)
+ --atdebug("tnc approach",pos,train_id, lzbdata)
+ local has_entered = atround(train.index) == index
+
+ local node = advtrains.ndb.get_node(pos) --this spares the check if node is nil, it has a name in any case
+ local mregnode=minetest.registered_nodes[node.name]
+ if mregnode and mregnode.advtrains and mregnode.advtrains.on_train_approach then
+ mregnode.advtrains.on_train_approach(pos, train_id, train, index, has_entered, lzbdata)
+ end
+
+ -- call other registered callbacks
+ run_callbacks_approach_node(pos, train_id, train, index, has_entered, lzbdata)
+end
+
+-- === te callback definition for tnc node callbacks ===
+
+advtrains.te_register_on_new_path(function(id, train)
+ train.tnc = {
+ old_index = atround(train.index),
+ old_end_index = atround(train.end_index),
+ }
+ --atdebug(id,"tnc init",train.index,train.end_index)
+end)
+
+advtrains.te_register_on_update(function(id, train)
+ local new_index = atround(train.index)
+ local new_end_index = atround(train.end_index)
+ local old_index = train.tnc.old_index
+ local old_end_index = train.tnc.old_end_index
+ while old_index < new_index do
+ old_index = old_index + 1
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,old_index))
+ tnc_call_enter_callback(pos, id, train, old_index)
+ end
+ while old_end_index < new_end_index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,old_end_index))
+ tnc_call_leave_callback(pos, id, train, old_end_index)
+ old_end_index = old_end_index + 1
+ end
+ train.tnc.old_index = new_index
+ train.tnc.old_end_index = new_end_index
+end)
+
+advtrains.te_register_on_create(function(id, train)
+ local index = atround(train.index)
+ local end_index = atround(train.end_index)
+ while end_index <= index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
+ tnc_call_enter_callback(pos, id, train, end_index)
+ end_index = end_index + 1
+ end
+ --atdebug(id,"tnc create",train.index,train.end_index)
+end)
+
+advtrains.te_register_on_remove(function(id, train)
+ local index = atround(train.index)
+ local end_index = atround(train.end_index)
+ while end_index <= index do
+ local pos = advtrains.round_vector_floor_y(advtrains.path_get(train,end_index))
+ tnc_call_leave_callback(pos, id, train, end_index)
+ end_index = end_index + 1
+ end
+ --atdebug(id,"tnc remove",train.index,train.end_index)
+end)
+
+--returns new id
+function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
+ local new_id=advtrains.random_id()
+ while advtrains.trains[new_id] do new_id=advtrains.random_id() end--ensure uniqueness
+
+ local t={}
+ t.id = new_id
+
+ t.last_pos=pos
+ t.last_connid=connid
+ t.last_frac=ioff
+
+ --t.tarvelocity=0
+ t.velocity=0
+ t.trainparts=trainparts
+
+ advtrains.trains[new_id] = t
+ --atdebug("Created new train:",t)
+
+ if not advtrains.train_ensure_init(new_id, advtrains.trains[new_id]) then
+ atwarn("create_new_train_at",pos,connid,"failed! This might lead to temporary bugs.")
+ return
+ end
+
+ run_callbacks_create(new_id, advtrains.trains[new_id])
+
+ return new_id
+end
+
+function advtrains.remove_train(id)
+ local train = advtrains.trains[id]
+
+ if not advtrains.train_ensure_init(id, train) then
+ atwarn("remove_train",id,"failed! This might lead to temporary bugs.")
+ return
+ end
+
+ run_callbacks_remove(id, train)
+
+ advtrains.path_invalidate(train)
+ advtrains.couple_invalidate(train)
+
+ local tp = train.trainparts
+ --atdebug("Removing train",id,"leftover trainparts:",tp)
+
+ advtrains.trains[id] = nil
+
+ return tp
+
+end
+
+
+function advtrains.add_wagon_to_train(wagon_id, train_id, index)
+ local train=advtrains.trains[train_id]
+
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ if index then
+ table.insert(train.trainparts, index, wagon_id)
+ else
+ table.insert(train.trainparts, wagon_id)
+ end
+
+ advtrains.update_trainpart_properties(train_id)
+ recalc_end_index(train)
+ run_callbacks_update(train_id, train)
+end
+
+-- Note: safe_decouple_wagon() has been moved to wagons.lua
+
+-- this function sets wagon's pos_in_train(parts) properties and train's max_speed and drives_on (and more)
+function advtrains.update_trainpart_properties(train_id, invert_flipstate)
+ local train=advtrains.trains[train_id]
+ train.drives_on=advtrains.merge_tables(advtrains.all_tracktypes)
+ --FIX: deep-copy the table!!!
+ train.max_speed=20
+ train.extent_h = 0;
+
+ local rel_pos=0
+ local count_l=0
+ local shift_dcpl_lock=false
+ for i, w_id in ipairs(train.trainparts) do
+
+ local data = advtrains.wagons[w_id]
+
+ -- 1st: update wagon data (pos_in_train a.s.o)
+ if data then
+ local wagon = advtrains.wagon_prototypes[data.type or data.entity_name]
+ if not wagon then
+ atwarn("Wagon '",data.type,"' couldn't be found. Please check that all required modules are loaded!")
+ wagon = advtrains.wagon_prototypes["advtrains:wagon_placeholder"]
+
+ end
+ rel_pos=rel_pos+wagon.wagon_span
+ data.train_id=train_id
+ data.pos_in_train=rel_pos
+ data.pos_in_trainparts=i
+ if wagon.is_locomotive then
+ count_l=count_l+1
+ end
+ if invert_flipstate then
+ data.wagon_flipped = not data.wagon_flipped
+ shift_dcpl_lock, data.dcpl_lock = data.dcpl_lock, shift_dcpl_lock
+ end
+ rel_pos=rel_pos+wagon.wagon_span
+
+ if wagon.drives_on then
+ for k,_ in pairs(train.drives_on) do
+ if not wagon.drives_on[k] then
+ train.drives_on[k]=nil
+ end
+ end
+ end
+ train.max_speed=math.min(train.max_speed, wagon.max_speed)
+ train.extent_h = math.max(train.extent_h, wagon.extent_h or 1);
+ end
+ end
+ train.trainlen = rel_pos
+ train.locomotives_in_train = count_l
+end
+
+
+local ablkrng = advtrains.wagon_load_range
+-- This function checks whether entities need to be spawned for certain wagons, and spawns them.
+-- Called from train_step_*(), not required to check init.
+function advtrains.spawn_wagons(train_id)
+ local train = advtrains.trains[train_id]
+
+ for i = 1, #train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if data then
+ if data.train_id ~= train_id then
+ atwarn("Train",train_id,"Wagon #",i,": Saved train ID",data.train_id,"did not match!")
+ data.train_id = train_id
+ end
+ if not advtrains.wagon_objects[w_id] or not advtrains.wagon_objects[w_id]:get_yaw() then
+ -- eventually need to spawn new object. check if position is loaded.
+ local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
+ local pos = advtrains.path_get(train, atfloor(index))
+
+ if advtrains.position_in_range(pos, ablkrng) then
+ --atdebug("wagon",w_id,"spawning")
+ local wt = advtrains.get_wagon_prototype(data)
+ local wagon = minetest.add_entity(pos, wt):get_luaentity()
+ wagon:set_id(w_id)
+ end
+ end
+ else
+ atwarn("Train",train_id,"Wagon #",1,": A wagon with id",w_id,"does not exist! Wagon will be removed from train.")
+ table.remove(train.trainparts, i)
+ i = i - 1
+ end
+ end
+end
+
+function advtrains.split_train_at_fc(train, count_empty, length_limit)
+ -- splits train at first different current FC by convention,
+ -- locomotives have empty FC so are ignored
+ -- count_empty is used to split off locomotives
+ -- length_limit limits the length of the first train to length_limit wagons
+ local train_id = train.id
+ local fc = false
+ local ind = 0
+ for i = 1, #train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if length_limit and i > length_limit then
+ ind = i
+ break
+ end
+ if data then
+ local wfc = advtrains.get_cur_fc(data)
+ if wfc ~= "" or count_empty then
+ if fc then
+ if fc ~= wfc then
+ ind = i
+ break
+ end
+ else
+ fc = wfc
+ end
+ end
+ end
+ end
+ if ind > 0 then
+ return advtrains.split_train_at_index(train, ind), fc
+ end
+ if fc then
+ return nil, fc
+ end
+end
+
+function advtrains.train_step_fc(train)
+ for i=1,#train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if data then
+ advtrains.step_fc(data)
+ end
+ end
+end
+
+function advtrains.split_train_at_index(train, index)
+ -- this function splits a train at index, creating a new train from the back part of the train.
+
+ local train_id=train.id
+ if index > #train.trainparts then
+ -- index specified too long
+ return
+ end
+ local w_id = train.trainparts[index]
+ local data = advtrains.wagons[w_id]
+ local _, wagon = advtrains.get_wagon_prototype(data)
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ local p_index=advtrains.path_get_index_by_offset(train, train.index, - data.pos_in_train + wagon.wagon_span)
+ local pos, connid, frac = advtrains.path_getrestore(train, p_index)
+ local tp = {}
+ for k,v in ipairs(train.trainparts) do
+ if k >= index then
+ table.insert(tp, v)
+ train.trainparts[k] = nil
+ end
+ end
+ advtrains.update_trainpart_properties(train_id)
+ recalc_end_index(train)
+ run_callbacks_update(train_id, train)
+
+ --create subtrain
+ local newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)
+ local newtrain=advtrains.trains[newtrain_id]
+
+ newtrain.velocity=train.velocity
+ return newtrain_id -- return new train ID, so new train can be manipulated
+
+end
+
+function advtrains.split_train_at_wagon(wagon_id)
+ --get train
+ local data = advtrains.wagons[wagon_id]
+ advtrains.split_train_at_index(advtrains.trains[data.train_id], data.pos_in_trainparts)
+end
+
+-- coupling
+local CPL_CHK_DST = -1
+local CPL_ZONE = 2
+
+-- train.couple_* contain references to ObjectRefs of couple objects, which contain all relevant information
+-- These objectRefs will delete themselves once the couples no longer match
+local function createcouple(pos, train1, t1_is_front, train2, t2_is_front)
+ local id1 = train1.id
+ local id2 = train2.id
+ if train1.autocouple or train2.autocouple then
+ -- couple trains
+ train1.autocouple = nil
+ train2.autocouple = nil
+ minetest.after(0, advtrains.safe_couple_trains, id1, id2, t1_is_front, t2_is_front, false, false, train1.velocity, train2.velocity)
+ return
+ end
+
+ local obj=minetest.add_entity(pos, "advtrains:couple")
+ if not obj then error("Failed creating couple object!") return end
+ local le=obj:get_luaentity()
+ le.train_id_1=id1
+ le.train_id_2=id2
+ le.t1_is_front=t1_is_front
+ le.t2_is_front=t2_is_front
+ --atdebug("created couple between",train1.id,t1_is_front,train2.id,t2_is_front)
+ if t1_is_front then
+ train1.cpl_front = obj
+ else
+ train1.cpl_back = obj
+ end
+ if t2_is_front then
+ train2.cpl_front = obj
+ else
+ train2.cpl_back = obj
+ end
+
+end
+
+function advtrains.train_check_couples(train)
+ --atdebug("rechecking couples")
+ if train.cpl_front then
+ if not train.cpl_front:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_front = nil
+ end
+ end
+ if not train.cpl_front then
+ -- recheck front couple
+ local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(front_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ --atdebug(train.id,"front: ",idx,"on",tid,atround(other_train.index),atround(other_train.end_index))
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ createcouple(pos, train, true, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ createcouple(pos, train, true, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+ if train.cpl_back then
+ if not train.cpl_back:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_back = nil
+ end
+ end
+ if not train.cpl_back then
+ -- recheck back couple
+ local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(back_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ createcouple(pos, train, false, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ createcouple(pos, train, false, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+end
+
+function advtrains.couple_invalidate(train)
+ if train.cpl_back then
+ train.cpl_back:remove()
+ train.cpl_back = nil
+ end
+ if train.cpl_front then
+ train.cpl_front:remove()
+ train.cpl_front = nil
+ end
+ train.was_standing = nil
+end
+
+-- relevant code for this comment is in couple.lua
+
+--there are 4 cases:
+--1/2. F<->R F<->R regular, put second train behind first
+--->frontpos of first train will match backpos of second
+--3. F<->R R<->F flip one of these trains, take the other as new train
+--->backpos's will match
+--4. R<->F F<->R flip one of these trains and take it as new parent
+--->frontpos's will match
+
+
+function advtrains.do_connect_trains(first_id, second_id, vel)
+ local first, second=advtrains.trains[first_id], advtrains.trains[second_id]
+
+ if not advtrains.train_ensure_init(first_id, first) then
+ atwarn("Train",first_id,"is not initialized! Operation aborted!")
+ return
+ end
+ if not advtrains.train_ensure_init(second_id, second) then
+ atwarn("Train",second_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ local first_wagoncnt=#first.trainparts
+ local second_wagoncnt=#second.trainparts
+
+ for _,v in ipairs(second.trainparts) do
+ table.insert(first.trainparts, v)
+ end
+
+ advtrains.remove_train(second_id)
+ if vel < 0 then
+ advtrains.invert_train(first_id)
+ vel = -vel
+ end
+ first.velocity= vel or 0
+
+ advtrains.update_trainpart_properties(first_id)
+ advtrains.couple_invalidate(first)
+ return true
+end
+
+function advtrains.invert_train(train_id)
+ local train=advtrains.trains[train_id]
+
+ if not advtrains.train_ensure_init(train_id, train) then
+ atwarn("Train",train_id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ advtrains.path_setrestore(train, true)
+
+ -- rotate some other stuff
+ if train.door_open then
+ train.door_open = - train.door_open
+ end
+ if train.atc_command then
+ train.atc_arrow = not train.atc_arrow
+ end
+
+ advtrains.path_invalidate(train, true)
+ advtrains.couple_invalidate(train)
+
+ local old_trainparts=train.trainparts
+ train.trainparts={}
+ for k,v in ipairs(old_trainparts) do
+ table.insert(train.trainparts, 1, v)--notice insertion at first place
+ end
+ advtrains.update_trainpart_properties(train_id, true)
+
+ -- recalculate path
+ advtrains.train_ensure_init(train_id, train)
+
+ -- If interlocking present, check whether this train is in a section and then set as shunt move after reversion
+ if advtrains.interlocking and train.il_sections and #train.il_sections > 0 then
+ train.is_shunt = true
+ train.speed_restriction = advtrains.SHUNT_SPEED_MAX
+ else
+ train.is_shunt = false
+ train.speed_restriction = nil
+ end
+end
+
+-- returns: train id, index of one of the trains that stand at this position.
+function advtrains.get_train_at_pos(pos)
+ local t = advtrains.occ.get_trains_at(pos)
+ for tid,idx in pairs(t) do
+ return tid, idx
+ end
+end
+
+
+-- ehm... I never adapted this function to the new path system ?!
+function advtrains.invalidate_all_paths(pos)
+ local tab
+ if pos then
+ -- if position given, check occupation system
+ tab = advtrains.occ.get_trains_over(pos)
+ else
+ tab = advtrains.trains
+ end
+
+ for id, _ in pairs(tab) do
+ advtrains.invalidate_path(id)
+ end
+end
+
+-- Calls invalidate_path_ahead on all trains occupying (having paths over) this node
+-- Can be called during train step.
+function advtrains.invalidate_all_paths_ahead(pos)
+ local tab = advtrains.occ.get_trains_over(pos)
+
+ for id,index in pairs(tab) do
+ local train = advtrains.trains[id]
+ advtrains.path_invalidate_ahead(train, index, true)
+ end
+end
+
+function advtrains.invalidate_path(id)
+ --atdebug("Path invalidate:",id)
+ local v=advtrains.trains[id]
+ if not v then return end
+ advtrains.path_invalidate(v)
+ advtrains.couple_invalidate(v)
+ v.dirty = true
+end
+
+--not blocking trains group
+function advtrains.train_collides(node)
+ if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
+ if not minetest.registered_nodes[node.name].groups.not_blocking_trains then
+ return true
+ end
+ end
+ return false
+end
+
+local nonblocknodes={
+ "default:fence_wood",
+ "default:fence_acacia_wood",
+ "default:fence_aspen_wood",
+ "default:fence_pine_wood",
+ "default:fence_junglewood",
+ "default:torch",
+ "bones:bones",
+
+ "default:sign_wall",
+ "signs:sign_wall",
+ "signs:sign_wall_blue",
+ "signs:sign_wall_brown",
+ "signs:sign_wall_orange",
+ "signs:sign_wall_green",
+ "signs:sign_yard",
+ "signs:sign_wall_white_black",
+ "signs:sign_wall_red",
+ "signs:sign_wall_white_red",
+ "signs:sign_wall_yellow",
+ "signs:sign_post",
+ "signs:sign_hanging",
+
+
+}
+minetest.after(0, function()
+ for _,name in ipairs(nonblocknodes) do
+ if minetest.registered_nodes[name] then
+ minetest.registered_nodes[name].groups.not_blocking_trains=1
+ end
+ end
+end)
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
new file mode 100644
index 0000000..f918571
--- /dev/null
+++ b/advtrains/wagons.lua
@@ -0,0 +1,1449 @@
+-- wagon.lua
+-- Holds all logic related to wagons
+-- From now on, wagons are, just like trains, just entries in a table
+-- All data that is static is stored in the entity prototype (self).
+-- A copy of the entity prototype is always available inside wagon_prototypes
+-- All dynamic data is stored in the (new) wagons table
+-- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.
+-- Only data that are only important to the entity itself are stored in the luaentity
+
+-- TP delay when getting off wagon
+local GETOFF_TP_DELAY = 0.5
+
+local IGNORE_WORLD = advtrains.IGNORE_WORLD
+
+advtrains.wagons = {}
+advtrains.wagon_prototypes = {}
+advtrains.wagon_objects = {}
+
+local unload_wgn_range = advtrains.wagon_load_range + 32
+function advtrains.wagon_outside_range(pos) -- returns true if the object is outside of unload_wgn_range of any player
+ return not advtrains.position_in_range(pos, unload_wgn_range)
+end
+
+local setting_show_ids = minetest.settings:get_bool("advtrains_show_ids")
+
+--
+function advtrains.create_wagon(wtype, owner)
+ local new_id=advtrains.random_id()
+ while advtrains.wagons[new_id] do new_id=advtrains.random_id() end
+ local wgn = {}
+ wgn.type = wtype
+ wgn.seatp = {}
+ wgn.owner = owner
+ wgn.id = new_id
+ ---wgn.train_id = train_id --- will get this via update_trainpart_properties
+ advtrains.wagons[new_id] = wgn
+ --atdebug("Created new wagon:",wgn)
+ return new_id
+end
+
+local function make_inv_name(uid)
+ return "detached:advtrains_wgn_"..uid
+end
+
+
+local wagon={
+ collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
+ --physical = true,
+ visual = "mesh",
+ mesh = "wagon.b3d",
+ visual_size = {x=1, y=1},
+ textures = {"black.png"},
+ is_wagon=true,
+ wagon_span=1,--how many index units of space does this wagon consume
+ wagon_width=3, -- Wagon width in meters
+ 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.set_textures then
+ self:set_textures(data)
+ 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:set_velocity({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)
+ 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.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 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 "")).."]"
+ form=form.."field[7.5,1.75;3,1;line;"..attrans("Line")..";"..(minetest.formspec_escape(train.line or "")).."]"
+ form=form.."field[7.5,3.25;3,1;routingcode;"..attrans("Routingcode")..";"..(minetest.formspec_escape(train.routingcode or "")).."]"
+ --row 5 : train overview and autocoupling
+ if train.velocity==0 then
+ form=form.."label[0.5,4;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 roadnumber = ent.roadnumber or ""
+ form = form .. string.format("button[%d,%d;%d,%d;%s;%s]", i, linhei, 1, 0.2, "wgprp"..i, roadnumber)
+ local ename = ent.type
+ form = form .. "item_image["..i..","..(linhei+0.5)..";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.5)..";1,1;advtrains_discouple.png;dcpl_"..i..";]"
+ end
+ end
+ if i == data.pos_in_trainparts then
+ form = form .. "box["..(i-0.1)..","..(linhei+0.4)..";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.checkpoints > 0 then
+ local i=1
+ while train.lzb.checkpoints[i] do
+ local oci = train.lzb.checkpoints[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
+ if train.ars_disable then
+ form = form .. "button[4.5,7;5,1;ilarsenable;Clear 'Disable ARS' flag]"
+ 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)
+ elseif fields["wgprp"..i] then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==tpid and data.owner==pname then
+ wagon:show_wagon_properties(pname)
+ return
+ end
+ end
+ 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 advtrains.interlocking then
+ if fields.ilrs and train.lzb and #train.lzb.checkpoints > 0 then
+ local i=1
+ while train.lzb.checkpoints[i] do
+ local oci = train.lzb.checkpoints[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 fields.ilarsenable then
+ advtrains.interlocking.ars_set_disable(train, false)
+ end
+ end
+
+
+ if not fields.quit then
+ self:show_bordcom(pname)
+ end
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ 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
+ if fields.roadnumber then
+ data.roadnumber = fields.roadnumber
+ end
+ if fields.fc then
+ wagon.set_fc(data, fields.fc)
+ end
+ if fields.fcp then
+ wagon.prev_fc(data)
+ wagon.show_wagon_properties({id=uid}, pname)
+ end
+ if fields.fcn then
+ advtrains.step_fc(data)
+ wagon.show_wagon_properties({id=uid}, pname)
+ 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
+ uid=string.match(formname, "^advtrains_inv_(.+)$")
+ if uid then
+ local pname=player:get_player_name()
+ local data = advtrains.wagons[uid]
+ if fields.prop and data.owner==pname then
+ for _,wagon in pairs(minetest.luaentities) do
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid and data.owner==pname then
+ wagon:show_wagon_properties(pname)
+ --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.close_formspec(pname, "advtrains_seating_"..self.id)
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname, make_inv_name(self.id)))
+ end
+ if fields.prop and data.owner==pname then
+ minetest.close_formspec(pname, "advtrains_seating_"..self.id)
+ 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
+ minetest.close_formspec(pname, "advtrains_seating_"..self.id)
+ 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,v1,v2)
+
+ if pname and 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, wck_t2
+ if pname then
+ wck_t1 = check_twagon_owner(train1, t1f, pname)
+ wck_t2 = check_twagon_owner(train2, t2f, pname)
+ end
+ if (wck_t1 or wck_t2) or not pname then
+ if not v1 then
+ v1 = 0
+ end
+ if not v2 then
+ v2 = 0
+ end
+ if try_run then
+ return true
+ end
+ if t1f then
+ if t2f then
+ v1 = -v1
+ advtrains.invert_train(id1)
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ else
+ advtrains.do_connect_trains(id2, id1, v1+v2)
+ end
+ else
+ if t2f then
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ else
+ v2 = -v2
+ advtrains.invert_train(id2)
+ advtrains.do_connect_trains(id1, id2, v1+v2)
+ 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.standard_inventory_formspec(self, pname, invname)
+ --[[minetest.chat_send_player(pname, string.format("self=%s, pname=%s, invname=%s", self, pname, invname))
+ for k,v in pairs(self) do
+ minetest.chat_send_player(pname, string.format("%s=%s", k,v))
+ end
+ minetest.chat_send_player(pname, string.format("***%s***", self.object:get_pos()))--]]
+ local data = advtrains.wagons[self.id]
+ local r = "size[8,11]"..
+ "list["..invname..";box;0,0;8,3;]"
+ if data.owner==pname then
+ r = r .. "button_exit[0,9;4,1;prop;"..attrans("Wagon properties").."]"
+ end
+ r = r .. "list[current_player;main;0,5;8,4;]"..
+ "listring[]"
+ return r
+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)
+ 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
+
+-- 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)
+