aboutsummaryrefslogtreecommitdiff
path: root/src/noise.cpp
diff options
context:
space:
mode:
authorPerttu Ahola <celeron55@gmail.com>2011-11-17 10:19:31 +0200
committerPerttu Ahola <celeron55@gmail.com>2011-11-29 19:13:48 +0200
commit4f021fc7ddd175888a3bac2fcc413231a4ef5c3d (patch)
tree3d28d70a4af372749d413510d624a282f71b4db2 /src/noise.cpp
parent498ebef3573ae27e9bb05a30b8dc8165ebc76e15 (diff)
downloadminetest-4f021fc7ddd175888a3bac2fcc413231a4ef5c3d.tar.gz
minetest-4f021fc7ddd175888a3bac2fcc413231a4ef5c3d.tar.bz2
minetest-4f021fc7ddd175888a3bac2fcc413231a4ef5c3d.zip
Remove crafting definitions from content_craft.cpp
Diffstat (limited to 'src/noise.cpp')
0 files changed, 0 insertions, 0 deletions
57b9d1'>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.lua362
-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
-rw-r--r--advtrains_interlocking/approach.lua126
-rw-r--r--advtrains_interlocking/ars.lua155
-rw-r--r--advtrains_interlocking/database.lua648
-rw-r--r--advtrains_interlocking/demosignals.lua97
-rw-r--r--advtrains_interlocking/init.lua30
-rw-r--r--advtrains_interlocking/mod.conf7
-rw-r--r--advtrains_interlocking/models/at_il_tcb_node.obj248
-rw-r--r--advtrains_interlocking/route_prog.lua549
-rw-r--r--advtrains_interlocking/route_ui.lua153
-rw-r--r--advtrains_interlocking/routesetting.lua342
-rw-r--r--advtrains_interlocking/settingtypes.txt4
-rw-r--r--advtrains_interlocking/signal_api.lua515
-rwxr-xr-xadvtrains_interlocking/tcb_ts_ui.lua830
-rw-r--r--advtrains_interlocking/textures/advtrains_dtrack_npr_placer.pngbin0 -> 1238 bytes-rw-r--r--advtrains_interlocking/textures/advtrains_dtrack_shared_npr.pngbin0 -> 3176 bytes-rw-r--r--advtrains_interlocking/textures/at_il_route_end.pngbin0 -> 451 bytes-rw-r--r--advtrains_interlocking/textures/at_il_route_lock.pngbin0 -> 534 bytes-rw-r--r--advtrains_interlocking/textures/at_il_route_lock_edit.pngbin0 -> 533 bytes-rw-r--r--advtrains_interlocking/textures/at_il_route_set.pngbin0 -> 398 bytes-rw-r--r--advtrains_interlocking/textures/at_il_route_start.pngbin0 -> 380 bytes-rw-r--r--advtrains_interlocking/textures/at_il_routep_advance.pngbin0 -> 304 bytes-rw-r--r--advtrains_interlocking/textures/at_il_routep_end_here.pngbin0 -> 243 bytes-rw-r--r--advtrains_interlocking/textures/at_il_routep_end_over.pngbin0 -> 281 bytes-rw-r--r--advtrains_interlocking/textures/at_il_routep_end_over_last.pngbin0 -> 277 bytes-rw-r--r--advtrains_interlocking/textures/at_il_signal_asp_danger.pngbin0 -> 247 bytes-rw-r--r--advtrains_interlocking/textures/at_il_signal_asp_free.pngbin0 -> 245 bytes-rw-r--r--advtrains_interlocking/textures/at_il_signal_asp_slow.pngbin0 -> 245 bytes-rw-r--r--advtrains_interlocking/textures/at_il_signal_ip.pngbin0 -> 285 bytes-rw-r--r--advtrains_interlocking/textures/at_il_signal_off.pngbin0 -> 236 bytes-rw-r--r--advtrains_interlocking/textures/at_il_tcb_marker.pngbin0 -> 308 bytes-rw-r--r--advtrains_interlocking/textures/at_il_tcb_node.pngbin0 -> 279 bytes-rw-r--r--advtrains_interlocking/textures/at_il_tool.pngbin0 -> 337 bytes-rw-r--r--advtrains_interlocking/textures/at_il_turnout_cr_l.pngbin0 -> 314 bytes-rw-r--r--advtrains_interlocking/textures/at_il_turnout_cr_r.pngbin0 -> 298 bytes-rw-r--r--advtrains_interlocking/textures/at_il_turnout_free.pngbin0 -> 367 bytes-rw-r--r--advtrains_interlocking/textures/at_il_turnout_st.pngbin0 -> 229 bytes-rw-r--r--advtrains_interlocking/tool.lua66
-rw-r--r--advtrains_interlocking/train_sections.lua199
-rw-r--r--advtrains_interlocking/tsr_rail.lua66
-rw-r--r--advtrains_itrainmap/init.lua149
-rw-r--r--advtrains_itrainmap/mod.conf6
-rw-r--r--advtrains_itrainmap/textures/itm_example.pngbin0 -> 154460 bytes-rw-r--r--advtrains_line_automation/init.lua47
-rw-r--r--advtrains_line_automation/mod.conf7
-rw-r--r--advtrains_line_automation/railwaytime.lua308
-rw-r--r--advtrains_line_automation/readme.txt5
-rw-r--r--advtrains_line_automation/scheduler.lua133
-rw-r--r--advtrains_line_automation/settingtypes.txt6
-rw-r--r--advtrains_line_automation/stoprail.lua225
-rw-r--r--advtrains_line_automation/textures/advtrains_dtrack_shared_stop.pngbin0 -> 3306 bytes-rw-r--r--advtrains_line_automation/textures/advtrains_dtrack_stop_placer.pngbin0 -> 1238 bytes-rw-r--r--advtrains_luaautomation/README.md446
-rw-r--r--advtrains_luaautomation/active_common.lua200
-rw-r--r--advtrains_luaautomation/atc_rail.lua247
-rw-r--r--advtrains_luaautomation/chatcmds.lua141
-rw-r--r--advtrains_luaautomation/environment.lua372
-rw-r--r--advtrains_luaautomation/init.lua116
-rw-r--r--advtrains_luaautomation/interrupt.lua73
-rw-r--r--advtrains_luaautomation/mod.conf7
-rw-r--r--advtrains_luaautomation/operation_panel.lua28
-rw-r--r--advtrains_luaautomation/p_display.lua0
-rw-r--r--advtrains_luaautomation/passive_api.txt24
-rw-r--r--advtrains_luaautomation/pcnaming.lua76
-rw-r--r--advtrains_luaautomation/textures/atlatc_oppanel.pngbin0 -> 631 bytes-rw-r--r--advtrains_luaautomation/textures/atlatc_pcnaming.pngbin0 -> 329 bytes-rwxr-xr-xadvtrains_signals_ks/init.lua286
-rw-r--r--advtrains_signals_ks/init_degrotate_nodes.lua94
-rw-r--r--advtrains_signals_ks/mod.conf6
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_head_dist.obj270
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_head_main.obj286
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_lamps_dist.obj1989
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_lamps_main.obj2719
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_main_smr0.obj4280
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_main_smr30.obj4293
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_main_smr45.obj4301
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_main_smr60.obj4310
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_mast.obj366
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_mast_smr0.obj141
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_mast_smr30.obj141
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_mast_smr45.obj141
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_mast_smr60.obj141
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sht_smr0.obj2942
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sht_smr30.obj2940
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sht_smr45.obj2933
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sht_smr60.obj2950
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sign_smr0.obj134
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sign_smr30.obj135
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sign_smr45.obj135
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_sign_smr60.obj135
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_zs_bottom.obj956
-rw-r--r--advtrains_signals_ks/models/advtrains_signals_ks_zs_top.obj835
-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_aspect_green.pngbin0 -> 155 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_aspect_red.pngbin0 -> 155 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_aspect_white.pngbin0 -> 155 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_aspect_yellow.pngbin0 -> 155 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_head.pngbin0 -> 260 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_hs_inv.pngbin0 -> 210 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ltm_danger.pngbin0 -> 227 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ltm_free.pngbin0 -> 215 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ltm_shunt.pngbin0 -> 238 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ltm_shuntd.pngbin0 -> 239 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ltm_slow.pngbin0 -> 219 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_mast.pngbin0 -> 224 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_ra_inv.pngbin0 -> 252 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_12.pngbin0 -> 197 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_16.pngbin0 -> 190 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_8.pngbin0 -> 174 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_e.pngbin0 -> 166 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_hfs.pngbin0 -> 193 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_sign_pam.pngbin0 -> 211 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_signpost.pngbin0 -> 224 bytes-rw-r--r--advtrains_signals_ks/textures/advtrains_signals_ks_zs_7.pngbin0 -> 251 bytes-rwxr-xr-xadvtrains_train_track/init.lua454
-rw-r--r--advtrains_train_track/mod.conf7
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_bumper_st.b3dbin0 -> 32927 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_bumper_st_30.b3dbin0 -> 48546 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_bumper_st_45.b3dbin0 -> 32926 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_bumper_st_60.b3dbin0 -> 48546 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_cr.b3dbin0 -> 28040 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_cr_30.b3dbin0 -> 28043 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_cr_45.b3dbin0 -> 34483 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_cr_60.b3dbin0 -> 34483 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_l.obj2335
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_l_30.obj1882
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_l_45.obj2710
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_l_60.obj1888
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_r.obj2335
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_r_30.obj1889
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_r_45.obj2710
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_r_60.obj1882
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_s.obj2339
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_s_30.obj1888
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_s_45.obj2707
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_s3_s_60.obj1888
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_st.b3dbin0 -> 10712 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_st_30.b3dbin0 -> 20043 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_st_45.b3dbin0 -> 12235 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_st_60.b3dbin0 -> 20043 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlcr.b3dbin0 -> 36203 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlcr_30.b3dbin0 -> 39958 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlcr_45.b3dbin0 -> 41302 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlcr_60.b3dbin0 -> 46270 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlst.b3dbin0 -> 36203 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlst_30.b3dbin0 -> 39958 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlst_45.b3dbin0 -> 41302 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swlst_60.b3dbin0 -> 46270 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrcr.b3dbin0 -> 36203 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrcr_30.b3dbin0 -> 46270 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrcr_45.b3dbin0 -> 41302 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrcr_60.b3dbin0 -> 39958 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrst.b3dbin0 -> 36203 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrst_30.b3dbin0 -> 46270 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrst_45.b3dbin0 -> 41302 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_swrst_60.b3dbin0 -> 39958 bytes-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_l.obj1936
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_l_30.obj1414
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_l_45.obj2033
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_l_60.obj1390
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_r.obj1929
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_r_30.obj1402
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_r_45.obj2037
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_sy_r_60.obj1402
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst1.obj348
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst1_45.obj434
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst2.obj372
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst2_45.obj462
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst31.obj348
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst32.obj372
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_vst33.obj388
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing4590_st.obj2196
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_30l.obj2417
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_30r.obj2378
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_45l.obj1576
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_45r.obj1557
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_60l.obj2303
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing90plusx_60r.obj2280
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing_st.obj4963
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing_st_30.obj3476
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing_st_45.obj3177
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xing_st_60.obj3492
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_30l45r.obj2317
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_30r45l.obj2294
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60l30l.obj2556
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60l30r.obj2556
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60l45r.obj2206
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60l60r.obj2229
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60r-46l.obj2149
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60r30l.obj2539
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60r30r.obj2539
-rw-r--r--advtrains_train_track/models/advtrains_dtrack_xingdiag_60r45l.obj2223
-rw-r--r--advtrains_train_track/models/advtrains_track_cr.b3dbin0 -> 8023 bytes-rw-r--r--advtrains_train_track/models/advtrains_track_st.b3dbin0 -> 15831 bytes-rw-r--r--advtrains_train_track/models/advtrains_track_st_45.b3dbin0 -> 8935 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_atc_placer.pngbin0 -> 1259 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_bumper_placer.pngbin0 -> 2213 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_detector_placer.pngbin0 -> 1253 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_load_placer.pngbin0 -> 1248 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_placer.pngbin0 -> 1097 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_rail.pngbin0 -> 4582 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_s3_placer.pngbin0 -> 619 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared.pngbin0 -> 7141 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared_atc.pngbin0 -> 7215 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared_detector_off.pngbin0 -> 7180 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared_detector_on.pngbin0 -> 7181 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared_load.pngbin0 -> 7339 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_shared_unload.pngbin0 -> 7338 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_slopeplacer.pngbin0 -> 2415 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_sy_placer.pngbin0 -> 625 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_unload_placer.pngbin0 -> 1260 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_xing4590_placer.pngbin0 -> 468 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_xing_placer.pngbin0 -> 549 bytes-rw-r--r--advtrains_train_track/textures/advtrains_dtrack_xingdiag_placer.pngbin0 -> 557 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_cr.pngbin0 -> 33370 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_cr_45.pngbin0 -> 33938 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_placer.pngbin0 -> 32349 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_st.pngbin0 -> 20405 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_st_45.pngbin0 -> 39977 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swlcr.pngbin0 -> 33378 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swlcr_45.pngbin0 -> 45772 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swlst.pngbin0 -> 32321 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swlst_45.pngbin0 -> 46408 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swrcr.pngbin0 -> 33670 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swrcr_45.pngbin0 -> 46865 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swrst.pngbin0 -> 32654 bytes-rw-r--r--advtrains_train_track/textures/advtrains_track_swrst_45.pngbin0 -> 47636 bytes-rw-r--r--assets/AdvTrains_Additions.zipbin0 -> 889352 bytes-rw-r--r--assets/Weichenhebel-CooleLokAnimiert.zipbin0 -> 154594 bytes-rw-r--r--assets/Weichenhebel-CooleLokAnimiert/dampflock4pipe_animated.blendbin0 -> 859028 bytes-rw-r--r--assets/advtrains_across.xcfbin0 -> 13559 bytes-rw-r--r--assets/advtrains_newlocomotive.xcfbin0 -> 7462378 bytes-rw-r--r--assets/advtrains_wagon.pngbin0 -> 72273 bytes-rw-r--r--assets/andreaskreuz.blendbin0 -> 507904 bytes-rw-r--r--assets/andreaskreuz.blend1bin0 -> 507904 bytes-rw-r--r--assets/andreaskreuz.pngbin0 -> 69701 bytes-rw-r--r--assets/at_il_tcb_node.blendbin0 -> 483100 bytes-rw-r--r--assets/blender/engine-with-animation.blendbin0 -> 599504 bytes-rw-r--r--assets/blender/engine-with-animation_newtry.blendbin0 -> 638904 bytes-rw-r--r--assets/blender/gleis/advtrains_dtrack_shared.xcfbin0 -> 75136 bytes-rw-r--r--assets/blender/gleis/before_redo/detectorpart.pngbin0 -> 857 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_my.blendbin0 -> 654772 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_my_rt.blendbin0 -> 847060 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_my_rt_final.blendbin0 -> 1015668 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_my_rt_switches.blendbin0 -> 2184236 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_my_rt_switches2.blendbin0 -> 2139340 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_together.blendbin0 -> 1016920 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_together.pngbin0 -> 233544 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_together2.pngbin0 -> 312891 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_together3.pngbin0 -> 309398 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_verticals.blendbin0 -> 550748 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_verticals3.blendbin0 -> 591800 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_verticals45.blendbin0 -> 547788 bytes-rw-r--r--assets/blender/gleis/before_redo/rail_verticals45.blend11bin0 -> 547300 bytes-rw-r--r--assets/blender/gleis/infos/Grafik_weiche.pngbin0 -> 361700 bytes-rw-r--r--assets/blender/gleis/infos/breite1
-rw-r--r--assets/blender/gleis/licence.txt~0
-rw-r--r--assets/blender/gleis/rail_crossings.blendbin0 -> 444480 bytes-rw-r--r--assets/blender/gleis/rail_moreturnouts.blendbin0 -> 468819 bytes-rw-r--r--assets/blender/gleis/rail_redo.blendbin0 -> 1929280 bytes-rw-r--r--assets/blender/gleis/rail_redo_bk_curves.blendbin0 -> 791704 bytes-rw-r--r--assets/blender/gleis/rail_redo_bk_rswit.blendbin0 -> 1567640 bytes-rw-r--r--assets/blender/gleis/rail_redo_bk_straight.blendbin0 -> 707632 bytes-rw-r--r--assets/blender/gleis/rail_redo_bk_swit.blendbin0 -> 1189696 bytes-rw-r--r--assets/blender/gleis/texturen/rail.pngbin0 -> 2616 bytes-rw-r--r--assets/blender/gleis/texturen/uv.pngbin0 -> 2343 bytes-rw-r--r--assets/blender/gleis/uv_rails_all.pngbin0 -> 3692 bytes-rw-r--r--assets/blender/industrial/industry_loc.blendbin0 -> 652640 bytes-rw-r--r--assets/blender/industrial/industry_loc.blend11bin0 -> 659096 bytes-rw-r--r--assets/blender/industrial/industry_oil_ wagong.blendbin0 -> 693488 bytes-rw-r--r--assets/blender/industrial/industry_tree_wagong.blendbin0 -> 640056 bytes-rw-r--r--assets/blender/industrial/industry_tree_wagong.pngbin0 -> 316 bytes-rw-r--r--assets/blender/ks/advtrains_signals_ks_head_dist.obj270
-rw-r--r--assets/blender/ks/advtrains_signals_ks_head_main.obj286
-rw-r--r--assets/blender/ks/advtrains_signals_ks_lamps_dist.obj1989
-rw-r--r--assets/blender/ks/advtrains_signals_ks_lamps_main.obj2719
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr0.mtl42
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr0.obj4280
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr30.mtl42
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr30.obj4293
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr45.mtl42
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr45.obj4301
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr60.mtl42
-rw-r--r--assets/blender/ks/advtrains_signals_ks_main_smr60.obj4310
-rw-r--r--assets/blender/ks/advtrains_signals_ks_mast.obj366
-rw-r--r--assets/blender/ks/advtrains_signals_ks_mast_smr0.obj141
-rw-r--r--assets/blender/ks/advtrains_signals_ks_mast_smr30.obj141
-rw-r--r--assets/blender/ks/advtrains_signals_ks_mast_smr45.obj141
-rw-r--r--assets/blender/ks/advtrains_signals_ks_mast_smr60.obj141
-rw-r--r--assets/blender/ks/advtrains_signals_ks_zs_bottom.obj956
-rw-r--r--assets/blender/ks/advtrains_signals_ks_zs_top.obj835
-rw-r--r--assets/blender/ks/signal_ks.blendbin0 -> 770684 bytes-rw-r--r--assets/blender/ks/signal_ks.blend1bin0 -> 770684 bytes-rw-r--r--assets/blender/ks/signal_ks_export.blendbin0 -> 880352 bytes-rw-r--r--assets/blender/ks/signal_ks_export.blend1bin0 -> 880352 bytes-rw-r--r--assets/blender/ks/signal_ks_exportd.blendbin0 -> 535508 bytes-rw-r--r--assets/blender/ks/signal_ks_exportd.blend1bin0 -> 535316 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh.blendbin0 -> 703884 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh.blend1bin0 -> 703884 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_final.blendbin0 -> 652332 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_final.blend1bin0 -> 661328 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_lampuv.pngbin0 -> 211402 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_lampuv.xcfbin0 -> 1558 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_shuntsig.blendbin0 -> 593572 bytes-rw-r--r--assets/blender/ks/signal_ks_singlemesh_shuntsig.blend1bin0 -> 593572 bytes-rw-r--r--assets/blender/ks/signal_ks_splt.blendbin0 -> 884984 bytes-rw-r--r--assets/blender/ks/signal_ks_splt.blend1bin0 -> 867672 bytes-rw-r--r--assets/blender/locomotive.blendbin0 -> 459600 bytes-rw-r--r--assets/blender/magnet_track.blendbin0 -> 516836 bytes-rw-r--r--assets/blender/mbb/advtrains_engine_japan.xcfbin0 -> 2621842 bytes-rw-r--r--assets/blender/mbb/jpn_engine_anim_uv.pngbin0 -> 274484 bytes-rw-r--r--assets/blender/mbb/jpn_wagon_anim_uv.pngbin0 -> 206208 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive.blendbin0 -> 715116 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive.blend1bin0 -> 715116 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive.pngbin0 -> 204978 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive.xcfbin0 -> 957175 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_anim.blendbin0 -> 1149060 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_anim.blend1bin0 -> 1170088 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_anim_uv.blendbin0 -> 1146996 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_anim_uv.blend1bin0 -> 1144108 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_windows.blendbin0 -> 790808 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_windows.blend1bin0 -> 790808 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_windows_join.blendbin0 -> 762448 bytes-rw-r--r--assets/blender/mbb/modern_japanlocomotive_windows_join.blend1bin0 -> 767528 bytes-rw-r--r--assets/blender/mbb/modern_locomotive.blendbin0 -> 732124 bytes-rw-r--r--assets/blender/mbb/modern_wagong.blendbin0 -> 471140 bytes-rw-r--r--assets/blender/mbb/modern_wagong.blend1bin0 -> 518316 bytes-rw-r--r--assets/blender/mbb/modern_wagong_uv.blendbin0 -> 571364 bytes-rw-r--r--assets/blender/mbb/modern_wagong_uv.blend1bin0 -> 575708 bytes-rw-r--r--assets/blender/mbb/modern_wagong_uv.pngbin0 -> 189564 bytes-rw-r--r--assets/blender/mbb/prellbock.blendbin0 -> 745176 bytes-rw-r--r--assets/blender/mbb/prellbock.blend1bin0 -> 485416 bytes-rw-r--r--assets/blender/mbb/prellbock.uv.pngbin0 -> 4034 bytes-rw-r--r--assets/blender/mbb/regio-lokomotive.blendbin0 -> 704160 bytes-rw-r--r--assets/blender/mbb/regio-waron.blendbin0 -> 545620 bytes-rw-r--r--assets/blender/mbb/retrosignal.blendbin0 -> 488920 bytes-rw-r--r--assets/blender/mbb/retrosignal.blend1bin0 -> 507800 bytes-rw-r--r--assets/blender/mbb/signal(2)_hoch.blendbin0 -> 472092 bytes-rw-r--r--assets/blender/mbb/signal(2)_unten.blendbin0 -> 472092 bytes-rw-r--r--assets/blender/mbb/signal.blendbin0 -> 570336 bytes-rw-r--r--assets/blender/mbb/signal.blend1bin0 -> 570336 bytes-rw-r--r--assets/blender/mbb/signal.pngbin0 -> 244314 bytes-rw-r--r--assets/blender/mbb/signal.xcfbin0 -> 881142 bytes-rw-r--r--assets/blender/mbb/signal2.pngbin0 -> 39924 bytes-rw-r--r--assets/blender/mbb/tr-logo.pngbin0 -> 197 bytes-rw-r--r--assets/blender/mbb/weichenhebel1.blendbin0 -> 493028 bytes-rw-r--r--assets/blender/mbb/weichenhebel2.blendbin0 -> 492996 bytes-rw-r--r--assets/blender/newlocomotive.blendbin0 -> 603376 bytes-rw-r--r--assets/blender/newlocomotive_uvs.pngbin0 -> 175030 bytes-rw-r--r--assets/blender/newwagon.blendbin0 -> 542188 bytes-rw-r--r--assets/blender/newwagon.pngbin0 -> 108762 bytes-rw-r--r--assets/blender/subway-train.blendbin0 -> 518888 bytes-rw-r--r--assets/blender/subway-train.pngbin0 -> 96773 bytes-rw-r--r--assets/blender/subway-train.xcfbin0 -> 92558 bytes-rw-r--r--assets/blender/subway-train_animate.blendbin0 -> 568668 bytes-rw-r--r--assets/blender/subway-train_animate_mirrored.blendbin0 -> 798328 bytes-rw-r--r--assets/blender/subway-train_animate_mirrored_solidify.blendbin0 -> 879916 bytes-rw-r--r--assets/blender/subway-train_animate_mirrored_solidify_uv.blendbin0 -> 836620 bytes-rw-r--r--assets/blender/subway-train_animate_mirrored_solidify_uv.pngbin0 -> 177111 bytes-rw-r--r--assets/blender/subway-train_animate_mirrored_solidify_uv.xcfbin0 -> 369791 bytes-rw-r--r--assets/blender/trackplane.blendbin0 -> 462200 bytes-rw-r--r--assets/blender/trackvertical1.blendbin0 -> 453656 bytes-rw-r--r--assets/blender/trackvertical1.pngbin0 -> 59146 bytes-rw-r--r--assets/blender/trackvertical2.blendbin0 -> 459216 bytes-rw-r--r--assets/blender/trackvertical2.pngbin0 -> 66755 bytes-rw-r--r--assets/dampflock4.blendbin0 -> 1000628 bytes-rw-r--r--assets/img_large/tcbex1.pngbin0 -> 546408 bytes-rw-r--r--assets/img_large/tcbex2.pngbin0 -> 174379 bytes-rw-r--r--assets/img_large/tcbex3.pngbin0 -> 927810 bytes-rw-r--r--assets/init.lua0
-rw-r--r--assets/interlocking.html.LyXconv/0_home_moritz_Home_Projekte_Minetest_minetest_m___s_assets_lyx_img_screenshot_20180830_142551.eps34162
-rw-r--r--assets/interlocking.html.LyXconv/0_home_moritz_Home_Projekte_Minetest_minetest_m___s_assets_lyx_img_screenshot_20180830_142551.pngbin0 -> 257012 bytes-rw-r--r--assets/interlocking.html.LyXconv/10_home_moritz_Home_Projekte_Minetest_minetest_____lyx_img_Bildschirmfoto_2019-01-15_19-28-09.pngbin0 -> 135195 bytes-rw-r--r--assets/interlocking.html.LyXconv/11_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_arslin_ex1.pngbin0 -> 32940 bytes-rw-r--r--assets/interlocking.html.LyXconv/12_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_arslin_ex2.pngbin0 -> 2205 bytes-rw-r--r--assets/interlocking.html.LyXconv/13_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_ars_ex1.pngbin0 -> 65581 bytes-rw-r--r--assets/interlocking.html.LyXconv/1_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-26-35.eps69401
-rw-r--r--assets/interlocking.html.LyXconv/1_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-26-35.pngbin0 -> 392215 bytes-rw-r--r--assets/interlocking.html.LyXconv/2_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-32-48.eps62462
-rw-r--r--assets/interlocking.html.LyXconv/2_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-32-48.pngbin0 -> 467151 bytes-rw-r--r--assets/interlocking.html.LyXconv/3_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-27-25.eps15074
-rw-r--r--assets/interlocking.html.LyXconv/3_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-27-25.pngbin0 -> 58974 bytes-rw-r--r--assets/interlocking.html.LyXconv/4_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-28-32.eps33895
-rw-r--r--assets/interlocking.html.LyXconv/4_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-28-32.pngbin0 -> 180489 bytes-rw-r--r--assets/interlocking.html.LyXconv/5_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-51-25.eps41321
-rw-r--r--assets/interlocking.html.LyXconv/5_home_moritz_Home_Projekte_Minetest_minetest_m____lyx_img_Bildschirmfoto_2018-08-30_14-51-25.pngbin0 -> 298898 bytes-rw-r--r--assets/interlocking.html.LyXconv/6_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex1.eps18740
-rw-r--r--assets/interlocking.html.LyXconv/6_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex1.pngbin0 -> 9681 bytes-rw-r--r--assets/interlocking.html.LyXconv/7_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex2.eps12586
-rw-r--r--assets/interlocking.html.LyXconv/7_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex2.pngbin0 -> 9374 bytes-rw-r--r--assets/interlocking.html.LyXconv/8_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_assign_signal.eps27704
-rw-r--r--assets/interlocking.html.LyXconv/8_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_assign_signal.pngbin0 -> 117671 bytes-rw-r--r--assets/interlocking.html.LyXconv/9_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex3.eps18740
-rw-r--r--assets/interlocking.html.LyXconv/9_home_moritz_Home_Projekte_Minetest_minetest_mods_advtrains_assets_lyx_img_route_ex3.pngbin0 -> 11544 bytes-rw-r--r--assets/interlocking.html.LyXconv/interlocking.4ct30
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.4tc29
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.aux4
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.css117
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.dvibin0 -> 65604 bytes-rw-r--r--assets/interlocking.html.LyXconv/interlocking.html830
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.idvbin0 -> 569 bytes-rw-r--r--assets/interlocking.html.LyXconv/interlocking.lg166
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.log555
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.pdfbin0 -> 1419828 bytes-rw-r--r--assets/interlocking.html.LyXconv/interlocking.tex745
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.tex.dep7
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.tex.dep-pdf57
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.tmp2
-rw-r--r--assets/interlocking.html.LyXconv/interlocking.xref73
-rw-r--r--assets/interlocking.lyx2026
-rw-r--r--assets/interlocking.lyx~2017
-rw-r--r--assets/larger rails.xcfbin0 -> 1239132 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2018-08-30_14-26-35.pngbin0 -> 392215 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2018-08-30_14-27-25.pngbin0 -> 58974 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2018-08-30_14-28-32.pngbin0 -> 180489 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2018-08-30_14-32-48.pngbin0 -> 467151 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2018-08-30_14-51-25.pngbin0 -> 298898 bytes-rw-r--r--assets/lyx_img/Bildschirmfoto_2019-01-15_19-28-09.pngbin0 -> 135195 bytes-rw-r--r--assets/lyx_img/ars_ex1.pngbin0 -> 65581 bytes-rw-r--r--assets/lyx_img/arslin_ex1.pngbin0 -> 32940 bytes-rw-r--r--assets/lyx_img/arslin_ex2.pngbin0 -> 2205 bytes-rw-r--r--assets/lyx_img/assign_signal.pngbin0 -> 117671 bytes-rw-r--r--assets/lyx_img/route_ex1.pngbin0 -> 9681 bytes-rw-r--r--assets/lyx_img/route_ex2.pngbin0 -> 9374 bytes-rw-r--r--assets/lyx_img/route_ex3.pngbin0 -> 11544 bytes-rw-r--r--assets/lyx_img/screenshot_20180830_142551.pngbin0 -> 257012 bytes-rw-r--r--assets/magleves_lockomotive.blendbin0 -> 747680 bytes-rw-r--r--assets/magleves_track.blendbin0 -> 464960 bytes-rw-r--r--assets/magleves_wagon.blendbin0 -> 709904 bytes-rw-r--r--assets/manual.html.LyXconv/0_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-43-29.pngbin0 -> 9345 bytes-rw-r--r--assets/manual.html.LyXconv/10_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-48-54.pngbin0 -> 14032 bytes-rw-r--r--assets/manual.html.LyXconv/11_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-50-27.pngbin0 -> 4246 bytes-rw-r--r--assets/manual.html.LyXconv/12_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-51-02.pngbin0 -> 3704 bytes-rw-r--r--assets/manual.html.LyXconv/13_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-58-39.pngbin0 -> 6880 bytes-rw-r--r--assets/manual.html.LyXconv/14_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2016-09-17_09-58-20.pngbin0 -> 10375 bytes-rw-r--r--assets/manual.html.LyXconv/15_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2017-03-09_11-33-09.pngbin0 -> 3496 bytes-rw-r--r--assets/manual.html.LyXconv/16_home_moritz_Home_Projekte_Minetest_minetest____nual_img_Bildschirmfoto_2017-03-09_11-42-49.pngbin0 -> 25657 bytes-rw-r--r--assets/manual.html.LyXconv/17_home_moritz_Home_Projekte_Minetest_minetest____ssets_manual_img_screenshot_20161203_231622.pngbin0 -> 108256 bytes-rw-r--r--assets/manual.html.LyXconv/1_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-04-12.pngbin0 -> 72813 bytes-rw-r--r--assets/manual.html.LyXconv/2_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-04-57.pngbin0 -> 105448 bytes-rw-r--r--assets/manual.html.LyXconv/3_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-05-51.pngbin0 -> 93538 bytes-rw-r--r--assets/manual.html.LyXconv/4_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-07-13.pngbin0 -> 83521 bytes-rw-r--r--assets/manual.html.LyXconv/5_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-56-34.pngbin0 -> 2789 bytes-rw-r--r--assets/manual.html.LyXconv/6_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-08-02.pngbin0 -> 107762 bytes-rw-r--r--assets/manual.html.LyXconv/7_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-09-01.pngbin0 -> 112579 bytes-rw-r--r--assets/manual.html.LyXconv/8_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_10-10-15.pngbin0 -> 114075 bytes-rw-r--r--assets/manual.html.LyXconv/9_home_moritz_Home_Projekte_Minetest_minetest_m___nual_img_Bildschirmfoto_2016-09-17_09-45-38.pngbin0 -> 16695 bytes-rw-r--r--assets/manual.html.LyXconv/manual.css112
-rw-r--r--assets/manual.html.LyXconv/manual.html236
-rw-r--r--assets/manual.lyx630
-rw-r--r--assets/manual.odtbin0 -> 1047714 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-43-29.pngbin0 -> 9345 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-45-38.pngbin0 -> 16695 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-46-21.pngbin0 -> 18834 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-48-54.pngbin0 -> 14032 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-50-27.pngbin0 -> 4246 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-51-02.pngbin0 -> 3704 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-52-17.pngbin0 -> 13788 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-52-40.pngbin0 -> 13889 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-53-01.pngbin0 -> 15473 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-56-34.pngbin0 -> 2789 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-58-20.pngbin0 -> 10375 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_09-58-39.pngbin0 -> 6880 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-04-12.pngbin0 -> 72813 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-04-57.pngbin0 -> 105448 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-05-51.pngbin0 -> 93538 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-07-13.pngbin0 -> 83521 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-08-02.pngbin0 -> 107762 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-09-01.pngbin0 -> 112579 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-09-17_10-10-15.pngbin0 -> 114075 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2016-12-13_19-25-26.pngbin0 -> 15044 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2017-03-09_11-33-09.pngbin0 -> 3496 bytes-rw-r--r--assets/manual_img/Bildschirmfoto_2017-03-09_11-42-49.pngbin0 -> 25657 bytes-rw-r--r--assets/manual_img/screenshot_20161203_223055.pngbin0 -> 824772 bytes-rw-r--r--assets/manual_img/screenshot_20161203_223328.pngbin0 -> 526640 bytes-rw-r--r--assets/manual_img/screenshot_20161203_230644.pngbin0 -> 610738 bytes-rw-r--r--assets/manual_img/screenshot_20161203_230648.pngbin0 -> 555494 bytes-rw-r--r--assets/manual_img/screenshot_20161203_231622.pngbin0 -> 108256 bytes-rw-r--r--assets/manual_img/screenshot_20161203_232335.pngbin0 -> 1607786 bytes-rw-r--r--assets/manual_img/screenshot_20161203_232545.pngbin0 -> 1527310 bytes-rw-r--r--assets/mbbrailtextures/advtrains_dtrack_rail.pngbin0 -> 4582 bytes-rw-r--r--assets/mbbrailtextures/advtrains_dtrack_rail_atc.pngbin0 -> 4596 bytes-rw-r--r--assets/mbbrailtextures/advtrains_dtrack_rail_detector_on.pngbin0 -> 4579 bytes-rw-r--r--assets/mod.conf4
-rw-r--r--assets/modern_japan_lockomotive.blendbin0 -> 892480 bytes-rw-r--r--assets/modern_japan_wagon.blendbin0 -> 856968 bytes-rw-r--r--assets/oldmodels/locomotive.b3dbin0 -> 181112 bytes-rw-r--r--assets/oldmodels/trackvertical1.b3dbin0 -> 629 bytes-rw-r--r--assets/oldmodels/trackvertical2.b3dbin0 -> 869 bytes-rw-r--r--assets/schild.blendbin0 -> 513160 bytes-rw-r--r--assets/schild.blend1bin0 -> 513160 bytes-rw-r--r--assets/schild.pngbin0 -> 28705 bytes-rw-r--r--assets/schild_flaeche.pngbin0 -> 526 bytes-rw-r--r--assets/signal_wall.blendbin0 -> 512620 bytes-rw-r--r--assets/signal_wall.pngbin0 -> 107155 bytes-rw-r--r--assets/signal_wall_ceiling.blendbin0 -> 512620 bytes-rw-r--r--assets/uban_fancy.blendbin0 -> 1020740 bytes-rw-r--r--assets/wagonfancytexture.pngbin0 -> 42023 bytes-rw-r--r--assets/wagong_fancy.blendbin0 -> 1044484 bytes-rw-r--r--atc_command.txt91
-rw-r--r--description.txt2
-rw-r--r--license.txt838
-rw-r--r--license_media.txt68
-rw-r--r--modpack.conf4
-rw-r--r--privilege_guide.txt43
-rw-r--r--readme.txt47
-rw-r--r--screenshot.pngbin0 -> 809738 bytes-rw-r--r--serialize_lib/api.md (renamed from api.md)0
-rw-r--r--serialize_lib/atomic.lua (renamed from atomic.lua)0
-rw-r--r--serialize_lib/init.lua (renamed from init.lua)0
-rw-r--r--serialize_lib/license.txt662
-rw-r--r--serialize_lib/mod.conf (renamed from mod.conf)0
-rw-r--r--serialize_lib/readme.md (renamed from readme.md)0
-rw-r--r--serialize_lib/serialize.lua (renamed from serialize.lua)0
-rw-r--r--serialize_lib/settingtypes.txt (renamed from settingtypes.txt)0
-rw-r--r--serialize_lib/tests/serialize_spec.lua (renamed from tests/serialize_spec.lua)0
660 files changed, 521717 insertions, 651 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b3180de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+## Eclipse project files & directories
+.project
+.settings
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..64cdcec
--- /dev/null
+++ b/advtrains/atc.lua
@@ -0,0 +1,379 @@
+--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
+ .."style[command;font=mono]"
+ .."field[0.8,1.5;7,1;command;"..attrans("Command")..";"..minetest.formspec_escape(command).."]"
+ if tonumber(mode)==2 then
+ formspec=formspec
+ .."style[command_on;font=mono]"
+ .."field[0.8,3;7,1;command_on;"..attrans("Command (on)")..";"..minetest.formspec_escape(command_on).."]"
+ end
+ else
+ formspec=formspec.."field[0.8,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..bcf7329
--- /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 = ndef.sounds,
+ 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 = ndef.sounds,
+ 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 = ndef.sounds,
+ 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 = ndef.sounds,
+ 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..5808d1a
--- /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,mesecons_switch,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..33fcecd
--- /dev/null
+++ b/advtrains/p_mesecon_iface.lua
@@ -0,0 +1,58 @@
+-- p_mesecon_iface.lua
+-- Mesecons interface by overriding the switch
+
+if minetest.get_modpath("mesecons_switch") == nil 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..5fb1d1b
--- /dev/null
+++ b/advtrains/signals.lua
@@ -0,0 +1,362 @@
+--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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+ if advtrains.interlocking then
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+ 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)
+
diff --git a/advtrains_interlocking/approach.lua b/advtrains_interlocking/approach.lua
new file mode 100644
index 0000000..f60468a
--- /dev/null
+++ b/advtrains_interlocking/approach.lua
@@ -0,0 +1,126 @@
+-- Interlocking counterpart of LZB, which has been moved into the core...
+-- Registers LZB callback for signal management.
+
+--[[
+usage of lzbdata:
+{
+ travsht = boolean indicating whether the train will be a shunt move at "trav"
+ travspd = speed restriction at end of traverser
+ travwspd = warning speed res.t
+}
+]]
+
+local SHUNT_SPEED_MAX = advtrains.SHUNT_SPEED_MAX
+
+local il = advtrains.interlocking
+
+local function get_over_function(speed, shunt)
+ return function(pos, id, train, index, speed, lzbdata)
+ if speed == 0 and minetest.settings:get_bool("at_il_force_lzb_halt") then
+ atwarn(id,"overrun LZB 0 restriction (red signal) ",pos)
+ -- Set train 1 index backward. Hope this does not lead to bugs...
+ --train.index = index - 0.5
+ train.speed_restriction = 0
+
+ --TODO temporary
+ --advtrains.drb_dump(id)
+ --error("Debug: "..id.." triggered LZB-0")
+ else
+ train.speed_restriction = speed
+ train.is_shunt = shunt
+ end
+ --atdebug("train drove over IP: speed=",speed,"shunt=",shunt)
+ end
+end
+
+advtrains.tnc_register_on_approach(function(pos, id, train, index, has_entered, lzbdata)
+
+ --atdebug(id,"IL ApprC",pos,index,lzbdata)
+ --train.debug = advtrains.print_concat_table({train.is_shunt,"|",index,"|",lzbdata})
+
+ local pts = advtrains.roundfloorpts(pos)
+ local cn = train.path_cn[index]
+ local travsht = lzbdata.il_shunt
+
+ local travspd = lzbdata.il_speed
+
+ if travsht==nil then
+ -- lzbdata has reset
+ travspd = train.speed_restriction
+ travsht = train.is_shunt or false
+ end
+
+
+
+ -- check for signal
+ local asp, spos = il.db.get_ip_signal_asp(pts, cn)
+
+ -- do ARS if needed
+ local ars_enabled = not train.ars_disable
+ -- Note on ars_disable:
+ -- Theoretically, the ars_disable flag would need to behave like the speed restriction field: it should be
+ -- stored in lzbdata and updated once the train drives over. However, for the sake of simplicity, it is simply
+ -- a value in the train. In this case, this is sufficient because once a train triggers ARS for the first time,
+ -- resetting the path does not matter to the set route and ARS doesn't need to be called again.
+ if spos and ars_enabled then
+ --atdebug(id,"IL Spos (ARS)",spos,asp)
+ local sigd = il.db.get_sigd_for_signal(spos)
+ if sigd then
+ il.ars_check(sigd, train)
+ end
+ end
+ --atdebug("trav: ",pos, cn, asp, spos, "travsht=", lzb.travsht)
+ local lspd
+ if asp then
+ --atdebug(id,"IL Signal",spos, asp, lzbdata, "trainstate", train.speed_restriction, train.is_shunt)
+ local nspd = 0
+ --interpreting aspect and determining speed to proceed
+ if travsht then
+ --shunt move
+ if asp.shunt then
+ nspd = SHUNT_SPEED_MAX
+ elseif asp.proceed_as_main and asp.main ~= 0 then
+ nspd = asp.main
+ travsht = false
+ end
+ else
+ --train move
+ if asp.main ~= 0 then
+ nspd = asp.main
+ elseif asp.shunt then
+ nspd = SHUNT_SPEED_MAX
+ travsht = true
+ end
+ end
+ -- nspd can now be: 1. !=0: new speed restriction, 2. =0: stop here or 3. nil: keep travspd
+ if nspd then
+ if nspd == -1 then
+ travspd = nil
+ else
+ travspd = nspd
+ end
+ end
+
+ --atdebug("ns,ts", nspd, travspd)
+
+ lspd = travspd
+
+ local udata = {signal_pos = spos}
+ local callback = get_over_function(lspd, travsht)
+ lzbdata.il_shunt = travsht
+ lzbdata.il_speed = travspd
+ --atdebug("new lzbdata",lzbdata)
+ advtrains.lzb_add_checkpoint(train, index, lspd, callback, lzbdata, udata)
+ end
+end)
+
+-- Set the ars_disable flag to the value passed
+-- Triggers a path invalidation if set to false
+function advtrains.interlocking.ars_set_disable(train, value)
+ if value then
+ train.ars_disable = true
+ else
+ train.ars_disable = nil
+ minetest.after(0, advtrains.path_invalidate, train)
+ end
+end
diff --git a/advtrains_interlocking/ars.lua b/advtrains_interlocking/ars.lua
new file mode 100644
index 0000000..434ae2c
--- /dev/null
+++ b/advtrains_interlocking/ars.lua
@@ -0,0 +1,155 @@
+-- ars.lua
+-- automatic routesetting
+
+--[[
+ The "ARS table" and its effects:
+ Every route has (or can have) an associated ARS table. This can either be
+ ars = { [n] = {ln="<line>"}/{rc="<routingcode>"}/{c="<a comment>"} }
+ a list of rules involving either line or routingcode matchers (or comments, those are ignored)
+ The first matching rule determines the route to set.
+ - or -
+ ars = {default = true}
+ this means that all trains that no other rule matches on should use this route
+
+ Compound ("and") conjunctions are not supported (--TODO should they?)
+
+ For editing, those tables are transformed into lines in a text area:
+ {ln=...} -> LN ...
+ {rc=...} -> RC ...
+ {c=...} -> #...
+ {default=true} -> *
+ See also route_ui.lua
+]]
+
+local il = advtrains.interlocking
+
+-- The ARS data are saved in a table format, but are entered in text format. Utility functions to transform between both.
+function il.ars_to_text(arstab)
+ if not arstab then
+ return ""
+ end
+
+ local txt = {}
+
+ for i, arsent in ipairs(arstab) do
+ local n = ""
+ if arsent.n then
+ n = "!"
+ end
+ if arsent.ln then
+ txt[#txt+1] = n.."LN "..arsent.ln
+ elseif arsent.rc then
+ txt[#txt+1] = n.."RC "..arsent.rc
+ elseif arsent.c then
+ txt[#txt+1] = "#"..arsent.c
+ end
+ end
+
+ if arstab.default then
+ return "*\n" .. table.concat(txt, "\n")
+ end
+ return table.concat(txt, "\n")
+end
+
+function il.text_to_ars(t)
+ if t=="" then
+ return nil
+ elseif t=="*" then
+ return {default=true}
+ end
+ local arstab = {}
+ for line in string.gmatch(t, "[^\r\n]+") do
+ if line=="*" then
+ arstab.default = true
+ else
+ local c, v = string.match(line, "^(...?)%s(.*)$")
+ if c and v then
+ local n = nil
+ if string.sub(c,1,1) == "!" then
+ n = true
+ c = string.sub(c,2)
+ end
+ local tt=string.upper(c)
+ if tt=="LN" then
+ arstab[#arstab+1] = {ln=v, n=n}
+ elseif tt=="RC" then
+ arstab[#arstab+1] = {rc=v, n=n}
+ end
+ else
+ local ct = string.match(line, "^#(.*)$")
+ if ct then arstab[#arstab+1] = {c = ct} end
+ end
+ end
+ end
+ return arstab
+end
+
+local function find_rtematch(routes, train)
+ local default
+ for rteid, route in ipairs(routes) do
+ if route.ars then
+ if route.ars.default then
+ default = rteid
+ else
+ if il.ars_check_rule_match(route.ars, train) then
+ return rteid
+ end
+ end
+ end
+ end
+ return default
+end
+
+-- Checks whether ARS rule explicitly matches. This does not take into account the "default" field, since a wider context is required for this.
+-- Returns the rule number that matched, or nil if nothing matched
+function il.ars_check_rule_match(ars, train)
+ if not ars then
+ return nil
+ end
+ local line = train.line
+ local routingcode = train.routingcode
+ for arskey, arsent in ipairs(ars) do
+ --atdebug(arsent, line, routingcode)
+ if arsent.n then
+ -- rule is inverse...
+ if arsent.ln and (not line or arsent.ln ~= line) then
+ return arskey
+ elseif arsent.rc and (not routingcode or not string.find(" "..routingcode.." ", " "..arsent.rc.." ", nil, true)) then
+ return arskey
+ end
+ return nil
+ end
+
+ if arsent.ln and line and arsent.ln == line then
+ return arskey
+ elseif arsent.rc and routingcode and string.find(" "..routingcode.." ", " "..arsent.rc.." ", nil, true) then
+ return arskey
+ end
+ end
+ return nil
+end
+
+function advtrains.interlocking.ars_check(sigd, train)
+ local tcbs = il.db.get_tcbs(sigd)
+ if not tcbs or not tcbs.routes then return end
+
+ if tcbs.ars_disabled then
+ -- No-ARS mode of signal.
+ -- ignore...
+ return
+ end
+
+ if tcbs.routeset then
+ -- ARS is not in effect when a route is already set
+ -- just "punch" routesetting, just in case callback got lost.
+ minetest.after(0, il.route.update_route, sigd, tcbs, nil, nil)
+ return
+ end
+
+ local rteid = find_rtematch(tcbs.routes, train)
+ if rteid then
+ --delay routesetting, it should not occur inside train step
+ -- using after here is OK because that gets called on every path recalculation
+ minetest.after(0, il.route.update_route, sigd, tcbs, rteid, nil)
+ end
+end
diff --git a/advtrains_interlocking/database.lua b/advtrains_interlocking/database.lua
new file mode 100644
index 0000000..a35d446
--- /dev/null
+++ b/advtrains_interlocking/database.lua
@@ -0,0 +1,648 @@
+-- interlocking/database.lua
+-- saving the location of TCB's, their neighbors and their state
+--[[
+
+== THIS COMMENT IS PARTIALLY INCORRECT AND OUTDATED! ==
+
+The interlocking system is based on track circuits.
+Track circuit breaks must be manually set by the user. Signals must be assigned to track circuit breaks and to a direction(connid).
+To simplify the whole system, there is no overlap.
+== Trains ==
+Trains always occupy certain track circuits. These are shown red in the signalbox view (TRAIN occupation entry).
+== Database storage ==
+The things that are actually saved are the Track Circuit Breaks. Each TCB holds a list of the TCBs that are adjacent in each direction.
+TC occupation/state is then saved inside each (TCB,Direction) and held in sync across all TCBs adjacent to this one. If something should not be in sync,
+all entries are merged to perform the most restrictive setup.
+== Traverser function ==
+To determine and update the list of neighboring TCBs, we need a traverser function.
+It will start at one TCB in a specified direction (connid) and use get_adjacent_rail to crawl along the track. When encountering a turnout or a crossing,
+it needs to branch(call itself recursively) to find all required TCBs. Those found TCBs are then saved in a list as tuples (TCB,Dir)
+In the last step, they exchange their neighbors.
+== TC states ==
+A track circuit does not have a state as such, but has more or less a list of "reservations"
+type can be one of these:
+TRAIN See Trains obove
+ROUTE Route set from a signal, but no train has yet passed that signal.
+Not implemented (see note by reversible): OWNED - former ROUTE segments that a train has begun passing (train_id assigned)
+ - Space behind a train up to the next signal, when a TC is set as REVERSIBLE
+Certain TCs can be marked as "allow call-on".
+== Route setting: ==
+Routes are set from a signal (the entry signal) to another signal facing the same direction (the exit signal)
+Remember that signals are assigned to a TCB and a connid.
+Whenever this is done, the following track circuits are set "reserved" by the train by saving the entry signal's ID:
+- all TCs on the direct way of the route - set as ROUTE
+Route setting fails whenever any TC that we want to set ROUTE to is already set ROUTE or TRAIN from another signal (except call-on, see below)
+Apart from this, we need to set turnouts
+- Turnouts on the track are set held as ROUTE
+- Turnouts that purpose as flank protection are set held as FLANK (NOTE: left as an idea for later, because it's not clear how to do this properly without an engineer)
+Note: In SimSig, it is possible to set a route into an still occupied section on the victoria line sim. (at the depot exit at seven sisters), although
+ there are still segments set ahead of the first train passing, remaining from another route.
+ Because our system will be able to remember "requested routes" and set them automatically once ready, this is not necessary here.
+== Call-On/Multiple Trains ==
+It will be necessary to join and split trains using call-on routes. A call-on route may be set when:
+- there are no ROUTE reservations
+- there are TRAIN reservations only inside TCs that have "allow call-on" set
+== TC Properties ==
+Note: Reversible property will not be implemented, assuming everything as non-rev.
+This is sufficient to cover all use cases, and is done this way in reality.
+ REVERSIBLE - Whether trains are allowed to reverse while on track circuit
+ This property is supposed to be set for station tracks, where there is a signal at each end, and for sidings.
+ It should in no case be set for TCs covering turnouts, or for main running lines.
+ When a TC is not set as reversible, the OWNED status is cleared from the TC right after the train left it,
+ to allow other trains to pass it.
+ If it is set reversible, interlocking will keep the OWNED state behind the train up to the next signal, clearing it
+ as soon as the train passes another signal or enters a non-reversible section.
+CALL_ON_ALLOWED - Whether this TC being blocked (TRAIN or ROUTE) does not prevent shunt routes being set through this TC
+== More notes ==
+- It may not be possible to switch turnouts when their TC has any state entry
+
+== Route releasing (TORR) ==
+A train passing through a route happens as follows:
+Route set from entry to exit signal
+Train passes entry signal and enters first TC past the signal
+-> Route from signal cleared (TCs remain locked)
+-> ROUTE status of first TC past signal cleared
+Train continues along the route.
+Whenever train leaves a TC
+-> Clearing any routes set from this TC outward recursively - see "Reversing problem"
+Whenever train enters a TC
+-> Clear route status from the just entered TC
+Note that this prohibits by design that the train clears the route ahead of it.
+== Reversing Problem ==
+Encountered at the Royston simulation in SimSig. It is solved there by imposing a time limit on the set route. Call-on routes can somehow be set anyway.
+Imagine this setup: (T=Train, R=Route, >=in_dir TCB)
+ O-| Royston P2 |-O
+T->---|->RRR-|->RRR-|--
+Train T enters from the left, the route is set to the right signal. But train is supposed to reverse here and stops this way:
+ O-| Royston P2 |-O
+------|-TTTT-|->RRR-|--
+The "Route" on the right is still set. Imposing a timeout here is a thing only professional engineers can determine, not an algorithm.
+ O-| Royston P2 |-O
+<-T---|------|->RRR-|--
+The train has left again, while route on the right is still set.
+So, we have to clear the set route when the train has left the left TC.
+This does not conflict with call-on routes, because both station tracks are set as "allow call-on"
+Because none of the routes extends past any non-call-on sections, call-on route would be allowed here, even though the route
+is locked in opposite direction at the time of routesetting.
+Another case of this:
+--TTT/--|->RRR--
+The / here is a non-interlocked turnout (to a non-frequently used siding). For some reason, there is no exit node there,
+so the route is set to the signal at the right end. The train is taking the exit to the siding and frees the TC, without ever
+having touched the right TC.
+]]--
+
+local TRAVERSER_LIMIT = 1000
+
+
+local ildb = {}
+
+local track_circuit_breaks = {}
+local track_sections = {}
+
+-- Assignment of signals to TCBs
+local signal_assignments = {}
+
+-- track+direction -> signal position
+local influence_points = {}
+
+advtrains.interlocking.npr_rails = {}
+
+
+function ildb.load(data)
+ if not data then return end
+ if data.tcbs then
+ track_circuit_breaks = data.tcbs
+ end
+ if data.ts then
+ track_sections = data.ts
+ end
+ if data.signalass then
+ signal_assignments = data.signalass
+ end
+ if data.rs_locks then
+ advtrains.interlocking.route.rte_locks = data.rs_locks
+ end
+ if data.rs_callbacks then
+ advtrains.interlocking.route.rte_callbacks = data.rs_callbacks
+ end
+ if data.influence_points then
+ influence_points = data.influence_points
+ end
+ if data.npr_rails then
+ advtrains.interlocking.npr_rails = data.npr_rails
+ end
+
+ --COMPATIBILITY to Signal aspect format
+ -- TODO remove in time...
+ for pts,tcb in pairs(track_circuit_breaks) do
+ for connid, tcbs in ipairs(tcb) do
+ if tcbs.routes then
+ for _,route in ipairs(tcbs.routes) do
+ if route.aspect then
+ -- transform the signal aspect format
+ local asp = route.aspect
+ if type(asp.main) == "table" then
+ atwarn("Transforming route aspect of signal",pts,"/",connid,"")
+ if asp.main.free then
+ asp.main = asp.main.speed
+ else
+ asp.main = 0
+ end
+ if asp.dst.free then
+ asp.dst = asp.dst.speed
+ else
+ asp.dst = 0
+ end
+ asp.proceed_as_main = asp.shunt.proceed_as_main
+ asp.shunt = asp.shunt.free
+ -- Note: info table not transferred, it's not used right now
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function ildb.save()
+ return {
+ tcbs = track_circuit_breaks,
+ ts=track_sections,
+ signalass = signal_assignments,
+ rs_locks = advtrains.interlocking.route.rte_locks,
+ rs_callbacks = advtrains.interlocking.route.rte_callbacks,
+ influence_points = influence_points,
+ npr_rails = advtrains.interlocking.npr_rails,
+ }
+end
+
+--
+--[[
+TCB data structure
+{
+-- This is the "A" side of the TCB
+[1] = { -- Variant: with adjacent TCs.
+ ts_id = <id> -- ID of the assigned track section
+ signal = <pos> -- optional: when set, routes can be set from this tcb/direction and signal
+ -- aspect will be set accordingly.
+ routeset = <index in routes> -- Route set from this signal. This is the entry that is cleared once
+ -- train has passed the signal. (which will set the aspect to "danger" again)
+ route_committed = <boolean> -- When setting/requesting a route, routetar will be set accordingly,
+ -- while the signal still displays danger and nothing is written to the TCs
+ -- As soon as the route can actually be set, all relevant TCs and turnouts are set and this field
+ -- is set true, clearing the signal
+ aspect = <asp> -- The aspect the signal should show. If this is nil, should show the most restrictive aspect (red)
+ signal_name = <string> -- The human-readable name of the signal, only for documenting purposes
+ routes = { <route definition> } -- a collection of routes from this signal
+ route_auto = <boolean> -- When set, we will automatically re-set the route (designated by routeset)
+},
+-- This is the "B" side of the TCB
+[2] = { -- Variant: end of track-circuited area (initial state of TC)
+ ts_id = nil, -- this is the indication for end_of_interlocking
+ section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc,
+ -- or from the tc formspec.
+}
+}
+
+Track section
+[id] = {
+ name = "Some human-readable name"
+ tc_breaks = { <signal specifier>,... } -- Bounding TC's (signal specifiers)
+ -- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other
+ route = {
+ origin = <signal>, -- route origin
+ entry = <sigd>, -- supposed train entry point
+ rsn = <string>,
+ first = <bool>
+ }
+ route_post = {
+ locks = {[n] = <pts>}
+ next = <sigd>
+ }
+ -- Set whenever a route has been set through this TC. It saves the origin tcb id and side
+ -- (=the origin signal). rsn is some description to be shown to the user
+ -- first says whether to clear the routesetting status from the origin signal.
+ -- locks contains the positions where locks are held by this ts.
+ -- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section.
+ trains = {<id>, ...} -- Set whenever a train (or more) reside in this TC
+}
+
+
+Signal specifier (sigd) (a pair of TCB/Side):
+{p = <pos>, s = <1/2>}
+
+Signal Assignments: reverse lookup of signals assigned to TCBs
+signal_assignments = {
+[<signal pts>] = <sigd>
+}
+]]
+
+
+--
+function ildb.create_tcb(pos)
+ local new_tcb = {
+ [1] = {},
+ [2] = {},
+ }
+ local pts = advtrains.roundfloorpts(pos)
+ if not track_circuit_breaks[pts] then
+ track_circuit_breaks[pts] = new_tcb
+ return true
+ else
+ return false
+ end
+end
+
+function ildb.get_tcb(pos)
+ local pts = advtrains.roundfloorpts(pos)
+ return track_circuit_breaks[pts]
+end
+
+function ildb.get_tcbs(sigd)
+ local tcb = ildb.get_tcb(sigd.p)
+ if not tcb then return nil end
+ return tcb[sigd.s]
+end
+
+
+function ildb.create_ts(sigd)
+ local tcbs = ildb.get_tcbs(sigd)
+ local id = advtrains.random_id()
+
+ while track_sections[id] do
+ id = advtrains.random_id()
+ end
+
+ track_sections[id] = {
+ name = "Section "..id,
+ tc_breaks = { sigd }
+ }
+ tcbs.ts_id = id
+end
+
+function ildb.get_ts(id)
+ return track_sections[id]
+end
+
+
+
+-- various helper functions handling sigd's
+local sigd_equal = advtrains.interlocking.sigd_equal
+local function insert_sigd_nodouble(list, sigd)
+ for idx, cmp in pairs(list) do
+ if sigd_equal(sigd, cmp) then
+ return
+ end
+ end
+ table.insert(list, sigd)
+end
+
+
+-- This function will actually handle the node that is in connid direction from the node at pos
+-- so, this needs the conns of the node at pos, since these are already calculated
+local function traverser(found_tcbs, pos, conns, connid, count, brk_when_found_n)
+ local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid, advtrains.all_tracktypes)
+ if not adj_pos then
+ --atdebug("Traverser found end-of-track at",pos, connid)
+ return
+ end
+ -- look whether there is a TCB here
+ if #next_conns == 2 then --if not, don't even try!
+ local tcb = ildb.get_tcb(adj_pos)
+ if tcb then
+ -- done with this branch
+ --atdebug("Traverser found tcb at",adj_pos, adj_connid)
+ insert_sigd_nodouble(found_tcbs, {p=adj_pos, s=adj_connid})
+ return
+ end
+ end
+ -- recursion abort condition
+ if count > TRAVERSER_LIMIT then
+ --atdebug("Traverser hit counter at",adj_pos, adj_connid)
+ return true
+ end
+ -- continue traversing
+ local counter_hit = false
+ for nconnid, nconn in ipairs(next_conns) do
+ if adj_connid ~= nconnid then
+ counter_hit = counter_hit or traverser(found_tcbs, adj_pos, next_conns, nconnid, count + 1, brk_when_found_n)
+ if brk_when_found_n and #found_tcbs>=brk_when_found_n then
+ break
+ end
+ end
+ end
+ return counter_hit
+end
+
+
+
+-- Merges the TS with merge_id into root_id and then deletes merge_id
+local function merge_ts(root_id, merge_id)
+ local rts = ildb.get_ts(root_id)
+ local mts = ildb.get_ts(merge_id)
+ if not mts then return end -- This may be the case when sync_tcb_neighbors
+ -- inserts the same id twice. do nothing.
+
+ if not ildb.may_modify_ts(rts) then return false end
+ if not ildb.may_modify_ts(mts) then return false end
+
+ -- cobble together the list of TCBs
+ for _, msigd in ipairs(mts.tc_breaks) do
+ local tcbs = ildb.get_tcbs(msigd)
+ if tcbs then
+ insert_sigd_nodouble(rts.tc_breaks, msigd)
+ tcbs.ts_id = root_id
+ end
+ advtrains.interlocking.show_tcb_marker(msigd.p)
+ end
+ -- done
+ track_sections[merge_id] = nil
+end
+
+local lntrans = { "A", "B" }
+local function sigd_to_string(sigd)
+ return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
+end
+
+-- Check for near TCBs and connect to their TS if they have one, and syncs their data.
+function ildb.sync_tcb_neighbors(pos, connid)
+ local found_tcbs = { {p = pos, s = connid} }
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if not node_ok then
+ atwarn("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos))
+ return
+ end
+
+ --atdebug("Traversing from ",pos, connid)
+ local counter_hit = traverser(found_tcbs, pos, conns, connid, 0)
+
+ local ts_id
+ local list_eoi = {}
+ local list_ok = {}
+ local list_mismatch = {}
+ local ts_to_merge = {}
+
+ for idx, sigd in pairs(found_tcbs) do
+ local tcbs = ildb.get_tcbs(sigd)
+ if not tcbs.ts_id then
+ --atdebug("Sync: put",sigd_to_string(sigd),"into list_eoi")
+ table.insert(list_eoi, sigd)
+ elseif not ts_id and tcbs.ts_id then
+ if not ildb.get_ts(tcbs.ts_id) then
+ atwarn("Track section database is inconsistent, there's no TS with ID=",tcbs.ts_id)
+ tcbs.ts_id = nil
+ table.insert(list_eoi, sigd)
+ else
+ --atdebug("Sync: put",sigd_to_string(sigd),"into list_ok")
+ ts_id = tcbs.ts_id
+ table.insert(list_ok, sigd)
+ end
+ elseif ts_id and tcbs.ts_id and tcbs.ts_id ~= ts_id then
+ atwarn("Track section database is inconsistent, sections share track!")
+ atwarn("Merging",tcbs.ts_id,"into",ts_id,".")
+ table.insert(list_mismatch, sigd)
+ table.insert(ts_to_merge, tcbs.ts_id)
+ end
+ end
+ if ts_id then
+ local ts = ildb.get_ts(ts_id)
+ for _, sigd in ipairs(list_eoi) do
+ local tcbs = ildb.get_tcbs(sigd)
+ tcbs.ts_id = ts_id
+ table.insert(ts.tc_breaks, sigd)
+ advtrains.interlocking.show_tcb_marker(sigd.p)
+ end
+ for _, mts in ipairs(ts_to_merge) do
+ merge_ts(ts_id, mts)
+ end
+ end
+end
+
+function ildb.link_track_sections(merge_id, root_id)
+ if merge_id == root_id then
+ return
+ end
+ merge_ts(root_id, merge_id)
+end
+
+function ildb.remove_from_interlocking(sigd)
+ local tcbs = ildb.get_tcbs(sigd)
+ if not ildb.may_modify_tcbs(tcbs) then return false end
+
+ if tcbs.ts_id then
+ local tsid = tcbs.ts_id
+ local ts = ildb.get_ts(tsid)
+ if not ts then
+ tcbs.ts_id = nil
+ return true
+ end
+
+ -- remove entry from the list
+ local idx = 1
+ while idx <= #ts.tc_breaks do
+ local cmp = ts.tc_breaks[idx]
+ if sigd_equal(sigd, cmp) then
+ table.remove(ts.tc_breaks, idx)
+ else
+ idx = idx + 1
+ end
+ end
+ tcbs.ts_id = nil
+
+ --ildb.sync_tcb_neighbors(sigd.p, sigd.s)
+
+ if #ts.tc_breaks == 0 then
+ track_sections[tsid] = nil
+ end
+ end
+ advtrains.interlocking.show_tcb_marker(sigd.p)
+ if tcbs.signal then
+ return false
+ end
+ return true
+end
+
+function ildb.remove_tcb(pos)
+ local pts = advtrains.roundfloorpts(pos)
+ if not track_circuit_breaks[pts] then
+ return true --FIX: not an error, because tcb is already removed
+ end
+ for connid=1,2 do
+ if not ildb.remove_from_interlocking({p=pos, s=connid}) then
+ return false
+ end
+ end
+ track_circuit_breaks[pts] = nil
+ return true
+end
+
+function ildb.dissolve_ts(ts_id)
+ local ts = ildb.get_ts(ts_id)
+ if not ildb.may_modify_ts(ts) then return false end
+ local tcbr = advtrains.merge_tables(ts.tc_breaks)
+ for _,sigd in ipairs(tcbr) do
+ ildb.remove_from_interlocking(sigd)
+ end
+ -- Note: ts gets removed in the moment of the removal of the last TCB.
+ return true
+end
+
+-- Returns true if it is allowed to modify any property of a track section, such as
+-- - removing TCBs
+-- - merging and dissolving sections
+-- As of now the action will be denied if a route is set or if a train is in the section.
+function ildb.may_modify_ts(ts)
+ if ts.route or ts.route_post or (ts.trains and #ts.trains>0) then
+ return false
+ end
+ return true
+end
+
+
+function ildb.may_modify_tcbs(tcbs)
+ if tcbs.ts_id then
+ local ts = ildb.get_ts(tcbs.ts_id)
+ if ts and not ildb.may_modify_ts(ts) then
+ return false
+ end
+ end
+ return true
+end
+
+-- Utilize the traverser to find the track section at the specified position
+-- Returns:
+-- ts_id, origin - the first found ts and the sigd of the found tcb
+-- nil - there were no TCBs in TRAVERSER_MAX range of the position
+-- false - the first found TCB stated End-Of-Interlocking, or track ends were reached
+function ildb.get_ts_at_pos(pos)
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if not node_ok then
+ error("get_ts_at_pos but node is NOK: "..minetest.pos_to_string(pos))
+ end
+ local limit_hit = false
+ local found_tcbs = {}
+ for connid, conn in ipairs(conns) do -- Note: a breadth-first-search would be better for performance
+ limit_hit = limit_hit or traverser(found_tcbs, pos, conns, connid, 0, 1)
+ if #found_tcbs >= 1 then
+ local tcbs = ildb.get_tcbs(found_tcbs[1])
+ local ts
+ if tcbs.ts_id then
+ return tcbs.ts_id, found_tcbs[1]
+ else
+ return false
+ end
+ end
+ end
+ if limit_hit then
+ -- there was at least one limit hit
+ return nil
+ else
+ -- all traverser ends were track ends
+ return false
+ end
+end
+
+
+-- returns the sigd the signal at pos belongs to, if this is known
+function ildb.get_sigd_for_signal(pos)
+ local pts = advtrains.roundfloorpts(pos)
+ local sigd = signal_assignments[pts]
+ if sigd then
+ if not ildb.get_tcbs(sigd) then
+ signal_assignments[pts] = nil
+ return nil
+ end
+ return sigd
+ end
+ return nil
+end
+function ildb.set_sigd_for_signal(pos, sigd)
+ local pts = advtrains.roundfloorpts(pos)
+ signal_assignments[pts] = sigd
+end
+
+-- checks if there's any influence point set to this position
+-- if purge is true, checks whether the associated signal still exists
+-- and deletes the ip if not.
+function ildb.is_ip_at(pos, purge)
+ local pts = advtrains.roundfloorpts(pos)
+ if influence_points[pts] then
+ if purge then
+ -- is there still a signal assigned to it?
+ for connid, sigpos in pairs(influence_points[pts]) do
+ local asp = advtrains.interlocking.signal_get_aspect(sigpos)
+ if not asp then
+ atlog("Clearing orphaned signal influence point", pts, "/", connid)
+ ildb.clear_ip_signal(pts, connid)
+ end
+ end
+ -- if there's no side left after purging, return false
+ if not influence_points[pts] then return false end
+ end
+ return true
+ end
+ return false
+end
+
+-- checks if a signal is influencing here
+function ildb.get_ip_signal(pts, connid)
+ if influence_points[pts] then
+ return influence_points[pts][connid]
+ end
+end
+
+-- Tries to get aspect to obey here, if there
+-- is a signal ip at this location
+-- auto-clears invalid assignments
+function ildb.get_ip_signal_asp(pts, connid)
+ local p = ildb.get_ip_signal(pts, connid)
+ if p then
+ local asp = advtrains.interlocking.signal_get_aspect(p)
+ if not asp then
+ atlog("Clearing orphaned signal influence point", pts, "/", connid)
+ ildb.clear_ip_signal(pts, connid)
+ return nil
+ end
+ return asp, p
+ end
+ return nil
+end
+
+-- set signal assignment.
+function ildb.set_ip_signal(pts, connid, spos)
+ ildb.clear_ip_by_signalpos(spos)
+ if not influence_points[pts] then
+ influence_points[pts] = {}
+ end
+ influence_points[pts][connid] = spos
+end
+-- clear signal assignment.
+function ildb.clear_ip_signal(pts, connid)
+ influence_points[pts][connid] = nil
+ for _,_ in pairs(influence_points[pts]) do
+ return
+ end
+ influence_points[pts] = nil
+end
+
+function ildb.get_ip_by_signalpos(spos)
+ for pts,tab in pairs(influence_points) do
+ for connid,pos in pairs(tab) do
+ if vector.equals(pos, spos) then
+ return pts, connid
+ end
+ end
+ end
+end
+-- clear signal assignment given the signal position
+function ildb.clear_ip_by_signalpos(spos)
+ local pts, connid = ildb.get_ip_by_signalpos(spos)
+ if pts then ildb.clear_ip_signal(pts, connid) end
+end
+
+
+advtrains.interlocking.db = ildb
+
+
+
+
diff --git a/advtrains_interlocking/demosignals.lua b/advtrains_interlocking/demosignals.lua
new file mode 100644
index 0000000..1c1b8b2
--- /dev/null
+++ b/advtrains_interlocking/demosignals.lua
@@ -0,0 +1,97 @@
+-- Demonstration signals
+-- Those can display the 3 main aspects of Ks signals
+
+-- Note that the group value of advtrains_signal is 2, which means "step 2 of signal capabilities"
+-- advtrains_signal=1 is meant for signals that do not implement set_aspect.
+
+
+local setaspect = function(pos, node, asp)
+ if asp.main == 0 then
+ advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_danger"})
+ else
+ if asp.dst ~= 0 and asp.main == -1 then
+ advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_free"})
+ else
+ advtrains.ndb.swap_node(pos, {name="advtrains_interlocking:ds_slow"})
+ end
+ end
+ local meta = minetest.get_meta(pos)
+ if meta then
+ meta:set_string("infotext", minetest.serialize(asp))
+ end
+end
+
+local suppasp = {
+ main = {0, 6, -1},
+ dst = {0, false},
+ shunt = false,
+ proceed_as_main = true,
+ info = {
+ call_on = false,
+ dead_end = false,
+ w_speed = nil,
+ }
+}
+
+minetest.register_node("advtrains_interlocking:ds_danger", {
+ description = "Demo signal at Danger",
+ tiles = {"at_il_signal_asp_danger.png"},
+ groups = {
+ cracky = 3,
+ advtrains_signal = 2,
+ save_in_at_nodedb = 1,
+ },
+ advtrains = {
+ set_aspect = setaspect,
+ supported_aspects = suppasp,
+ get_aspect = function(pos, node)
+ return advtrains.interlocking.DANGER
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+})
+minetest.register_node("advtrains_interlocking:ds_free", {
+ description = "Demo signal at Free",
+ tiles = {"at_il_signal_asp_free.png"},
+ groups = {
+ cracky = 3,
+ advtrains_signal = 2,
+ save_in_at_nodedb = 1,
+ },
+ advtrains = {
+ set_aspect = setaspect,
+ supported_aspects = suppasp,
+ get_aspect = function(pos, node)
+ return {
+ main = -1,
+ }
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+})
+minetest.register_node("advtrains_interlocking:ds_slow", {
+ description = "Demo signal at Slow",
+ tiles = {"at_il_signal_asp_slow.png"},
+ groups = {
+ cracky = 3,
+ advtrains_signal = 2,
+ save_in_at_nodedb = 1,
+ },
+ advtrains = {
+ set_aspect = setaspect,
+ supported_aspects = suppasp,
+ get_aspect = function(pos, node)
+ return {
+ main = 6,
+ }
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+})
+
diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua
new file mode 100644
index 0000000..a2f5882
--- /dev/null
+++ b/advtrains_interlocking/init.lua
@@ -0,0 +1,30 @@
+-- Advtrains interlocking system
+-- See database.lua for a detailed explanation
+
+advtrains.interlocking = {}
+
+advtrains.SHUNT_SPEED_MAX = 6
+
+function advtrains.interlocking.sigd_equal(sigd, cmp)
+ return vector.equals(sigd.p, cmp.p) and sigd.s==cmp.s
+end
+
+
+local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM
+
+dofile(modpath.."database.lua")
+dofile(modpath.."signal_api.lua")
+dofile(modpath.."demosignals.lua")
+dofile(modpath.."train_sections.lua")
+dofile(modpath.."route_prog.lua")
+dofile(modpath.."routesetting.lua")
+dofile(modpath.."tcb_ts_ui.lua")
+dofile(modpath.."route_ui.lua")
+dofile(modpath.."tool.lua")
+
+dofile(modpath.."approach.lua")
+dofile(modpath.."ars.lua")
+dofile(modpath.."tsr_rail.lua")
+
+
+minetest.register_privilege("interlocking", {description = "Can set up track sections, routes and signals.", give_to_singleplayer = true})
diff --git a/advtrains_interlocking/mod.conf b/advtrains_interlocking/mod.conf
new file mode 100644
index 0000000..3b2d029
--- /dev/null
+++ b/advtrains_interlocking/mod.conf
@@ -0,0 +1,7 @@
+name=advtrains_interlocking
+title=Advanced Trains Interlocking System
+description=Interlocking system for Advanced Trains
+author=orwell96
+
+depends=advtrains
+optional_depends=advtrains_train_track
diff --git a/advtrains_interlocking/models/at_il_tcb_node.obj b/advtrains_interlocking/models/at_il_tcb_node.obj
new file mode 100644
index 0000000..bb6aab5
--- /dev/null
+++ b/advtrains_interlocking/models/at_il_tcb_node.obj
@@ -0,0 +1,248 @@
+# Blender v2.76 (sub 0) OBJ File: ''
+# www.blender.org
+mtllib at_il_tcb_node.mtl
+o Cube
+v 0.038370 -0.500000 -0.038370
+v 0.038370 -0.500000 0.038370
+v -0.038370 -0.500000 0.038370
+v -0.038370 -0.500000 -0.038370
+v 0.038370 0.098086 -0.038370
+v 0.038370 0.098086 0.038370
+v -0.038370 0.098086 0.038370
+v -0.038370 0.098086 -0.038370
+v -0.182395 0.065479 0.099357
+v -0.182395 0.182395 0.099357
+v -0.182395 0.065479 -0.171034
+v -0.182395 0.182395 -0.171034
+v 0.182395 0.065479 0.099357
+v 0.182395 0.182395 0.099357
+v 0.182395 0.065479 -0.171034
+v 0.182395 0.182395 -0.171034
+v -0.112374 0.070035 -0.139406
+v -0.112374 -0.500000 -0.139406
+v 0.112189 -0.500000 -0.139406
+v 0.112189 0.070035 -0.139406
+v 0.122883 -0.500000 -0.137278
+v 0.122883 0.070035 -0.137278
+v 0.131950 -0.500000 -0.131220
+v 0.131950 0.070035 -0.131220
+v 0.138008 -0.500000 -0.122154
+v 0.138008 0.070035 -0.122154
+v 0.140135 -0.500000 -0.111459
+v 0.140135 0.070035 -0.111459
+v 0.138008 -0.500000 -0.100765
+v 0.138008 0.070035 -0.100765
+v 0.131950 -0.500000 -0.091698
+v 0.131950 0.070035 -0.091698
+v 0.122883 -0.500000 -0.085640
+v 0.122883 0.070035 -0.085640
+v 0.112189 -0.500000 -0.083513
+v 0.112189 0.070035 -0.083513
+v 0.101494 -0.500000 -0.085640
+v 0.101494 0.070035 -0.085640
+v 0.092428 -0.500000 -0.091698
+v 0.092428 0.070035 -0.091698
+v 0.086370 -0.500000 -0.100765
+v 0.086370 0.070035 -0.100765
+v 0.084242 -0.500000 -0.111459
+v 0.084242 0.070035 -0.111459
+v 0.086370 -0.500000 -0.122154
+v 0.086370 0.070035 -0.122154
+v 0.092428 -0.500000 -0.131220
+v 0.092428 0.070035 -0.131220
+v 0.101494 -0.500000 -0.137278
+v 0.101494 0.070035 -0.137278
+v -0.101679 -0.500000 -0.137278
+v -0.101679 0.070035 -0.137278
+v -0.092613 -0.500000 -0.131220
+v -0.092613 0.070035 -0.131220
+v -0.086555 -0.500000 -0.122154
+v -0.086555 0.070035 -0.122154
+v -0.084428 -0.500000 -0.111459
+v -0.084428 0.070035 -0.111459
+v -0.086555 -0.500000 -0.100765
+v -0.086555 0.070035 -0.100765
+v -0.092613 -0.500000 -0.091698
+v -0.092613 0.070035 -0.091698
+v -0.101679 -0.500000 -0.085640
+v -0.101679 0.070035 -0.085640
+v -0.112374 -0.500000 -0.083513
+v -0.112374 0.070035 -0.083513
+v -0.123069 -0.500000 -0.085640
+v -0.123069 0.070035 -0.085640
+v -0.132135 -0.500000 -0.091698
+v -0.132135 0.070035 -0.091698
+v -0.138193 -0.500000 -0.100765
+v -0.138193 0.070035 -0.100765
+v -0.140320 -0.500000 -0.111459
+v -0.140320 0.070035 -0.111459
+v -0.138193 -0.500000 -0.122154
+v -0.138193 0.070035 -0.122154
+v -0.132135 -0.500000 -0.131220
+v -0.132135 0.070035 -0.131220
+v -0.123069 -0.500000 -0.137278
+v -0.123069 0.070035 -0.137278
+vt 0.876073 0.266665
+vt 0.876073 0.977812
+vt 0.784827 0.977812
+vt 0.784827 0.266665
+vt 0.693582 0.977812
+vt 0.693582 0.266665
+vt 0.602336 0.977812
+vt 0.602336 0.266665
+vt 0.967319 0.266665
+vt 0.967319 0.977812
+vt 0.147929 0.032040
+vt 0.469434 0.032040
+vt 0.469434 0.171057
+vt 0.147929 0.171057
+vt 0.903184 0.032040
+vt 0.903184 0.171057
+vt 0.147929 0.032751
+vt 0.469434 0.032751
+vt 0.469434 0.171768
+vt 0.147929 0.171768
+vt 0.903184 0.032751
+vt 0.903183 0.171768
+vt 0.263807 0.270252
+vt 0.585312 0.270252
+vt 0.585312 0.704001
+vt 0.263807 0.704001
+vt 0.584297 0.703059
+vt 0.262792 0.703059
+vt 0.262793 0.269309
+vt 0.584297 0.269309
+vt 0.108472 0.980897
+vt 0.108473 0.303114
+vt 0.121438 0.303114
+vt 0.121438 0.980897
+vt 0.081877 0.980125
+vt 0.081879 0.302342
+vt 0.094844 0.302342
+vt 0.094843 0.980125
+vt 0.095507 0.980897
+vt 0.095508 0.303114
+vt 0.107809 0.302342
+vt 0.107808 0.980125
+vt 0.082541 0.980897
+vt 0.082543 0.303114
+vt 0.120774 0.302342
+vt 0.120774 0.980125
+vt 0.069575 0.980897
+vt 0.069577 0.303114
+vt 0.133739 0.302342
+vt 0.133740 0.980125
+vt 0.056609 0.980897
+vt 0.056612 0.303114
+vt 0.146705 0.302342
+vt 0.146706 0.980125
+vt 0.043643 0.980897
+vt 0.043647 0.303114
+vt 0.159670 0.302342
+vt 0.159672 0.980125
+vt 0.030677 0.980897
+vt 0.030682 0.303113
+vt 0.172635 0.302342
+vt 0.172638 0.980125
+vt 0.017711 0.980897
+vt 0.017717 0.303113
+vt 0.185600 0.302342
+vt 0.185604 0.980125
+vt 0.212200 0.980896
+vt 0.212195 0.303113
+vt 0.225160 0.303113
+vt 0.225166 0.980896
+vt 0.198565 0.302342
+vt 0.198570 0.980125
+vt 0.199234 0.980897
+vt 0.199230 0.303114
+vt 0.211531 0.302342
+vt 0.211536 0.980125
+vt 0.186268 0.980897
+vt 0.186264 0.303114
+vt 0.224496 0.302342
+vt 0.224502 0.980125
+vt 0.173302 0.980897
+vt 0.173299 0.303114
+vt 0.017047 0.980125
+vt 0.017052 0.302342
+vt 0.030018 0.302342
+vt 0.030013 0.980125
+vt 0.134403 0.303114
+vt 0.134404 0.980897
+vt 0.160336 0.980897
+vt 0.160334 0.303114
+vt 0.042983 0.302342
+vt 0.042979 0.980125
+vt 0.147369 0.303114
+vt 0.147370 0.980897
+vt 0.055948 0.302342
+vt 0.055945 0.980125
+vt 0.068911 0.980125
+vt 0.068913 0.302342
+vn 1.000000 0.000000 0.000000
+vn -0.000000 -0.000000 1.000000
+vn -1.000000 -0.000000 -0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.831500 0.000000 -0.555600
+vn 0.195100 0.000000 -0.980800
+vn -0.980800 0.000000 -0.195100
+vn 0.555600 0.000000 -0.831500
+vn -0.980800 0.000000 0.195100
+vn 0.831500 0.000000 -0.555600
+vn -0.831500 0.000000 0.555600
+vn 0.980800 0.000000 -0.195100
+vn -0.555600 0.000000 0.831500
+vn 0.980800 0.000000 0.195100
+vn -0.195100 0.000000 0.980800
+vn 0.831500 0.000000 0.555600
+vn 0.195100 0.000000 0.980800
+vn 0.555600 0.000000 0.831500
+vn -0.555600 0.000000 -0.831500
+vn -0.195100 0.000000 -0.980800
+usemtl Material
+s off
+f 1/1/1 5/2/1 6/3/1 2/4/1
+f 2/4/2 6/3/2 7/5/2 3/6/2
+f 3/6/3 7/5/3 8/7/3 4/8/3
+f 5/2/4 1/1/4 4/9/4 8/10/4
+f 10/11/3 12/12/3 11/13/3 9/14/3
+f 12/12/4 16/15/4 15/16/4 11/13/4
+f 16/17/1 14/18/1 13/19/1 15/20/1
+f 14/18/2 10/21/2 9/22/2 13/19/2
+f 9/23/5 11/24/5 15/25/5 13/26/5
+f 14/27/6 16/28/6 12/29/6 10/30/6
+f 75/31/7 76/32/7 78/33/7 77/34/7
+f 19/35/8 20/36/8 22/37/8 21/38/8
+f 73/39/9 74/40/9 76/32/9 75/31/9
+f 21/38/10 22/37/10 24/41/10 23/42/10
+f 71/43/11 72/44/11 74/40/11 73/39/11
+f 23/42/12 24/41/12 26/45/12 25/46/12
+f 69/47/13 70/48/13 72/44/13 71/43/13
+f 25/46/14 26/45/14 28/49/14 27/50/14
+f 67/51/15 68/52/15 70/48/15 69/47/15
+f 27/50/16 28/49/16 30/53/16 29/54/16
+f 65/55/17 66/56/17 68/52/17 67/51/17
+f 29/54/18 30/53/18 32/57/18 31/58/18
+f 63/59/19 64/60/19 66/56/19 65/55/19
+f 31/58/20 32/57/20 34/61/20 33/62/20
+f 61/63/20 62/64/20 64/60/20 63/59/20
+f 33/62/19 34/61/19 36/65/19 35/66/19
+f 59/67/18 60/68/18 62/69/18 61/70/18
+f 35/66/17 36/65/17 38/71/17 37/72/17
+f 57/73/16 58/74/16 60/68/16 59/67/16
+f 37/72/15 38/71/15 40/75/15 39/76/15
+f 55/77/14 56/78/14 58/74/14 57/73/14
+f 39/76/13 40/75/13 42/79/13 41/80/13
+f 53/81/12 54/82/12 56/78/12 55/77/12
+f 41/83/11 42/84/11 44/85/11 43/86/11
+f 77/34/21 78/33/21 80/87/21 79/88/21
+f 51/89/10 52/90/10 54/82/10 53/81/10
+f 43/86/9 44/85/9 46/91/9 45/92/9
+f 79/88/22 80/87/22 17/93/22 18/94/22
+f 18/94/8 17/93/8 52/90/8 51/89/8
+f 45/92/7 46/91/7 48/95/7 47/96/7
+f 49/97/22 50/98/22 20/36/22 19/35/22
+f 47/96/21 48/95/21 50/98/21 49/97/21
diff --git a/advtrains_interlocking/route_prog.lua b/advtrains_interlocking/route_prog.lua
new file mode 100644
index 0000000..6abe431
--- /dev/null
+++ b/advtrains_interlocking/route_prog.lua
@@ -0,0 +1,549 @@
+-- Route programming system
+
+--[[
+Progamming routes:
+1. Select "program new route" in the signalling dialog
+-> route_start marker will appear to designate route-program mode
+2. Do those actions in any order:
+A. punch a TCB marker node to proceed route along this TCB. This will only work if
+ this is actually a TCB bordering the current TS, and will place a
+ route_set marker and shift to the next TS
+B. right-click a turnout to switch it (no impact to route programming
+C. punch a turnout (or some other passive component) to fix its state (toggle)
+ for the route. A sprite telling "Route Fix" will show that fact.
+3. To complete route setting, use the chat command '/at_program_route <route name>'.
+ The last punched TCB will get a 'route end' marker
+ The end of a route should be at another signal facing the same direction as the entrance signal,
+ however this is not enforced and left up to the signal engineer (the programmer)
+
+The route visualization will also be used to visualize routes after they have been programmed.
+]]--
+
+
+-- table with objectRefs
+local markerent = {}
+
+minetest.register_entity("advtrains_interlocking:routemarker", {
+ visual = "mesh",
+ mesh = "trackplane.b3d",
+ textures = {"at_il_route_set.png"},
+ collisionbox = {-1,-0.5,-1, 1,-0.4,1},
+ visual_size = {x=10, y=10},
+ on_punch = function(self)
+ self.object:remove()
+ end,
+ get_staticdata = function() return "STATIC" end,
+ on_activate = function(self, sdata) if sdata=="STATIC" then self.object:remove() end end,
+ static_save = false,
+})
+
+
+-- Spawn or update a route marker entity
+-- pos: position where this is going to be
+-- key: something unique to determine which entity to remove if this was set before
+-- img: texture
+local function routemarker(context, pos, key, img, yaw, itex)
+ if not markerent[context] then
+ markerent[context] = {}
+ end
+ if markerent[context][key] then
+ markerent[context][key]:remove()
+ end
+
+ local obj = minetest.add_entity(vector.add(pos, {x=0, y=0.3, z=0}), "advtrains_interlocking:routemarker")
+ if not obj then return end
+ obj:set_yaw(yaw)
+ obj:set_properties({
+ infotext = itex,
+ textures = {img},
+ })
+
+ markerent[context][key] = obj
+end
+
+minetest.register_entity("advtrains_interlocking:routesprite", {
+ visual = "sprite",
+ textures = {"at_il_turnout_free.png"},
+ collisionbox = {-0.2,-0.2,-0.2, 0.2,0.2,0.2},
+ visual_size = {x=1, y=1},
+ on_punch = function(self)
+ if self.callback then
+ self.callback()
+ end
+ self.object:remove()
+ end,
+ get_staticdata = function() return "STATIC" end,
+ on_activate = function(self, sdata) if sdata=="STATIC" then self.object:remove() end end,
+ static_save = false,
+})
+
+
+-- Spawn or update a route sprite entity
+-- pos: position where this is going to be
+-- key: something unique to determine which entity to remove if this was set before
+-- img: texture
+local function routesprite(context, pos, key, img, itex, callback)
+ if not markerent[context] then
+ markerent[context] = {}
+ end
+ if markerent[context][key] then
+ markerent[context][key]:remove()
+ end
+
+ local obj = minetest.add_entity(vector.add(pos, {x=0, y=0, z=0}), "advtrains_interlocking:routesprite")
+ if not obj then return end
+ obj:set_properties({
+ infotext = itex,
+ textures = {img},
+ })
+
+ if callback then
+ obj:get_luaentity().callback = callback
+ end
+
+ markerent[context][key] = obj
+end
+
+--[[
+Route definition:
+route = {
+ name = <string>
+ [n] = {
+ next = <sigd>, -- of the next (note: next) TCB on the route
+ locks = {<pts> = "state"} -- route locks of this route segment
+ }
+ terminal = <sigd>,
+ aspect = <signal aspect>,--note, might change in future
+}
+The first item in the TCB path (namely i=0) is always the start signal of this route,
+so this is left out.
+All subsequent entries, starting from 1, contain:
+- all route locks of the segment on TS between the (i-1). and the i. TCB
+- the next TCB signal describer in proceeding direction of the route.
+'Terminal' once again repeats the "next" entry of the last route segment.
+It is needed for distant signal aspect determination. If it is not set,
+the distant signal aspect is determined as DANGER.
+]]--
+
+local function chat(pname, message)
+ minetest.chat_send_player(pname, "[Route programming] "..message)
+end
+local function clear_lock(locks, pname, pts)
+ locks[pts] = nil
+ chat(pname, pts.." is no longer affected when this route is set.")
+end
+
+local function otherside(s)
+ if s==1 then return 2 else return 1 end
+end
+
+function advtrains.interlocking.clear_visu_context(context)
+ if not markerent[context] then return end
+ for key, obj in pairs(markerent[context]) do
+ obj:remove()
+ end
+ markerent[context] = nil
+end
+
+-- visualize route. 'context' is a string that identifies the context of this visualization
+-- e.g. prog_<player> or vis_<pts> for later visualizations
+-- last 2 parameters are only to be used in the context of route programming!
+function advtrains.interlocking.visualize_route(origin, route, context, tmp_lcks, pname)
+ advtrains.interlocking.clear_visu_context(context)
+
+ local oyaw = 0
+ local onode_ok, oconns, orhe = advtrains.get_rail_info_at(origin.p, advtrains.all_tracktypes)
+ if onode_ok then
+ oyaw = advtrains.dir_to_angle(oconns[origin.s].c)
+ end
+ routemarker(context, origin.p, "rte_origin", "at_il_route_start.png", oyaw, route.name)
+
+ local c_sigd = origin
+ for k,v in ipairs(route) do
+ c_sigd = v.next
+ -- display route path
+ -- Final "next" marker can be EOI, thus undefined. This is legitimate.
+ if c_sigd then
+ local yaw = 0
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(c_sigd.p, advtrains.all_tracktypes)
+ if node_ok then
+ yaw = advtrains.dir_to_angle(conns[c_sigd.s].c)
+ end
+ local img = "at_il_route_set.png"
+ if k==#route and not tmp_lcks then
+ img = "at_il_route_end.png"
+ end
+ routemarker(context, c_sigd.p, "rte"..k, img, yaw, route.name.." #"..k)
+ end
+ -- display locks
+ for pts, state in pairs(v.locks) do
+ local pos = minetest.string_to_pos(pts)
+ routesprite(context, pos, "fix"..k..pts, "at_il_route_lock.png", "Fixed in state '"..state.."' by route "..route.name.." until segment #"..k.." is freed.")
+ end
+ end
+
+ -- The presence of tmp_lcks tells us that we are displaying during route programming.
+ if tmp_lcks then
+ -- display route end markers at appropriate places (check next TS, if it exists)
+ local terminal = c_sigd
+ if terminal then
+ local term_tcbs = advtrains.interlocking.db.get_tcbs(terminal)
+ if term_tcbs.ts_id then
+ local over_ts = advtrains.interlocking.db.get_ts(term_tcbs.ts_id)
+ for i, sigd in ipairs(over_ts.tc_breaks) do
+ if not vector.equals(sigd.p, terminal.p) then
+ local yaw = 0
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(sigd.p, advtrains.all_tracktypes)
+ if node_ok then
+ yaw = advtrains.dir_to_angle(conns[otherside(sigd.s)].c)
+ end
+ routemarker(context, sigd.p, "rteterm"..i, "at_il_route_end.png", yaw, route.name.." Terminal "..i)
+ end
+ end
+ end
+ end
+ -- display locks set by player
+ for pts, state in pairs(tmp_lcks) do
+ local pos = minetest.string_to_pos(pts)
+ routesprite(context, pos, "fixp"..pts, "at_il_route_lock_edit.png", "Fixed in state '"..state.."' by route "..route.name.." (punch to unfix)",
+ function() clear_lock(tmp_lcks, pname, pts) end)
+ end
+ end
+end
+
+
+local player_rte_prog = {}
+
+function advtrains.interlocking.init_route_prog(pname, sigd)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ player_rte_prog[pname] = {
+ origin = sigd,
+ route = {
+ name = "PROG["..pname.."]",
+ },
+ tmp_lcks = {},
+ }
+ advtrains.interlocking.visualize_route(sigd, player_rte_prog[pname].route, "prog_"..pname, player_rte_prog[pname].tmp_lcks, pname)
+ minetest.chat_send_player(pname, "Route programming mode active. Punch TCBs to add route segments, punch turnouts to lock them.")
+end
+
+local function get_last_route_item(origin, route)
+ if #route == 0 then
+ return origin
+ end
+ return route[#route].next
+end
+
+local function do_advance_route(pname, rp, sigd, tsname)
+ table.insert(rp.route, {next = sigd, locks = rp.tmp_lcks})
+ rp.tmp_lcks = {}
+ chat(pname, "Added track section '"..tsname.."' to the route.")
+end
+
+local function finishrpform(pname)
+ local rp = player_rte_prog[pname]
+ if not rp then return end
+
+ local form = "size[7,6]label[0.5,0.5;Finish programming route]"
+ local terminal = get_last_route_item(rp.origin, rp.route)
+ if terminal then
+ local term_tcbs = advtrains.interlocking.db.get_tcbs(terminal)
+
+ if term_tcbs.signal then
+ form = form .. "label[0.5,1.5;Route ends at signal:]"
+ form = form .. "label[0.5,2 ;"..term_tcbs.signal_name.."]"
+ else
+ form = form .. "label[0.5,1.5;WARNING: Route does not end at a signal.]"
+ form = form .. "label[0.5,2 ;Routes should in most cases end at signals.]"
+ form = form .. "label[0.5,2.5;Cancel if you are unsure!]"
+ end
+ else
+ form = form .. "label[0.5,1.5;Route leads into]"
+ form = form .. "label[0.5,2 ;non-interlocked area]"
+ end
+ form = form.."field[0.8,3.5;5.2,1;name;Enter Route Name;]"
+ form = form.."button_exit[0.5,4.5; 5,1;save;Save Route]"
+
+
+ minetest.show_formspec(pname, "at_il_routepf", form)
+end
+
+
+local function check_advance_valid(tcbpos, rp)
+ -- track circuit break, try to advance route over it
+ local lri = get_last_route_item(rp.origin, rp.route)
+ if not lri then
+ return false, false
+ end
+
+ local is_endpoint = false
+
+ local this_sigd, this_ts, adv_side
+
+ if vector.equals(lri.p, tcbpos) then
+ -- If the player just punched the last TCB again, it's of course possible to
+ -- finish the route here (although it can't be advanced by here.
+ -- Fun fact: you can now program routes that end exactly where they begin :)
+ is_endpoint = true
+ this_sigd = lri
+ else
+ -- else, we need to check whether this TS actually borders
+ local start_tcbs = advtrains.interlocking.db.get_tcbs(lri)
+ if not start_tcbs.ts_id then
+ return false, false
+ end
+
+ this_ts = advtrains.interlocking.db.get_ts(start_tcbs.ts_id)
+ for _,sigd in ipairs(this_ts.tc_breaks) do
+ if vector.equals(sigd.p, tcbpos) then
+ adv_side = otherside(sigd.s)
+ end
+ end
+ if not adv_side then
+ -- this TCB is not bordering to the section
+ return false, false
+ end
+ this_sigd = {p=tcbpos, s=adv_side}
+ end
+
+ -- check whether the ts at the other end is capable of "end over"
+ local adv_tcbs = advtrains.interlocking.db.get_tcbs(this_sigd)
+ local next_tsid = adv_tcbs.ts_id
+ local can_over, over_ts, next_tc_bs = false, nil, nil
+ local cannotover_rsn = "Next section is diverging (>2 TCBs)"
+ if next_tsid then
+ -- you may not advance over EOI. While this is technically possible,
+ -- in practise this just enters an unnecessary extra empty route item.
+ over_ts = advtrains.interlocking.db.get_ts(adv_tcbs.ts_id)
+ next_tc_bs = over_ts.tc_breaks
+ can_over = #next_tc_bs <= 2
+ else
+ cannotover_rsn = "End of interlocking"
+ end
+
+ local over_sigd = nil
+ if can_over then
+ if next_tc_bs and #next_tc_bs == 2 then
+ local sdt
+ if vector.equals(next_tc_bs[1].p, tcbpos) then
+ sdt = next_tc_bs[2]
+ end
+ if vector.equals(next_tc_bs[2].p, tcbpos) then
+ sdt = next_tc_bs[1]
+ end
+ if not sdt then
+ error("Inconsistency: "..dump(next_ts))
+ end
+ -- swap TCB direction
+ over_sigd = {p = sdt.p, s = otherside(sdt.s) }
+ end
+ end
+
+ return is_endpoint, true, this_sigd, this_ts, can_over, over_ts, over_sigd, cannotover_rsn
+end
+
+local function show_routing_form(pname, tcbpos, message)
+
+ local rp = player_rte_prog[pname]
+
+ if not rp then return end
+
+ local is_endpoint, advance_valid, this_sigd, this_ts, can_over, over_ts, over_sigd, cannotover_rsn = check_advance_valid(tcbpos, rp)
+
+ -- at this place, advance_valid shows whether the current route can be advanced
+ -- over this TCB.
+ -- If it can:
+ -- Advance over (continue programming)
+ -- End here
+ -- Advance and end (only <=2 TCBs, terminal signal needs to be known)
+ -- if not:
+ -- show nothing at all
+ -- In all cases, Discard and Backtrack buttons needed.
+
+ local form = "size[7,9.5]label[0.5,0.5;Advance/Complete Route]"
+ if message then
+ form = form .. "label[0.5,1;"..message.."]"
+ end
+
+ if advance_valid and not is_endpoint then
+ form = form.. "label[0.5,1.8;Advance to next route section]"
+ form = form.."image_button[0.5,2.2; 5,1;at_il_routep_advance.png;advance;]"
+
+ form = form.. "label[0.5,3.5;-------------------------]"
+ else
+ form = form.. "label[0.5,2.3;This TCB is not suitable as]"
+ form = form.. "label[0.5,2.8;route continuation.]"
+ end
+ if advance_valid or is_endpoint then
+ form = form.. "label[0.5,3.8;Finish route HERE]"
+ form = form.."image_button[0.5, 4.2; 5,1;at_il_routep_end_here.png;endhere;]"
+ if can_over then
+ form = form.. "label[0.5,5.3;Finish route at end of NEXT section]"
+ form = form.."image_button[0.5,5.7; 5,1;at_il_routep_end_over.png;endover;]"
+ else
+ form = form.. "label[0.5,5.3;Advancing over next section is]"
+ form = form.. "label[0.5,5.8;impossible at this place.]"
+ if cannotover_rsn then
+ form = form.. "label[0.5,6.3;"..cannotover_rsn.."]"
+ end
+ end
+ end
+
+ form = form.. "label[0.5,7;-------------------------]"
+ if #rp.route > 0 then
+ form = form.."button[0.5,7.4; 5,1;retract;Step back one section]"
+ end
+ form = form.."button[0.5,8.4; 5,1;cancel;Cancel route programming]"
+
+ minetest.show_formspec(pname, "at_il_rprog_"..minetest.pos_to_string(tcbpos), form)
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+
+ local tcbpts = string.match(formname, "^at_il_rprog_([^_]+)$")
+ local tcbpos
+ if tcbpts then
+ tcbpos = minetest.string_to_pos(tcbpts)
+ end
+ if tcbpos then
+ -- RPROG form
+ local rp = player_rte_prog[pname]
+ if not rp then
+ minetest.close_formspec(pname, formname)
+ return
+ end
+
+ local is_endpoint, advance_valid, this_sigd, this_ts, can_over, over_ts, over_sigd = check_advance_valid(tcbpos, rp)
+
+ if advance_valid then
+ if fields.advance then
+ -- advance route
+ if not is_endpoint then
+ do_advance_route(pname, rp, this_sigd, this_ts.name)
+ end
+ end
+ if fields.endhere then
+ if not is_endpoint then
+ do_advance_route(pname, rp, this_sigd, this_ts.name)
+ end
+ finishrpform(pname)
+ end
+ if can_over and fields.endover then
+ if not is_endpoint then
+ do_advance_route(pname, rp, this_sigd, this_ts.name)
+ end
+ do_advance_route(pname, rp, over_sigd, over_ts and over_ts.name or "--EOI--")
+ finishrpform(pname)
+ end
+ end
+ if fields.retract then
+ if #rp.route <= 0 then
+ minetest.close_formspec(pname, formname)
+ return
+ end
+ rp.tmp_locks = rp.route[#rp.route].locks
+ rp.route[#rp.route] = nil
+ chat(pname, "Route section "..(#rp.route+1).." removed.")
+ end
+ if fields.cancel then
+ player_rte_prog[pname] = nil
+ advtrains.interlocking.clear_visu_context("prog_"..pname)
+ chat(pname, "Route discarded.")
+ minetest.close_formspec(pname, formname)
+ return
+ end
+
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname)
+ minetest.close_formspec(pname, formname)
+ return
+ end
+
+ if formname == "at_il_routepf" then
+ if not fields.save or not fields.name then return end
+ if fields.name == "" then
+ -- show form again
+ finishrpform(pname)
+ return
+ end
+
+ local rp = player_rte_prog[pname]
+ if rp then
+ if #rp.route <= 0 then
+ chat(pname, "Cannot program route without a target")
+ return
+ end
+
+ local tcbs = advtrains.interlocking.db.get_tcbs(rp.origin)
+ if not tcbs then
+ chat(pname, "The origin TCB has become unknown during programming. Try again.")
+ return
+ end
+
+ local terminal = get_last_route_item(rp.origin, rp.route)
+ rp.route.terminal = terminal
+ rp.route.name = fields.name
+
+ table.insert(tcbs.routes, rp.route)
+
+ advtrains.interlocking.clear_visu_context("prog_"..pname)
+ player_rte_prog[pname] = nil
+ chat(pname, "Successfully programmed route.")
+
+ advtrains.interlocking.show_route_edit_form(pname, rp.origin, #tcbs.routes)
+ return
+ end
+ end
+end)
+
+
+-- Central route programming punch callback
+minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ local rp = player_rte_prog[pname]
+ if rp then
+ -- determine what the punched node is
+ if minetest.get_item_group(node.name, "at_il_track_circuit_break") >= 1 then
+ -- get position of the assigned tcb
+ local meta = minetest.get_meta(pos)
+ local tcbpts = meta:get_string("tcb_pos")
+ if tcbpts == "" then
+ chat(pname, "This TCB is unconfigured, you first need to assign it to a rail")
+ return
+ end
+ local tcbpos = minetest.string_to_pos(tcbpts)
+
+ -- show formspec
+
+ show_routing_form(pname, tcbpos)
+
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname)
+
+ return
+ end
+ if advtrains.is_passive(pos) then
+ local pts = advtrains.roundfloorpts(pos)
+ if rp.tmp_lcks[pts] then
+ clear_lock(rp.tmp_lcks, pname, pts)
+ else
+ local state = advtrains.getstate(pos)
+ rp.tmp_lcks[pts] = state
+ chat(pname, pts.." is held in "..state.." position when this route is set and freed ")
+ end
+ advtrains.interlocking.visualize_route(rp.origin, rp.route, "prog_"..pname, rp.tmp_lcks, pname)
+ return
+ end
+
+ end
+end)
+
+
+--TODO on route setting
+-- routes should end at signals. complete route setting by punching a signal, and command as exceptional route completion
+-- Create simpler way to advance a route to the next tcb/signal on simple sections without turnouts
diff --git a/advtrains_interlocking/route_ui.lua b/advtrains_interlocking/route_ui.lua
new file mode 100644
index 0000000..1999941
--- /dev/null
+++ b/advtrains_interlocking/route_ui.lua
@@ -0,0 +1,153 @@
+-- route_ui.lua
+-- User interface for showing and editing routes
+
+local atil = advtrains.interlocking
+local ildb = atil.db
+
+-- TODO duplicate
+local lntrans = { "A", "B" }
+local function sigd_to_string(sigd)
+ return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
+end
+
+
+
+function atil.show_route_edit_form(pname, sigd, routeid)
+
+ if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+
+ local tcbs = atil.db.get_tcbs(sigd)
+ if not tcbs then return end
+ local route = tcbs.routes[routeid]
+ if not route then return end
+
+ local form = "size[9,10]label[0.5,0.2;Route overview]"
+ form = form.."field[0.8,1.2;6.5,1;name;Route name;"..minetest.formspec_escape(route.name).."]"
+ form = form.."button[7.0,0.9;1.5,1;setname;Set]"
+
+ -- construct textlist for route information
+ local tab = {}
+ local function itab(t)
+ tab[#tab+1] = minetest.formspec_escape(string.gsub(t, ",", " "))
+ end
+ itab("TCB "..sigd_to_string(sigd).." ("..tcbs.signal_name..") Route #"..routeid)
+
+ -- this code is partially copy-pasted from routesetting.lua
+ -- we start at the tc designated by signal
+ local c_sigd = sigd
+ local i = 1
+ local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp
+ while c_sigd and i<=#route do
+ c_tcbs = ildb.get_tcbs(c_sigd)
+ if not c_tcbs then
+ itab("-!- No TCBS at "..sigd_to_string(c_sigd)..". Please reconfigure route!")
+ break
+ end
+ c_ts_id = c_tcbs.ts_id
+ if not c_ts_id then
+ itab("-!- No track section adjacent to "..sigd_to_string(c_sigd)..". Please reconfigure route!")
+ break
+ end
+ c_ts = ildb.get_ts(c_ts_id)
+
+ c_rseg = route[i]
+ c_lckp = {}
+
+ itab(""..i.." Entry "..sigd_to_string(c_sigd).." -> Sec. "..(c_ts and c_ts.name or "-").." -> Exit "..(c_rseg.next and sigd_to_string(c_rseg.next) or "END"))
+
+ if c_rseg.locks then
+ for pts, state in pairs(c_rseg.locks) do
+
+ local pos = minetest.string_to_pos(pts)
+ itab(" Lock: "..pts.." -> "..state)
+ if not advtrains.is_passive(pos) then
+ itab("-!- No passive component at "..pts..". Please reconfigure route!")
+ break
+ end
+ end
+ end
+ -- advance
+ c_sigd = c_rseg.next
+ i = i + 1
+ end
+ if c_sigd then
+ local e_tcbs = ildb.get_tcbs(c_sigd)
+ itab("Route end: "..sigd_to_string(c_sigd).." ("..(e_tcbs and e_tcbs.signal_name or "-")..")")
+ else
+ itab("Route ends on dead-end")
+ end
+
+ form = form.."textlist[0.5,2;7.75,3.9;rtelog;"..table.concat(tab, ",").."]"
+
+ form = form.."button[0.5,6;3,1;back;<<< Back to signal]"
+ form = form.."button[4.5,6;2,1;aspect;Signal Aspect]"
+ form = form.."button[6.5,6;2,1;delete;Delete Route]"
+
+ --atdebug(route.ars)
+ form = form.."style[ars;font=mono]"
+ form = form.."textarea[0.8,7.3;5,3;ars;ARS Rule List;"..atil.ars_to_text(route.ars).."]"
+ form = form.."button[5.5,7.23;3,1;savears;Save ARS List]"
+
+ minetest.show_formspec(pname, "at_il_routeedit_"..minetest.pos_to_string(sigd.p).."_"..sigd.s.."_"..routeid, form)
+
+end
+
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then
+ return
+ end
+
+ local pts, connids, routeids = string.match(formname, "^at_il_routeedit_([^_]+)_(%d)_(%d+)$")
+ local pos, connid, routeid
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ connid = tonumber(connids)
+ routeid = tonumber(routeids)
+ if not connid or connid<1 or connid>2 then return end
+ if not routeid then return end
+ end
+ if pos and connid and routeid and not fields.quit then
+ local sigd = {p=pos, s=connid}
+ local tcbs = ildb.get_tcbs(sigd)
+ if not tcbs then return end
+ local route = tcbs.routes[routeid]
+ if not route then return end
+
+ if fields.setname and fields.name then
+ route.name = fields.name
+ end
+
+ if fields.aspect then
+ local suppasp = advtrains.interlocking.signal_get_supported_aspects(tcbs.signal)
+
+ local callback = function(pname, asp)
+ route.aspect = asp
+ advtrains.interlocking.show_route_edit_form(pname, sigd, routeid)
+ end
+
+ advtrains.interlocking.show_signal_aspect_selector(pname, suppasp, route.name, callback, route.aspect or advtrains.interlocking.GENERIC_FREE)
+ return
+ end
+ if fields.delete then
+ -- if something set the route in the meantime, make sure this doesn't break.
+ atil.route.update_route(sigd, tcbs, nil, true)
+ table.remove(tcbs.routes, routeid)
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ end
+
+ if fields.ars and fields.savears then
+ route.ars = atil.text_to_ars(fields.ars)
+ --atdebug(route.ars)
+ end
+
+ if fields.back then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ end
+
+ end
+end)
diff --git a/advtrains_interlocking/routesetting.lua b/advtrains_interlocking/routesetting.lua
new file mode 100644
index 0000000..67efaea
--- /dev/null
+++ b/advtrains_interlocking/routesetting.lua
@@ -0,0 +1,342 @@
+-- Setting and clearing routes
+
+-- TODO duplicate
+local lntrans = { "A", "B" }
+local function sigd_to_string(sigd)
+ return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
+end
+
+local ildb = advtrains.interlocking.db
+local ilrs = {}
+
+local sigd_equal = advtrains.interlocking.sigd_equal
+
+-- table containing locked points
+-- also manual locks (maintenance a.s.o.) are recorded here
+-- [pts] = {
+-- [n] = { [by = <ts_id>], rsn = <human-readable text>, [origin = <sigd>] }
+-- }
+ilrs.rte_locks = {}
+ilrs.rte_callbacks = {
+ ts = {},
+ lck = {}
+}
+
+
+-- main route setting. First checks if everything can be set as designated,
+-- then (if "try" is not set) actually sets it
+-- returns:
+-- true - route can be/was successfully set
+-- false, message, cbts, cblk - something went wrong, what is contained in the message.
+-- cbts: the ts id of the conflicting ts, cblk: the pts of the conflicting component
+function ilrs.set_route(signal, route, try)
+ if not try then
+ local tsuc, trsn, cbts, cblk = ilrs.set_route(signal, route, true)
+ if not tsuc then
+ return false, trsn, cbts, cblk
+ end
+ end
+
+
+ -- we start at the tc designated by signal
+ local c_sigd = signal
+ local first = true
+ local i = 1
+ local rtename = route.name
+ local signalname = ildb.get_tcbs(signal).signal_name
+ local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp
+ while c_sigd and i<=#route do
+ c_tcbs = ildb.get_tcbs(c_sigd)
+ if not c_tcbs then
+ if not try then atwarn("Did not find TCBS",c_sigd,"while setting route",rtename,"of",signal) end
+ return false, "No TCB found at "..sigd_to_string(c_sigd)..". Please reconfigure route!"
+ end
+ c_ts_id = c_tcbs.ts_id
+ if not c_ts_id then
+ if not try then atwarn("Encountered End-Of-Interlocking while setting route",rtename,"of",signal) end
+ return false, "No track section adjacent to "..sigd_to_string(c_sigd)..". Please reconfigure route!"
+ end
+ c_ts = ildb.get_ts(c_ts_id)
+ c_rseg = route[i]
+ c_lckp = {}
+
+ if c_ts.route then
+ if not try then atwarn("Encountered ts lock during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end
+ return false, "Section '"..c_ts.name.."' already has route set from "..sigd_to_string(c_ts.route.origin)..":\n"..c_ts.route.rsn, c_ts_id, nil
+ end
+ if c_ts.trains and #c_ts.trains>0 then
+ if not try then atwarn("Encountered ts occupied during a real run of routesetting routine, at ts=",c_ts_id,"while setting route",rtename,"of",signal) end
+ return false, "Section '"..c_ts.name.."' is occupied!", c_ts_id, nil
+ end
+
+ for pts, state in pairs(c_rseg.locks) do
+ local confl = ilrs.has_route_lock(pts, state)
+
+ local pos = minetest.string_to_pos(pts)
+ if advtrains.is_passive(pos) then
+ local cstate = advtrains.getstate(pos)
+ if cstate ~= state then
+ local confl = ilrs.has_route_lock(pts)
+ if confl then
+ if not try then atwarn("Encountered route lock while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end
+ return false, "Lock conflict at "..pts..", Held locked by:\n"..confl, nil, pts
+ elseif not try then
+ advtrains.setstate(pos, state)
+ end
+ end
+ if not try then
+ ilrs.add_route_lock(pts, c_ts_id, "Route '"..rtename.."' from signal '"..signalname.."'", signal)
+ c_lckp[#c_lckp+1] = pts
+ end
+ else
+ if not try then atwarn("Encountered route lock misconfiguration (no passive component) while a real run of routesetting routine, at position",pts,"while setting route",rtename,"of",signal) end
+ return false, "No passive component at "..pts..". Please reconfigure route!"
+ end
+ end
+ -- reserve ts and write locks
+ if not try then
+ local nvar = c_rseg.next
+ if not route[i+1] then
+ -- We shouldn't use the "next" value of the final route segment, because this can lead to accidental route-cancelling of already set routes from another signal.
+ nvar = nil
+ end
+ c_ts.route = {
+ origin = signal,
+ entry = c_sigd,
+ rsn = "Route '"..rtename.."' from signal '"..signalname.."', segment #"..i,
+ first = first,
+ }
+ c_ts.route_post = {
+ locks = c_lckp,
+ next = nvar,
+ }
+ if c_tcbs.signal then
+ c_tcbs.route_committed = true
+ c_tcbs.aspect = route.aspect or advtrains.interlocking.GENERIC_FREE
+ c_tcbs.route_origin = signal
+ advtrains.interlocking.update_signal_aspect(c_tcbs)
+ end
+ end
+ -- advance
+ first = nil
+ c_sigd = c_rseg.next
+ i = i + 1
+ end
+
+ return true
+end
+
+-- Checks whether there is a route lock that prohibits setting the component
+-- to the wanted state. returns string with reasons on conflict
+function ilrs.has_route_lock(pts)
+ -- look this up
+ local e = ilrs.rte_locks[pts]
+ if not e then return nil
+ elseif #e==0 then
+ ilrs.rte_locks[pts] = nil
+ return nil
+ end
+ local txts = {}
+ for _, ent in ipairs(e) do
+ txts[#txts+1] = ent.rsn
+ end
+ return table.concat(txts, "\n")
+end
+
+-- adds route lock for position
+function ilrs.add_route_lock(pts, ts, rsn, origin)
+ ilrs.free_route_locks_indiv(pts, ts, true)
+ local elm = {by=ts, rsn=rsn, origin=origin}
+ if not ilrs.rte_locks[pts] then
+ ilrs.rte_locks[pts] = { elm }
+ else
+ table.insert(ilrs.rte_locks[pts], elm)
+ end
+end
+
+-- adds route lock for position
+function ilrs.add_manual_route_lock(pts, rsn)
+ local elm = {rsn=rsn}
+ if not ilrs.rte_locks[pts] then
+ ilrs.rte_locks[pts] = { elm }
+ else
+ table.insert(ilrs.rte_locks[pts], elm)
+ end
+end
+
+-- frees route locking for all points (components) that were set by this ts
+function ilrs.free_route_locks(ts, lcks, nocallbacks)
+ for _,pts in pairs(lcks) do
+ ilrs.free_route_locks_indiv(pts, ts, nocallbacks)
+ end
+end
+
+function ilrs.free_route_locks_indiv(pts, ts, nocallbacks)
+ local e = ilrs.rte_locks[pts]
+ if not e then return nil
+ elseif #e==0 then
+ ilrs.rte_locks[pts] = nil
+ return nil
+ end
+ local i = 1
+ while i <= #e do
+ if e[i].by == ts then
+ --atdebug("free_route_locks_indiv",pts,"clearing entry",e[i].by,e[i].rsn)
+ table.remove(e,i)
+ else
+ i = i + 1
+ end
+ end
+ -- This must be delayed, because this code is executed in-between a train step
+ -- TODO use luaautomation timers?
+ if not nocallbacks then
+ minetest.after(0, ilrs.update_waiting, "lck", pts)
+ minetest.after(0.5, advtrains.set_fallback_state, minetest.string_to_pos(pts))
+ end
+end
+-- frees all route locks, even manual ones set with the tool, at a specific position
+function ilrs.remove_route_locks(pts, nocallbacks)
+ ilrs.rte_locks[pts] = nil
+ -- This must be delayed, because this code is executed in-between a train step
+ -- TODO use luaautomation timers?
+ if not nocallbacks then
+ minetest.after(0, ilrs.update_waiting, "lck", pts)
+ end
+end
+
+
+-- starting from the designated sigd, clears all subsequent route and route_post
+-- information from the track sections.
+-- note that this does not clear the routesetting status from the entry signal,
+-- only from the ts's
+function ilrs.cancel_route_from(sigd)
+ -- we start at the tc designated by signal
+ local c_sigd = sigd
+ local c_tcbs, c_ts_id, c_ts, c_rseg, c_lckp
+ while c_sigd do
+ --atdebug("cancel_route_from: at sigd",c_sigd)
+ c_tcbs = ildb.get_tcbs(c_sigd)
+ if not c_tcbs then
+ atwarn("Failed to cancel route, no TCBS at",c_sigd)
+ return false
+ end
+
+ --atdebug("cancelling",c_ts.route.rsn)
+ -- clear signal aspect and routesetting state
+ c_tcbs.route_committed = nil
+ c_tcbs.aspect = nil
+ c_tcbs.routeset = nil
+ c_tcbs.route_auto = nil
+ c_tcbs.route_origin = nil
+
+ advtrains.interlocking.update_signal_aspect(c_tcbs)
+
+ c_ts_id = c_tcbs.ts_id
+ if not c_tcbs then
+ atwarn("Failed to cancel route, end of interlocking at",c_sigd)
+ return false
+ end
+ c_ts = ildb.get_ts(c_ts_id)
+
+ if not c_ts
+ or not c_ts.route
+ or not sigd_equal(c_ts.route.entry, c_sigd) then
+ --atdebug("cancel_route_from: abort (eoi/no route):")
+ return false
+ end
+
+ c_ts.route = nil
+
+ if c_ts.route_post then
+ advtrains.interlocking.route.free_route_locks(c_ts_id, c_ts.route_post.locks)
+ c_sigd = c_ts.route_post.next
+ else
+ c_sigd = nil
+ end
+ c_ts.route_post = nil
+ minetest.after(0, advtrains.interlocking.route.update_waiting, "ts", c_ts_id)
+ end
+ --atdebug("cancel_route_from: done (no final sigd)")
+ return true
+end
+
+-- TCBS Routesetting helper: generic update function for
+-- route setting
+-- Call this function to set and cancel routes!
+-- sigd, tcbs: self-explanatory
+-- newrte: If a new route should be set, the route index of it (in tcbs.routes). nil otherwise
+-- cancel: true in combination with newrte=nil causes cancellation of the current route.
+function ilrs.update_route(sigd, tcbs, newrte, cancel)
+ --atdebug("Update_Route for",sigd,tcbs.signal_name)
+ local has_changed_aspect = false
+ if tcbs.route_origin and not sigd_equal(tcbs.route_origin, sigd) then
+ --atdebug("Signal not in control, held by",tcbs.signal_name)
+ return
+ end
+ if (newrte and tcbs.routeset and tcbs.routeset ~= newrte) or cancel then
+ if tcbs.route_committed then
+ --atdebug("Cancelling:",tcbs.routeset)
+ advtrains.interlocking.route.cancel_route_from(sigd)
+ end
+ tcbs.route_committed = nil
+ tcbs.aspect = nil
+ has_changed_aspect = true
+ tcbs.routeset = nil
+ tcbs.route_auto = nil
+ tcbs.route_rsn = nil
+ end
+ if newrte or tcbs.routeset then
+ if tcbs.route_committed then
+ return
+ end
+ if newrte then tcbs.routeset = newrte end
+ --atdebug("Setting:",tcbs.routeset)
+ local succ, rsn, cbts, cblk = ilrs.set_route(sigd, tcbs.routes[tcbs.routeset])
+ if not succ then
+ tcbs.route_rsn = rsn
+ --atdebug("Routesetting failed:",rsn)
+ -- add cbts or cblk to callback table
+ if cbts then
+ --atdebug("cbts =",cbts)
+ if not ilrs.rte_callbacks.ts[cbts] then ilrs.rte_callbacks.ts[cbts]={} end
+ advtrains.insert_once(ilrs.rte_callbacks.ts[cbts], sigd, sigd_equal)
+ end
+ if cblk then
+ --atdebug("cblk =",cblk)
+ if not ilrs.rte_callbacks.lck[cblk] then ilrs.rte_callbacks.lck[cblk]={} end
+ advtrains.insert_once(ilrs.rte_callbacks.lck[cblk], sigd, sigd_equal)
+ end
+ else
+ --atdebug("Committed Route:",tcbs.routeset)
+ has_changed_aspect = true
+ end
+ end
+ if has_changed_aspect then
+ -- FIX: prevent an minetest.after() loop caused by update_signal_aspect dispatching path invalidation, which in turn calls ARS again
+ advtrains.interlocking.update_signal_aspect(tcbs)
+ end
+ advtrains.interlocking.update_player_forms(sigd)
+end
+
+-- Try to re-set routes that conflicted with this point
+-- sys can be one of "ts" and "lck"
+-- key is then ts_id or pts respectively
+function ilrs.update_waiting(sys, key)
+ --atdebug("update_waiting:",sys,".",key)
+ local t = ilrs.rte_callbacks[sys][key]
+ ilrs.rte_callbacks[sys][key] = nil
+ if t then
+ for _,sigd in ipairs(t) do
+ --atdebug("Updating", sigd)
+ -- While these are run, the table we cleared before may be populated again, which is in our interest.
+ -- (that's the reason we needed to copy it)
+ local tcbs = ildb.get_tcbs(sigd)
+ if tcbs then
+ ilrs.update_route(sigd, tcbs)
+ end
+ end
+ end
+end
+
+advtrains.interlocking.route = ilrs
+
diff --git a/advtrains_interlocking/settingtypes.txt b/advtrains_interlocking/settingtypes.txt
new file mode 100644
index 0000000..f1c22b0
--- /dev/null
+++ b/advtrains_interlocking/settingtypes.txt
@@ -0,0 +1,4 @@
+# Stop trains forcibly in front of signal when about to run over an LZB 0 restriction, instead of setting emergency halt for manual resolving
+# This prevents the need to manually restart trains that overran red signals, but is unrealistic.
+# This is a workaround to circumvent system breakages due to bugs in LZB braking curves
+at_il_force_lzb_halt (Force LZB Halt) bool true
diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua
new file mode 100644
index 0000000..a44eda6
--- /dev/null
+++ b/advtrains_interlocking/signal_api.lua
@@ -0,0 +1,515 @@
+-- Signal API implementation
+
+
+--[[
+Signal aspect table:
+Note: All speeds are measured in m/s, aka the number of + signs in the HUD.
+asp = {
+ main = <int speed>,
+ -- Main signal aspect, tells state and permitted speed of next section
+ -- 0 = section is blocked
+ -- >0 = section is free, speed limit is this value
+ -- -1 = section is free, maximum speed permitted
+ -- false/nil = Signal doesn't provide main signal information, retain current speed limit.
+ shunt = <boolean>,
+ -- Whether train may proceed as shunt move, on sight
+ -- main aspect takes precedence over this
+ -- When main==0, train switches to shunt move and is restricted to speed 6
+ proceed_as_main = <boolean>,
+ -- If an approaching train is a shunt move and 'shunt' is false,
+ -- the train may proceed as a train move under the "main" aspect
+ -- if the main aspect permits it (i.e. main!=0)
+ -- If this is not set, shunt moves are NOT allowed to switch to
+ -- a train move, and must stop even if "main" would permit passing.
+ -- This is intended to be used for "Halt for shunt moves" signs.
+
+ dst = <int speed>,
+ -- Distant signal aspect, tells state and permitted speed of the section after next section
+ -- The character of these information is purely informational
+ -- At this time, this field is not actively used
+ -- 0 = section is blocked
+ -- >0 = section is free, speed limit is this value
+ -- -1 = section is free, maximum speed permitted
+ -- false/nil = Signal doesn't provide distant signal information.
+
+ -- the character of call_on and dead_end is purely informative
+ call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
+ dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
+
+ w_speed = <integer>,
+ -- "Warning speed restriction". Supposed for short-term speed
+ -- restrictions which always override any other restrictions
+ -- imposed by "speed" fields, until lifted by a value of -1
+ -- (Example: german Langsamfahrstellen-Signale)
+ }
+}
+
+== How signals actually work in here ==
+Each signal (in the advtrains universe) is some node that has at least the
+following things:
+- An "influence point" that is set somewhere on a rail
+- An aspect which trains that pass the "influence point" have to obey
+
+There can be static and dynamic signals. Static signals are, roughly
+spoken, signs, while dynamic signals are "real" signals which can display
+different things.
+
+The node definition of a signal node should contain those fields:
+groups = {
+ advtrains_signal = 2,
+ save_in_at_nodedb = 1,
+}
+advtrains = {
+ set_aspect = function(pos, node, asp)
+ -- This function gets called whenever the signal should display
+ -- a new or changed signal aspect. It is not required that
+ -- the signal actually displays the exact same aspect, since
+ -- some signals can not do this by design. However, it must
+ -- display an aspect that is at least as restrictive as the passed
+ -- aspect as far as it is capable of doing so.
+ -- Examples:
+ -- - pure shunt signals can not display a "main" aspect
+ -- and have no effect on train moves, so they will only ever
+ -- honor the shunt.free field for their aspect.
+ -- - the german Hl system can only signal speeds of 40, 60
+ -- and 100 km/h, a speed of 80km/h should then be signalled
+ -- as 60 km/h instead.
+ -- In turn, it is not guaranteed that the aspect will fulfill the
+ -- criteria put down in supported_aspects.
+ -- If set_aspect is present, supported_aspects should also be declared.
+
+ -- The aspect passed in here can always be queried using the
+ -- advtrains.interlocking.signal_get_supposed_aspect(pos) function.
+ -- It is always DANGER when the signal is not used as route signal.
+
+ -- For static signals, this function should be completely omitted
+ -- If this function is omitted, it won't be possible to use
+ -- route setting on this signal.
+ end,
+ supported_aspects = {
+ -- A table which tells which different types of aspects this signal
+ -- is able to display. It is used to construct the "aspect editing"
+ -- formspec for route programming (and others) It should always be
+ -- present alongside with set_aspect. If this is not specified but
+ -- set_aspect is, the user will be allowed to select any aspect.
+ -- Any of the fields marked with <boolean/nil> support 3 types of values:
+ nil: if this signal can switch between free/blocked
+ false: always shows "blocked", unchangable
+ true: always shows "free", unchangable
+ -- Any of the "speed" fields should contain a list of possible values
+ -- to be set as restriction. If omitted, the value of the described
+ -- field is always assumed to be false (no information)
+ -- A speed of 0 means that the signal can show a "blocked" aspect
+ -- (which is probably the case for most signals)
+ -- If the signal can signal "no information" on one of the fields
+ -- (thus false is an acceptable value), include false in the list
+ -- If your signal can only display a single speed (may it be -1),
+ -- always enclose that single value into a list. (such as {-1})
+ main = {<speed1>, ..., <speedn>} or nil,
+ dst = {<speed1>, ..., <speedn>} or nil,
+ shunt = <boolean/nil>,
+
+ call_on = <boolean/nil>,
+ dead_end = <boolean/nil>,
+ w_speed = {<speed1>, ..., <speedn>} or nil,
+
+ },
+ Example for supported_aspects:
+ supported_aspects = {
+ main = {0, 6, -1}, -- can show either "Section blocked", "Proceed at speed 6" or "Proceed at maximum speed"
+ dst = {0, false}, -- can show only if next signal shows "blocked", no other information.
+ shunt = false, -- shunting by this signal is never allowed.
+
+ call_on = false,
+ dead_end = false,
+ w_speed = nil,
+ -- none of the information can be shown by the signal
+
+ },
+
+ get_aspect = function(pos, node)
+ -- This function gets called by the train safety system. It
+ should return the aspect that this signal actually displays,
+ not preferably the input of set_aspect.
+ -- For regular, full-featured light signals, they will probably
+ honor all entries in the original aspect, however, e.g.
+ simple shunt signals always return main=false regardless of
+ the set_aspect input because they can not signal "Halt" to
+ train moves.
+ -- advtrains.interlocking.DANGER contains a default "all-danger" aspect.
+ -- If your signal does not cover certain sub-tables of the aspect,
+ the following reasonable defaults are automatically assumed:
+ main = false (unchanged)
+ dst = false (unchanged)
+ shunt = false (shunting not allowed)
+ info = {} (no further information)
+ end,
+}
+on_rightclick = advtrains.interlocking.signal_rc_handler
+can_dig = advtrains.interlocking.signal_can_dig
+after_dig_node = advtrains.interlocking.signal_after_dig
+
+(If you need to specify custom can_dig or after_dig_node callbacks,
+please call those functions anyway!)
+
+Important note: If your signal should support external ways to set its
+aspect (e.g. via mesecons), there are some things that need to be considered:
+- advtrains.interlocking.signal_get_supposed_aspect(pos) won't respect this
+- Whenever you change the signal aspect, and that aspect change
+did not happen through a call to
+advtrains.interlocking.signal_set_aspect(pos, asp), you are
+*required* to call this function:
+advtrains.interlocking.signal_on_aspect_changed(pos)
+in order to notify trains about the aspect change.
+This function will query get_aspect to retrieve the new aspect.
+
+]]--
+
+local DANGER = {
+ main = 0,
+ dst = false,
+ shunt = false,
+}
+advtrains.interlocking.DANGER = DANGER
+
+advtrains.interlocking.GENERIC_FREE = {
+ main = -1,
+ shunt = false,
+ dst = false,
+}
+
+local function convert_aspect_if_necessary(asp)
+ if type(asp.main) == "table" then
+ local newasp = {}
+ if asp.main.free then
+ newasp.main = asp.main.speed
+ else
+ newasp.main = 0
+ end
+ if asp.dst and asp.dst.free then
+ newasp.dst = asp.dst.speed
+ else
+ newasp.dst = 0
+ end
+ newasp.proceed_as_main = asp.shunt.proceed_as_main
+ newasp.shunt = asp.shunt.free
+ -- Note: info table not transferred, it's not used right now
+ return newasp
+ end
+ return asp
+end
+
+function advtrains.interlocking.update_signal_aspect(tcbs)
+ if tcbs.signal then
+ local asp = tcbs.aspect or DANGER
+ advtrains.interlocking.signal_set_aspect(tcbs.signal, asp)
+ end
+end
+
+function advtrains.interlocking.signal_can_dig(pos)
+ return not advtrains.interlocking.db.get_sigd_for_signal(pos)
+end
+
+function advtrains.interlocking.signal_after_dig(pos)
+ -- clear influence point
+ advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+end
+
+function advtrains.interlocking.signal_set_aspect(pos, asp)
+ asp = convert_aspect_if_necessary(asp)
+ local node=advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.set_aspect then
+ ndef.advtrains.set_aspect(pos, node, asp)
+ advtrains.interlocking.signal_on_aspect_changed(pos)
+ end
+end
+
+-- should be called when aspect has changed on this signal.
+function advtrains.interlocking.signal_on_aspect_changed(pos)
+ local ipts, iconn = advtrains.interlocking.db.get_ip_by_signalpos(pos)
+ if not ipts then return end
+ local ipos = minetest.string_to_pos(ipts)
+
+ advtrains.invalidate_all_paths_ahead(ipos)
+end
+
+function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, pointed_thing)
+ local pname = player:get_player_name()
+ local control = player:get_player_control()
+ if control.aux1 then
+ advtrains.interlocking.show_ip_form(pos, pname)
+ return
+ end
+
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if sigd then
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ else
+ local ndef = minetest.registered_nodes[node.name]
+ if ndef.advtrains and ndef.advtrains.set_aspect then
+ -- permit to set aspect manually
+ local function callback(pname, aspect)
+ advtrains.interlocking.signal_set_aspect(pos, aspect)
+ end
+ local isasp = ndef.advtrains.get_aspect(pos, node)
+
+ advtrains.interlocking.show_signal_aspect_selector(
+ pname,
+ ndef.advtrains.supported_aspects,
+ "Set aspect manually", callback,
+ isasp)
+ else
+ --static signal - only IP
+ advtrains.interlocking.show_ip_form(pos, pname)
+ end
+ end
+end
+
+-- Returns the aspect the signal at pos is supposed to show
+function advtrains.interlocking.signal_get_supposed_aspect(pos)
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if sigd then
+ local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
+ if tcbs.aspect then
+ return convert_aspect_if_necessary(tcbs.aspect)
+ end
+ end
+ return DANGER;
+end
+
+-- Returns the actual aspect of the signal at position, as returned by the nodedef.
+-- returns nil when there's no signal at the position
+function advtrains.interlocking.signal_get_aspect(pos)
+ local node=advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.get_aspect then
+ local asp = ndef.advtrains.get_aspect(pos, node)
+ if not asp then asp = DANGER end
+ return convert_aspect_if_necessary(asp)
+ end
+ return nil
+end
+
+-- Returns the "supported_aspects" of the signal at position, as returned by the nodedef.
+-- returns nil when there's no signal at the position
+function advtrains.interlocking.signal_get_supported_aspects(pos)
+ local node=advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.supported_aspects then
+ local asp = ndef.advtrains.supported_aspects
+ return asp
+ end
+ return nil
+end
+
+local players_assign_ip = {}
+
+local function ipmarker(ipos, connid)
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(ipos, advtrains.all_tracktypes)
+ if not node_ok then return end
+ local yaw = advtrains.dir_to_angle(conns[connid].c)
+
+ -- using tcbmarker here
+ local obj = minetest.add_entity(vector.add(ipos, {x=0, y=0.2, z=0}), "advtrains_interlocking:tcbmarker")
+ if not obj then return end
+ obj:set_yaw(yaw)
+ obj:set_properties({
+ textures = { "at_il_signal_ip.png" },
+ })
+end
+
+-- shows small info form for signal IP state/assignment
+-- only_notset: show only if it is not set yet (used by signal tcb assignment)
+function advtrains.interlocking.show_ip_form(pos, pname, only_notset)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ local form = "size[7,5]label[0.5,0.5;Signal at "..minetest.pos_to_string(pos).."]"
+ local pts, connid = advtrains.interlocking.db.get_ip_by_signalpos(pos)
+ if pts then
+ form = form.."label[0.5,1.5;Influence point is set at "..pts.."/"..connid.."]"
+ form = form.."button_exit[0.5,2.5; 5,1;set;Move]"
+ form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
+ local ipos = minetest.string_to_pos(pts)
+ ipmarker(ipos, connid)
+ else
+ form = form.."label[0.5,1.5;Influence point is not set.]"
+ form = form.."label[0.5,2.0;It is recommended to set an influence point.]"
+ form = form.."label[0.5,2.5;This is the point where trains will obey the signal.]"
+
+ form = form.."button_exit[0.5,3.5; 5,1;set;Set]"
+ end
+ if not only_notset or not pts then
+ minetest.show_formspec(pname, "at_il_ipassign_"..minetest.pos_to_string(pos), form)
+ end
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, {train_operator=true, interlocking=true}) then
+ return
+ end
+ local pts = string.match(formname, "^at_il_ipassign_([^_]+)$")
+ local pos
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ end
+ if pos then
+ if fields.set then
+ advtrains.interlocking.signal_init_ip_assign(pos, pname)
+ elseif fields.clear then
+ advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+ end
+ end
+end)
+
+-- inits the signal IP assignment process
+function advtrains.interlocking.signal_init_ip_assign(pos, pname)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ --remove old IP
+ --advtrains.interlocking.db.clear_ip_by_signalpos(pos)
+ minetest.chat_send_player(pname, "Configuring Signal: Please look in train's driving direction and punch rail to set influence point.")
+
+ players_assign_ip[pname] = pos
+end
+
+minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ -- IP assignment
+ local signalpos = players_assign_ip[pname]
+ if signalpos then
+ if vector.distance(pos, signalpos)<=50 then
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if node_ok and #conns == 2 then
+
+ local yaw = player:get_look_horizontal()
+ local plconnid = advtrains.yawToClosestConn(yaw, conns)
+
+ -- add assignment if not already present.
+ local pts = advtrains.roundfloorpts(pos)
+ if not advtrains.interlocking.db.get_ip_signal_asp(pts, plconnid) then
+ advtrains.interlocking.db.set_ip_signal(pts, plconnid, signalpos)
+ ipmarker(pos, plconnid)
+ minetest.chat_send_player(pname, "Configuring Signal: Successfully set influence point")
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: Influence point of another signal is already present!")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: This is not a normal two-connection rail! Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring Signal: Node is too far away. Aborted.")
+ end
+ players_assign_ip[pname] = nil
+ end
+end)
+
+
+--== aspect selector ==--
+
+local players_aspsel = {}
+
+--[[
+suppasp: "supported_aspects" table
+purpose: form title string
+callback: func(pname, aspect) called on form submit
+isasp: aspect currently set
+]]
+function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp)
+ local suppasp = p_suppasp or {
+ main = {0, -1}, dst = {false}, shunt = false, info = {},
+ }
+ local purpose = p_purpose or ""
+
+ local form = "size[7,5]label[0.5,0.5;Select Signal Aspect:]"
+ form = form.."label[0.5,1;"..purpose.."]"
+
+ form = form.."label[0.5,1.5;== Main Signal ==]"
+ local selid = 1
+ local entries = {}
+ for idx, spv in ipairs(suppasp.main) do
+ local entry
+ if spv == 0 then
+ entry = "Halt"
+ elseif spv == -1 then
+ entry = "Continue at maximum speed"
+ elseif not spv then
+ entry = "Continue\\, speed limit unchanged (no info)"
+ else
+ entry = "Continue at speed of "..spv
+ end
+ -- hack: the crappy formspec system returns the label, not the index. save the index in it.
+ entries[idx] = idx.."| "..entry
+ if isasp and spv == (isasp.main or false) then
+ selid = idx
+ end
+ end
+ form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]"
+
+
+ form = form.."label[0.5,3;== Shunting ==]"
+ if suppasp.shunt == nil then
+ local st = 1
+ if isasp and isasp.shunt then st=2 end
+ form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]"
+ end
+
+ form = form.."button_exit[0.5,4.5; 5,1;save;OK]"
+
+ local token = advtrains.random_id()
+
+ minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form)
+
+ minetest.after(1, function()
+ players_aspsel[pname] = {
+ suppasp = suppasp,
+ callback = callback,
+ token = token,
+ }
+ end)
+end
+
+local function usebool(sup, val, free)
+ if sup == nil then
+ return val==free
+ else
+ return sup
+ end
+end
+
+-- other side of hack: extract the index
+local function ddindex(val)
+ return tonumber(string.match(val, "^(%d+)|"))
+end
+
+-- TODO use non-hacky way to parse outputs
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ local psl = players_aspsel[pname]
+ if psl then
+ if formname == "at_il_sigaspdia_"..psl.token then
+ if fields.save then
+ local maini = ddindex(fields.main)
+ if not maini then return end
+ local asp = {
+ main = psl.suppasp.main[maini],
+ dst = false,
+ shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"),
+ info = {}
+ }
+ psl.callback(pname, asp)
+ end
+ else
+ players_aspsel[pname] = nil
+ end
+ end
+
+end)
diff --git a/advtrains_interlocking/tcb_ts_ui.lua b/advtrains_interlocking/tcb_ts_ui.lua
new file mode 100755
index 0000000..34fbf7f
--- /dev/null
+++ b/advtrains_interlocking/tcb_ts_ui.lua
@@ -0,0 +1,830 @@
+-- Track Circuit Breaks and Track Sections - Player interaction
+
+local players_assign_tcb = {}
+local players_assign_signal = {}
+local players_link_ts = {}
+
+local ildb = advtrains.interlocking.db
+local ilrs = advtrains.interlocking.route
+
+local sigd_equal = advtrains.interlocking.sigd_equal
+
+local lntrans = { "A", "B" }
+
+local function sigd_to_string(sigd)
+ return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
+end
+
+minetest.register_node("advtrains_interlocking:tcb_node", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="facedir",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/6, -1/2, -1/6, 1/6, 1/4, 1/6},
+ },
+ mesh = "at_il_tcb_node.obj",
+ tiles = {"at_il_tcb_node.png"},
+ description="Track Circuit Break",
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ --save_in_at_nodedb=2,
+ at_il_track_circuit_break = 1,
+ },
+ after_place_node = function(pos, node, player)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Unconfigured Track Circuit Break, right-click to assign.")
+ end,
+ on_rightclick = function(pos, node, player)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+
+ local meta = minetest.get_meta(pos)
+ local tcbpts = meta:get_string("tcb_pos")
+ if tcbpts ~= "" then
+ local tcbpos = minetest.string_to_pos(tcbpts)
+ local tcb = ildb.get_tcb(tcbpos)
+ if tcb then
+ advtrains.interlocking.show_tcb_form(tcbpos, pname)
+ else
+ minetest.chat_send_player(pname, "This TCB has been removed. Please dig marker.")
+ end
+ else
+ --unconfigured
+ minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.")
+
+ players_assign_tcb[pname] = pos
+ end
+ end,
+ --on_punch = function(pos, node, player)
+ -- local meta = minetest.get_meta(pos)
+ -- local tcbpts = meta:get_string("tcb_pos")
+ -- if tcbpts ~= "" then
+ -- local tcbpos = minetest.string_to_pos(tcbpts)
+ -- advtrains.interlocking.show_tcb_marker(tcbpos)
+ -- end
+ --end,
+ can_dig = function(pos, player)
+ if player == nil then return false end
+
+ local pname = player:get_player_name()
+
+ -- Those markers can only be dug when all adjacent TS's are set
+ -- as EOI.
+ local meta = minetest.get_meta(pos)
+ local tcbpts = meta:get_string("tcb_pos")
+ if tcbpts ~= "" then
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ local tcbpos = minetest.string_to_pos(tcbpts)
+ local tcb = ildb.get_tcb(tcbpos)
+ if not tcb then return true end
+ for connid=1,2 do
+ if tcb[connid].ts_id or tcb[connid].signal then
+ minetest.chat_send_player(pname, "Can't remove TCB: Both sides must have no track section and no signal assigned!")
+ return false
+ end
+ if not ildb.may_modify_tcbs(tcb[connid]) then
+ minetest.chat_send_player(pname, "Can't remove TCB: Side "..connid.." forbids modification (shouldn't happen).")
+ return false
+ end
+ end
+ end
+ return true
+ end,
+ after_dig_node = function(pos, oldnode, oldmetadata, player)
+ if not oldmetadata or not oldmetadata.fields then return end
+ local tcbpts = oldmetadata.fields.tcb_pos
+ if tcbpts and tcbpts ~= "" then
+ local tcbpos = minetest.string_to_pos(tcbpts)
+ local success = ildb.remove_tcb(tcbpos)
+ if success and player then
+ minetest.chat_send_player(player:get_player_name(), "TCB has been removed.")
+ else
+ minetest.chat_send_player(player:get_player_name(), "Failed to remove TCB!")
+ minetest.set_node(pos, oldnode)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("tcb_pos", minetest.pos_to_string(tcbpos))
+ end
+ end
+ end,
+})
+
+
+-- Crafting
+
+-- set some fallbacks
+local tcb_core = "default:mese_crystal"
+local tcb_secondary = "default:mese_crystal_fragment"
+
+--alternative recipe items
+--core
+if minetest.get_modpath("basic_materials") then
+ tcb_core = "basic_materials:ic"
+elseif minetest.get_modpath("technic") then
+ tcb_core = "technic:control_logic_unit"
+end
+--print("TCB Core: "..tcb_core)
+--secondary
+if minetest.get_modpath("mesecons") then
+ tcb_secondary = 'mesecons:wire_00000000_off'
+end
+--print("TCB Secondary: "..tcb_secondary)
+
+minetest.register_craft({
+ output = 'advtrains_interlocking:tcb_node 4',
+ recipe = {
+ {tcb_secondary,tcb_core,tcb_secondary},
+ {'advtrains:dtrack_placer','','advtrains:dtrack_placer'}
+ },
+ --actually use track in the tcb recipe
+ replacements = {
+ {"advtrains:dtrack_placer","advtrains:dtrack_placer"},
+ {"advtrains:dtrack_placer","advtrains:dtrack_placer"},
+ }
+})
+
+--nil the temp crafting variables
+tcb_core= nil
+tcb_secondary = nil
+
+minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ -- TCB assignment
+ local tcbnpos = players_assign_tcb[pname]
+ if tcbnpos then
+ if vector.distance(pos, tcbnpos)<=20 then
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if node_ok and #conns == 2 then
+ local ok = ildb.create_tcb(pos)
+
+ if not ok then
+ minetest.chat_send_player(pname, "Configuring TCB: TCB already exists at this position! It has now been re-assigned.")
+ end
+
+ ildb.sync_tcb_neighbors(pos, 1)
+ ildb.sync_tcb_neighbors(pos, 2)
+
+ local meta = minetest.get_meta(tcbnpos)
+ meta:set_string("tcb_pos", minetest.pos_to_string(pos))
+ meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos))
+ minetest.chat_send_player(pname, "Configuring TCB: Successfully configured TCB")
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
+ end
+ players_assign_tcb[pname] = nil
+ end
+
+ -- Signal assignment
+ local sigd = players_assign_signal[pname]
+ if sigd then
+ if vector.distance(pos, sigd.p)<=50 then
+ local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2
+ if is_signal then
+ local ndef = minetest.registered_nodes[node.name]
+ if ndef and ndef.advtrains and ndef.advtrains.set_aspect then
+ local tcbs = ildb.get_tcbs(sigd)
+ if tcbs then
+ tcbs.signal = pos
+ if not tcbs.signal_name then
+ tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p)
+ end
+ if not tcbs.routes then
+ tcbs.routes = {}
+ end
+ ildb.set_sigd_for_signal(pos, sigd)
+ minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.")
+ advtrains.interlocking.show_ip_form(pos, pname, true)
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: Cannot use static signals for routesetting. Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: Not a compatible signal. Aborted.")
+ end
+ else
+ minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
+ end
+ players_assign_signal[pname] = nil
+ end
+end)
+
+-- TCB Form
+
+local function mktcbformspec(tcbs, btnpref, offset, pname)
+ local form = ""
+ local ts
+ if tcbs.ts_id then
+ ts = ildb.get_ts(tcbs.ts_id)
+ end
+ if ts then
+ form = form.."label[0.5,"..offset..";Side "..btnpref..": "..minetest.formspec_escape(ts.name).."]"
+ form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;Show track section]"
+ if ildb.may_modify_tcbs(tcbs) then
+ -- Note: the security check to prohibit those actions is located in database.lua in the corresponding functions.
+ form = form.."button[0.5,"..(offset+1.5)..";2.5,1;"..btnpref.."_update;Update near TCBs]"
+ form = form.."button[3 ,"..(offset+1.5)..";2.5,1;"..btnpref.."_remove;Remove from section]"
+ end
+ else
+ tcbs.ts_id = nil
+ form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]"
+ form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]"
+ --if tcbs.section_free then
+ --form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setlocked;Section is free]"
+ --else
+ --form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"
+ --end
+ end
+ if tcbs.signal then
+ form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"
+ else
+ form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_asnsig;Assign a signal]"
+ end
+ return form
+end
+
+
+function advtrains.interlocking.show_tcb_form(pos, pname)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ local tcb = ildb.get_tcb(pos)
+ if not tcb then return end
+
+ local form = "size[6,9] label[0.5,0.5;Track Circuit Break Configuration]"
+ form = form .. mktcbformspec(tcb[1], "A", 1, pname)
+ form = form .. mktcbformspec(tcb[2], "B", 5, pname)
+
+ minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form)
+ advtrains.interlocking.show_tcb_marker(pos)
+end
+
+--helper: length of nil table is 0
+local function nlen(t)
+ if not t then return 0 end
+ return #t
+end
+
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ local pts = string.match(formname, "^at_il_tcbconfig_(.+)$")
+ local pos
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ end
+ if pos and not fields.quit then
+ local tcb = ildb.get_tcb(pos)
+ if not tcb then return end
+ local f_gotots = {fields.A_gotots, fields.B_gotots}
+ local f_update = {fields.A_update, fields.B_update}
+ local f_remove = {fields.A_remove, fields.B_remove}
+ local f_makeil = {fields.A_makeil, fields.B_makeil}
+ local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
+ local f_setfree = {fields.A_setfree, fields.B_setfree}
+ local f_asnsig = {fields.A_asnsig, fields.B_asnsig}
+ local f_sigdia = {fields.A_sigdia, fields.B_sigdia}
+
+ for connid=1,2 do
+ local tcbs = tcb[connid]
+ if tcbs.ts_id then
+ if f_gotots[connid] then
+ advtrains.interlocking.show_ts_form(tcbs.ts_id, pname)
+ return
+ end
+ if f_update[connid] then
+ ildb.sync_tcb_neighbors(pos, connid)
+ end
+ if f_remove[connid] then
+ ildb.remove_from_interlocking({p=pos, s=connid})
+ end
+ else
+ if f_makeil[connid] then
+ -- try sinc_tcb_neighbors first
+ ildb.sync_tcb_neighbors(pos, connid)
+ -- if that didn't work, create new section
+ if not tcbs.ts_id then
+ ildb.create_ts({p=pos, s=connid})
+ ildb.sync_tcb_neighbors(pos, connid)
+ end
+ end
+ -- non-interlocked
+ if f_setfree[connid] then
+ tcbs.section_free = true
+ end
+ if f_setlocked[connid] then
+ tcbs.section_free = nil
+ end
+ end
+ if f_asnsig[connid] and not tcbs.signal then
+ minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.")
+ players_assign_signal[pname] = {p=pos, s=connid}
+ minetest.close_formspec(pname, formname)
+ return
+ end
+ if f_sigdia[connid] and tcbs.signal then
+ advtrains.interlocking.show_signalling_form({p=pos, s=connid}, pname)
+ return
+ end
+
+ end
+ advtrains.interlocking.show_tcb_form(pos, pname)
+ end
+
+end)
+
+
+
+-- TS Formspec
+
+-- textlist selection temporary storage
+local ts_pselidx = {}
+
+function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
+ if not minetest.check_player_privs(pname, "interlocking") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ local ts = ildb.get_ts(ts_id)
+ if not ts_id then return end
+
+ local form = "size[10,10]label[0.5,0.5;Track Section Detail - "..ts_id.."]"
+ form = form.."field[0.8,2;5.2,1;name;Section name;"..minetest.formspec_escape(ts.name).."]"
+ form = form.."button[5.5,1.7;1,1;setname;Set]"
+ local hint
+
+ local strtab = {}
+ for idx, sigd in ipairs(ts.tc_breaks) do
+ strtab[#strtab+1] = minetest.formspec_escape(sigd_to_string(sigd))
+ advtrains.interlocking.show_tcb_marker(sigd.p)
+ end
+
+ form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]"
+
+ if ildb.may_modify_ts(ts) then
+
+ if players_link_ts[pname] then
+ local other_id = players_link_ts[pname]
+ local other_ts = ildb.get_ts(other_id)
+ if other_ts then
+ if ildb.may_modify_ts(other_ts) then
+ form = form.."button[5.5,3;3.5,1;mklink;Join with "..minetest.formspec_escape(other_ts.name).."]"
+ form = form.."button[9 ,3;0.5,1;cancellink;X]"
+ end
+ end
+ else
+ form = form.."button[5.5,3;4,1;link;Join into other section]"
+ hint = 1
+ end
+ form = form.."button[5.5,4;4,1;dissolve;Dissolve Section]"
+ form = form.."tooltip[dissolve;This will remove the track section and set all its end points to End Of Interlocking]"
+ if sel_tcb then
+ form = form.."button[5.5,5;4,1;del_tcb;Unlink selected TCB]"
+ hint = 2
+ end
+ else
+ hint=3
+ end
+
+ if ts.route then
+ form = form.."label[0.5,6.1;Route is set: "..ts.route.rsn.."]"
+ elseif ts.route_post then
+ form = form.."label[0.5,6.1;Section holds "..#(ts.route_post.lcks or {}).." route locks.]"
+ end
+ -- occupying trains
+ if ts.trains and #ts.trains>0 then
+ form = form.."label[0.5,7.1;Trains on this section:]"
+ form = form.."textlist[0.5,7.7;3,2;trnlist;"..table.concat(ts.trains, ",").."]"
+ else
+ form = form.."label[0.5,7.1;No trains on this section.]"
+ end
+
+ form = form.."button[5.5,7;4,1;reset;Reset section state]"
+
+ if hint == 1 then
+ form = form.."label[0.5,0.75;Use the 'Join' button to designate rail crosses and link not listed far-away TCBs]"
+ elseif hint == 2 then
+ form = form.."label[0.5,0.75;Unlinking a TCB will set it to non-interlocked mode.]"
+ elseif hint == 3 then
+ form = form.."label[0.5,0.75;You cannot modify track sections when a route is set or a train is on the section.]"
+ --form = form.."label[0.5,1;Trying to unlink a TCB directly connected to this track will not work.]"
+ end
+
+ ts_pselidx[pname]=sel_tcb
+ minetest.show_formspec(pname, "at_il_tsconfig_"..ts_id, form)
+
+end
+
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ -- independent of the formspec, clear this whenever some formspec event happens
+ local tpsi = ts_pselidx[pname]
+ ts_pselidx[pname] = nil
+
+ local ts_id = string.match(formname, "^at_il_tsconfig_(.+)$")
+ if ts_id and not fields.quit then
+ local ts = ildb.get_ts(ts_id)
+ if not ts then return end
+
+ local sel_tcb
+ if fields.tcblist then
+ local tev = minetest.explode_textlist_event(fields.tcblist)
+ sel_tcb = tev.index
+ ts_pselidx[pname] = sel_tcb
+ elseif tpsi then
+ sel_tcb = tpsi
+ end
+
+ if ildb.may_modify_ts(ts) then
+ if players_link_ts[pname] then
+ if fields.cancellink then
+ players_link_ts[pname] = nil
+ elseif fields.mklink then
+ ildb.link_track_sections(players_link_ts[pname], ts_id)
+ players_link_ts[pname] = nil
+ end
+ end
+
+ if fields.del_tcb and sel_tcb and sel_tcb > 0 and sel_tcb <= #ts.tc_breaks then
+ if not ildb.remove_from_interlocking(ts.tc_breaks[sel_tcb]) then
+ minetest.chat_send_player(pname, "Please unassign signal first!")
+ end
+ sel_tcb = nil
+ end
+
+ if fields.link then
+ players_link_ts[pname] = ts_id
+ end
+ if fields.dissolve then
+ ildb.dissolve_ts(ts_id)
+ minetest.close_formspec(pname, formname)
+ return
+ end
+ end
+
+ if fields.setname then
+ ts.name = fields.name
+ if ts.name == "" then
+ ts.name = "Section "..ts_id
+ end
+ end
+
+ if fields.reset then
+ -- User requested resetting the section
+ -- Show him what this means...
+ local form = "size[7,5]label[0.5,0.5;Reset track section]"
+ form = form.."label[0.5,1;This will clear the list of trains\nand the routesetting status of this section.\nAre you sure?]"
+ form = form.."button_exit[0.5,2.5; 5,1;reset;Yes]"
+ form = form.."button_exit[0.5,3.5; 5,1;cancel;Cancel]"
+ minetest.show_formspec(pname, "at_il_tsreset_"..ts_id, form)
+ return
+ end
+
+ advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
+ return
+ end
+
+ ts_id = string.match(formname, "^at_il_tsreset_(.+)$")
+ if ts_id and fields.reset then
+ local ts = ildb.get_ts(ts_id)
+ if not ts then return end
+ ts.trains = {}
+ if ts.route_post then
+ advtrains.interlocking.route.free_route_locks(ts_id, ts.route_post.locks)
+ end
+ ts.route_post = nil
+ ts.route = nil
+ for _, sigd in ipairs(ts.tc_breaks) do
+ local tcbs = ildb.get_tcbs(sigd)
+ advtrains.interlocking.update_signal_aspect(tcbs)
+ end
+ minetest.chat_send_player(pname, "Reset track section "..ts_id.."!")
+ end
+end)
+
+-- TCB marker entities
+
+-- table with objectRefs
+local markerent = {}
+
+minetest.register_entity("advtrains_interlocking:tcbmarker", {
+ visual = "mesh",
+ mesh = "trackplane.b3d",
+ textures = {"at_il_tcb_marker.png"},
+ collisionbox = {-1,-0.5,-1, 1,-0.4,1},
+ visual_size = {x=10, y=10},
+ on_punch = function(self)
+ self.object:remove()
+ end,
+ on_rightclick = function(self, player)
+ if self.tcbpos and player then
+ advtrains.interlocking.show_tcb_form(self.tcbpos, player:get_player_name())
+ end
+ end,
+ get_staticdata = function() return "STATIC" end,
+ on_activate = function(self, sdata) if sdata=="STATIC" then self.object:remove() end end,
+ static_save = false,
+})
+
+function advtrains.interlocking.show_tcb_marker(pos)
+ --atdebug("showing tcb marker",pos)
+ local tcb = ildb.get_tcb(pos)
+ if not tcb then return end
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ if not node_ok then return end
+ local yaw = advtrains.conn_angle_median(conns[2].c, conns[1].c)
+
+ local itex = {}
+ for connid=1,2 do
+ local tcbs = tcb[connid]
+ local ts
+ if tcbs.ts_id then
+ ts = ildb.get_ts(tcbs.ts_id)
+ end
+ if ts then
+ itex[connid] = ts.name
+ else
+ itex[connid] = "--EOI--"
+ end
+ end
+
+ local pts = advtrains.roundfloorpts(pos)
+ if markerent[pts] then
+ markerent[pts]:remove()
+ end
+
+ local obj = minetest.add_entity(pos, "advtrains_interlocking:tcbmarker")
+ if not obj then return end
+ obj:set_yaw(yaw)
+ obj:set_properties({
+ infotext = "A = "..itex[1].."\nB = "..itex[2]
+ })
+ local le = obj:get_luaentity()
+ if le then le.tcbpos = pos end
+
+ markerent[pts] = obj
+end
+
+-- Signalling formspec - set routes a.s.o
+
+-- textlist selection temporary storage
+local sig_pselidx = {}
+-- Players having a signalling form open
+local p_open_sig_form = {}
+
+function advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, called_from_form_update)
+ if not minetest.check_player_privs(pname, "train_operator") then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ local hasprivs = minetest.check_player_privs(pname, "interlocking")
+ local tcbs = ildb.get_tcbs(sigd)
+
+ if not tcbs.signal then return end
+ if not tcbs.signal_name then tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p) end
+ if not tcbs.routes then tcbs.routes = {} end
+
+ local form = "size[7,10]label[0.5,0.5;Signal at "..minetest.pos_to_string(sigd.p).."]"
+ form = form.."field[0.8,1.5;5.2,1;name;Signal name;"..minetest.formspec_escape(tcbs.signal_name).."]"
+ form = form.."button[5.5,1.2;1,1;setname;Set]"
+
+ if tcbs.routeset then
+ local rte = tcbs.routes[tcbs.routeset]
+ if not rte then
+ atwarn("Unknown route set from signal!")
+ tcbs.routeset = nil
+ return
+ end
+ form = form.."label[0.5,2.5;A route is requested from this signal:]"
+ form = form.."label[0.5,3.0;"..minetest.formspec_escape(rte.name).."]"
+ if tcbs.route_committed then
+ form = form.."label[0.5,3.5;Route has been set.]"
+ else
+ form = form.."label[0.5,3.5;Waiting for route to be set...]"
+ if tcbs.route_rsn then
+ form = form.."label[0.5,4;"..minetest.formspec_escape(tcbs.route_rsn).."]"
+ end
+ end
+ if not tcbs.route_auto then
+ form = form.."button[0.5,7; 5,1;auto;Enable Automatic Working]"
+ else
+ form = form.."label[0.5,7 ;Automatic Working is active.]"
+ form = form.."label[0.5,7.3;Route is re-set when a train passed.]"
+ form = form.."button[0.5,7.7; 5,1;noauto;Disable Automatic Working]"
+ end
+
+ form = form.."button[0.5,6; 5,1;cancelroute;Cancel Route]"
+ else
+ if not tcbs.route_origin then
+ local strtab = {}
+ for idx, route in ipairs(tcbs.routes) do
+ local clr = ""
+ if route.ars then
+ clr = "#FF5555"
+ if route.ars.default then
+ clr = "#55FF55"
+ end
+ end
+ strtab[#strtab+1] = clr .. minetest.formspec_escape(route.name)
+ end
+ form = form.."label[0.5,2.5;Routes:]"
+ form = form.."textlist[0.5,3;5,3;rtelist;"..table.concat(strtab, ",").."]"
+ if sel_rte then
+ form = form.."button[0.5,6; 5,1;setroute;Set Route]"
+ form = form.."button[0.5,7;2,1;dsproute;Show]"
+ if hasprivs then
+ form = form.."button[3.5,7;2,1;editroute;Edit]"
+ end
+ else
+ if tcbs.ars_disabled then
+ form = form.."label[0.5,6 ;NOTE: ARS is disabled.]"
+ form = form.."label[0.5,6.5;Routes are not automatically set.]"
+ end
+ end
+ if hasprivs then
+ form = form.."button[0.5,8;2.5,1;newroute;New Route]"
+ form = form.."button[ 3,8;2.5,1;unassign;Unassign Signal]"
+ form = form.."button[ 3,9;2.5,1;influp;Influence Point]"
+ end
+ if tcbs.ars_disabled then
+ form = form.."button[0.5,9;2.5,1;arsenable;Enable ARS]"
+ else
+ form = form.."button[0.5,9;2.5,1;arsdisable;Disable ARS]"
+ end
+ elseif sigd_equal(tcbs.route_origin, sigd) then
+ -- something has gone wrong: tcbs.routeset should have been set...
+ form = form.."label[0.5,2.5;Inconsistent state: route_origin is same TCBS but no route set. Try again.]"
+ ilrs.cancel_route_from(sigd)
+ else
+ form = form.."label[0.5,2.5;Route is set over this signal by:\n"..sigd_to_string(tcbs.route_origin).."]"
+ form = form.."label[0.5,4;Wait for this route to be cancelled in order to do anything here.]"
+ end
+ end
+ sig_pselidx[pname] = sel_rte
+ minetest.show_formspec(pname, "at_il_signalling_"..minetest.pos_to_string(sigd.p).."_"..sigd.s, form)
+ p_open_sig_form[pname] = sigd
+
+ -- always a good idea to update the signal aspect
+ if not called_from_form_update then
+ -- FIX prevent a callback loop
+ advtrains.interlocking.update_signal_aspect(tcbs)
+ end
+end
+
+function advtrains.interlocking.update_player_forms(sigd)
+ for pname, tsigd in pairs(p_open_sig_form) do
+ if advtrains.interlocking.sigd_equal(sigd, tsigd) then
+ advtrains.interlocking.show_signalling_form(sigd, pname, nil)
+ end
+ end
+end
+
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "train_operator") then
+ return
+ end
+ local hasprivs = minetest.check_player_privs(pname, "interlocking")
+
+ -- independent of the formspec, clear this whenever some formspec event happens
+ local tpsi = sig_pselidx[pname]
+ sig_pselidx[pname] = nil
+ p_open_sig_form[pname] = nil
+
+ local pts, connids = string.match(formname, "^at_il_signalling_([^_]+)_(%d)$")
+ local pos, connid
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ connid = tonumber(connids)
+ if not connid or connid<1 or connid>2 then return end
+ end
+ if pos and connid and not fields.quit then
+ local sigd = {p=pos, s=connid}
+ local tcbs = ildb.get_tcbs(sigd)
+ if not tcbs then return end
+
+ local sel_rte
+ if fields.rtelist then
+ local tev = minetest.explode_textlist_event(fields.rtelist)
+ sel_rte = tev.index
+ elseif tpsi then
+ sel_rte = tpsi
+ end
+ if fields.setname and fields.name and hasprivs then
+ tcbs.signal_name = fields.name
+ end
+ if tcbs.routeset and fields.cancelroute then
+ if tcbs.routes[tcbs.routeset] and tcbs.routes[tcbs.routeset].ars then
+ tcbs.ars_disabled = true
+ end
+ -- if route committed, cancel route ts info
+ ilrs.update_route(sigd, tcbs, nil, true)
+ end
+ if not tcbs.routeset then
+ if fields.newroute and hasprivs then
+ advtrains.interlocking.init_route_prog(pname, sigd)
+ minetest.close_formspec(pname, formname)
+ return
+ end
+ if sel_rte and tcbs.routes[sel_rte] then
+ if fields.setroute then
+ ilrs.update_route(sigd, tcbs, sel_rte)
+ end
+ if fields.dsproute then
+ local t = os.clock()
+ advtrains.interlocking.visualize_route(sigd, tcbs.routes[sel_rte], "disp_"..t)
+ minetest.after(10, function() advtrains.interlocking.clear_visu_context("disp_"..t) end)
+ end
+ if fields.editroute and hasprivs then
+ advtrains.interlocking.show_route_edit_form(pname, sigd, sel_rte)
+ --local rte = tcbs.routes[sel_rte]
+ --minetest.show_formspec(pname, formname.."_renroute_"..sel_rte, "field[name;Enter new route name;"..rte.name.."]")
+ return
+ end
+ end
+ end
+
+ if fields.unassign and hasprivs then
+ -- unassigning the signal from the tcbs
+ -- only when no route is set.
+ -- Routes and name remain saved, in case the player wants to reassign a new signal
+ if not tcbs.routeset then
+ local signal_pos = tcbs.signal
+ ildb.set_sigd_for_signal(signal_pos, nil)
+ tcbs.signal = nil
+ tcbs.aspect = nil
+ minetest.close_formspec(pname, formname)
+ minetest.chat_send_player(pname, "Signal has been unassigned. Name and routes are kept for reuse.")
+ return
+ else
+ minetest.chat_send_player(pname, "Please cancel route first!")
+ end
+ end
+ if fields.influp and hasprivs then
+ advtrains.interlocking.show_ip_form(tcbs.signal, pname)
+ return
+ end
+
+ if tcbs.ars_disabled and fields.arsenable then
+ tcbs.ars_disabled = nil
+ end
+ if not tcbs.ars_disabled and fields.arsdisable then
+ tcbs.ars_disabled = true
+ end
+
+ if fields.auto then
+ tcbs.route_auto = true
+ end
+ if fields.noauto then
+ tcbs.route_auto = false
+ end
+
+ advtrains.interlocking.show_signalling_form(sigd, pname, sel_rte, true)
+ return
+ end
+
+
+ if not hasprivs then return end
+ -- rename route
+ local rind, rte_id
+ pts, connids, rind = string.match(formname, "^at_il_signalling_([^_]+)_(%d)_renroute_(%d+)$")
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ connid = tonumber(connids)
+ rte_id = tonumber(rind)
+ if not connid or connid<1 or connid>2 then return end
+ end
+ if pos and connid and rind and fields.name then
+ local sigd = {p=pos, s=connid}
+ local tcbs = ildb.get_tcbs(sigd)
+ if tcbs.routes[rte_id] then
+ tcbs.routes[rte_id].name = fields.name
+ advtrains.interlocking.show_signalling_form(sigd, pname)
+ end
+ end
+end)
diff --git a/advtrains_interlocking/textures/advtrains_dtrack_npr_placer.png b/advtrains_interlocking/textures/advtrains_dtrack_npr_placer.png
new file mode 100644
index 0000000..0d1c769
--- /dev/null
+++ b/advtrains_interlocking/textures/advtrains_dtrack_npr_placer.png
Binary files differ
diff --git a/advtrains_interlocking/textures/advtrains_dtrack_shared_npr.png b/advtrains_interlocking/textures/advtrains_dtrack_shared_npr.png
new file mode 100644
index 0000000..0116c27
--- /dev/null
+++ b/advtrains_interlocking/textures/advtrains_dtrack_shared_npr.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_route_end.png b/advtrains_interlocking/textures/at_il_route_end.png
new file mode 100644
index 0000000..1433f0c
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_route_end.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_route_lock.png b/advtrains_interlocking/textures/at_il_route_lock.png
new file mode 100644
index 0000000..6a5269b
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_route_lock.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_route_lock_edit.png b/advtrains_interlocking/textures/at_il_route_lock_edit.png
new file mode 100644
index 0000000..df5f923
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_route_lock_edit.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_route_set.png b/advtrains_interlocking/textures/at_il_route_set.png
new file mode 100644
index 0000000..3531420
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_route_set.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_route_start.png b/advtrains_interlocking/textures/at_il_route_start.png
new file mode 100644
index 0000000..dcb5160
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_route_start.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_routep_advance.png b/advtrains_interlocking/textures/at_il_routep_advance.png
new file mode 100644
index 0000000..d971e85
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_routep_advance.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_routep_end_here.png b/advtrains_interlocking/textures/at_il_routep_end_here.png
new file mode 100644
index 0000000..9dd3088
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_routep_end_here.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_routep_end_over.png b/advtrains_interlocking/textures/at_il_routep_end_over.png
new file mode 100644
index 0000000..e03198b
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_routep_end_over.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_routep_end_over_last.png b/advtrains_interlocking/textures/at_il_routep_end_over_last.png
new file mode 100644
index 0000000..f4fb1aa
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_routep_end_over_last.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_signal_asp_danger.png b/advtrains_interlocking/textures/at_il_signal_asp_danger.png
new file mode 100644
index 0000000..fca786d
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_asp_danger.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_signal_asp_free.png b/advtrains_interlocking/textures/at_il_signal_asp_free.png
new file mode 100644
index 0000000..e9d6e9c
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_asp_free.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_signal_asp_slow.png b/advtrains_interlocking/textures/at_il_signal_asp_slow.png
new file mode 100644
index 0000000..9242bb3
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_asp_slow.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_signal_ip.png b/advtrains_interlocking/textures/at_il_signal_ip.png
new file mode 100644
index 0000000..bf1618a
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_ip.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_signal_off.png b/advtrains_interlocking/textures/at_il_signal_off.png
new file mode 100644
index 0000000..f9b1f79
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_signal_off.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_tcb_marker.png b/advtrains_interlocking/textures/at_il_tcb_marker.png
new file mode 100644
index 0000000..3efc38a
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_tcb_marker.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_tcb_node.png b/advtrains_interlocking/textures/at_il_tcb_node.png
new file mode 100644
index 0000000..d5f615f
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_tcb_node.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_tool.png b/advtrains_interlocking/textures/at_il_tool.png
new file mode 100644
index 0000000..f6ce1cc
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_tool.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_turnout_cr_l.png b/advtrains_interlocking/textures/at_il_turnout_cr_l.png
new file mode 100644
index 0000000..fb79e3d
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_turnout_cr_l.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_turnout_cr_r.png b/advtrains_interlocking/textures/at_il_turnout_cr_r.png
new file mode 100644
index 0000000..e04dfbd
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_turnout_cr_r.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_turnout_free.png b/advtrains_interlocking/textures/at_il_turnout_free.png
new file mode 100644
index 0000000..5c83193
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_turnout_free.png
Binary files differ
diff --git a/advtrains_interlocking/textures/at_il_turnout_st.png b/advtrains_interlocking/textures/at_il_turnout_st.png
new file mode 100644
index 0000000..50d5ad5
--- /dev/null
+++ b/advtrains_interlocking/textures/at_il_turnout_st.png
Binary files differ
diff --git a/advtrains_interlocking/tool.lua b/advtrains_interlocking/tool.lua
new file mode 100644
index 0000000..5d38b3a
--- /dev/null
+++ b/advtrains_interlocking/tool.lua
@@ -0,0 +1,66 @@
+-- tool.lua
+-- Interlocking tool
+
+local ilrs = advtrains.interlocking.route
+
+minetest.register_craftitem("advtrains_interlocking:tool",{
+ description = "Interlocking tool\nright-click turnouts to inspect route locks",
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "at_il_tool.png",
+ wield_image = "at_il_tool.png",
+ stack_max = 1,
+ on_place = function(itemstack, placer, pointed_thing)
+ local pname = placer:get_player_name()
+ if not pname then
+ return
+ end
+ if not minetest.check_player_privs(pname, {interlocking=true}) then
+ minetest.chat_send_player(pname, "Insufficient privileges to use this!")
+ return
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ if advtrains.is_passive(pos) then
+ local form = "size[7,5]label[0.5,0.5;Route lock inspector]"
+ local pts = minetest.pos_to_string(pos)
+
+ local rtl = ilrs.has_route_lock(pts)
+
+ if rtl then
+ form = form.."label[0.5,1;Route locks currently put:\n"..rtl.."]"
+ form = form.."button_exit[0.5,3.5; 5,1;clear;Clear]"
+ else
+ form = form.."label[0.5,1;No route locks set]"
+ form = form.."button_exit[0.5,3.5; 5,1;emplace;Emplace manual lock]"
+ end
+
+ minetest.show_formspec(pname, "at_il_rtool_"..pts, form)
+ else
+ minetest.chat_send_player(pname, "Cannot use this here.")
+ return
+ end
+ end
+ end,
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, "interlocking") then
+ return
+ end
+ local pos
+ local pts = string.match(formname, "^at_il_rtool_(.+)$")
+ if pts then
+ pos = minetest.string_to_pos(pts)
+ end
+ if pos then
+ if advtrains.is_passive(pos) then
+ if fields.clear then
+ ilrs.remove_route_locks(pts)
+ end
+ if fields.emplace then
+ ilrs.add_manual_route_lock(pts, "Manual lock ("..pname..")")
+ end
+ end
+ end
+end)
diff --git a/advtrains_interlocking/train_sections.lua b/advtrains_interlocking/train_sections.lua
new file mode 100644
index 0000000..757f36a
--- /dev/null
+++ b/advtrains_interlocking/train_sections.lua
@@ -0,0 +1,199 @@
+-- train_related.lua
+-- Occupation of track sections - mainly implementation of train callbacks
+
+--[[
+Track section occupation is saved as follows
+
+In train:
+train.il_sections = {
+ [n] = {ts_id = <...> (origin = <sigd>)}
+}
+-- "origin" is the TCB (signal describer) the train initially entered this section
+
+In track section
+ts.trains = {
+ [n] = <train_id>
+}
+
+When any inconsistency is detected, we will assume the most restrictive setup.
+It will be possible to indicate a section "free" via the GUI.
+]]
+
+local ildb = advtrains.interlocking.db
+
+local sigd_equal = advtrains.interlocking.sigd_equal
+
+local function itexist(tbl, com)
+ for _,item in ipairs(tbl) do
+ if (item==com) then
+ return true
+ end
+ end
+ return false
+end
+local function itkexist(tbl, ikey, com)
+ for _,item in ipairs(tbl) do
+ if item[ikey] == com then
+ return true
+ end
+ end
+ return false
+end
+
+local function itremove(tbl, com)
+ local i=1
+ while i <= #tbl do
+ if tbl[i] == com then
+ table.remove(tbl, i)
+ else
+ i = i + 1
+ end
+ end
+end
+local function itkremove(tbl, ikey, com)
+ local i=1
+ while i <= #tbl do
+ if tbl[i][ikey] == com then
+ table.remove(tbl, i)
+ else
+ i = i + 1
+ end
+ end
+end
+
+local function setsection(tid, train, ts_id, ts, sigd)
+ -- train
+ if not train.il_sections then train.il_sections = {} end
+ if not itkexist(train.il_sections, "ts_id", ts_id) then
+ table.insert(train.il_sections, {ts_id = ts_id, origin = sigd})
+ end
+
+ -- ts
+ if not ts.trains then ts.trains = {} end
+ if not itexist(ts.trains, tid) then
+ table.insert(ts.trains, tid)
+ end
+
+ -- routes
+ local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
+
+ -- route setting - clear route state
+ if ts.route then
+ --atdebug(tid,"enters",ts_id,"examining Routestate",ts.route)
+ if not sigd_equal(ts.route.entry, sigd) then
+ -- Train entered not from the route. Locate origin and cancel route!
+ atwarn("Train",tid,"hit route",ts.route.rsn,"!")
+ advtrains.interlocking.route.cancel_route_from(ts.route.origin)
+ atwarn("Route was cancelled.")
+ else
+ -- train entered route regularily. Reset route and signal
+ tcbs.route_committed = nil
+ tcbs.route_comitted = nil -- TODO compatibility cleanup
+ tcbs.aspect = nil
+ tcbs.route_origin = nil
+ advtrains.interlocking.update_signal_aspect(tcbs)
+ if tcbs.signal and sigd_equal(ts.route.entry, ts.route.origin) then
+ if tcbs.route_auto and tcbs.routeset then
+ --atdebug("Resetting route (",ts.route.origin,")")
+ advtrains.interlocking.route.update_route(ts.route.origin, tcbs)
+ else
+ tcbs.routeset = nil
+ end
+ end
+ end
+ ts.route = nil
+ end
+ if tcbs.signal then
+ advtrains.interlocking.route.update_route(sigd, tcbs)
+ end
+end
+
+local function freesection(tid, train, ts_id, ts)
+ -- train
+ if not train.il_sections then train.il_sections = {} end
+ itkremove(train.il_sections, "ts_id", ts_id)
+
+ -- ts
+ if not ts.trains then ts.trains = {} end
+ itremove(ts.trains, tid)
+
+ if ts.route_post then
+ advtrains.interlocking.route.free_route_locks(ts_id, ts.route_post.locks)
+ if ts.route_post.next then
+ --this does nothing when the train went the right way, because
+ -- "route" info is already cleared.
+ advtrains.interlocking.route.cancel_route_from(ts.route_post.next)
+ end
+ ts.route_post = nil
+ end
+ -- This must be delayed, because this code is executed in-between a train step
+ -- TODO use luaautomation timers?
+ minetest.after(0, advtrains.interlocking.route.update_waiting, "ts", ts_id)
+end
+
+
+-- This is regular operation
+-- The train is on a track and drives back and forth
+
+-- This sets the section for both directions, to be failsafe
+advtrains.tnc_register_on_enter(function(pos, id, train, index)
+ local tcb = ildb.get_tcb(pos)
+ if tcb then
+ for connid=1,2 do
+ local ts = tcb[connid].ts_id and ildb.get_ts(tcb[connid].ts_id)
+ if ts then
+ setsection(id, train, tcb[connid].ts_id, ts, {p=pos, s=connid})
+ end
+ end
+ end
+end)
+
+
+-- this time, of course, only clear the backside (cp connid)
+advtrains.tnc_register_on_leave(function(pos, id, train, index)
+ local tcb = ildb.get_tcb(pos)
+ if tcb and train.path_cp[index] then
+ local connid = train.path_cp[index]
+ local ts = tcb[connid].ts_id and ildb.get_ts(tcb[connid].ts_id)
+ if ts then
+ freesection(id, train, tcb[connid].ts_id, ts)
+ end
+ end
+end)
+
+-- those callbacks are needed to account for created and removed trains (also regarding coupling)
+
+advtrains.te_register_on_create(function(id, train)
+ -- let's see what track sections we find here
+ local index = atround(train.index)
+ local pos = advtrains.path_get(train, index)
+ local ts_id, origin = ildb.get_ts_at_pos(pos)
+ if ts_id then
+ local ts = ildb.get_ts(ts_id)
+ if ts then
+ setsection(id, train, ts_id, ts, origin)
+ else
+ atwarn("ILDB corruption: TCB",origin," has invalid TS reference")
+ end
+ -- Make train a shunt move
+ train.is_shunt = true
+ elseif ts_id==nil then
+ atlog("Train",id,": Unable to determine whether to block a track section!")
+ else
+ --atdebug("Train",id,": Outside of interlocked area!")
+ end
+end)
+
+advtrains.te_register_on_remove(function(id, train)
+ if train.il_sections then
+ for idx, item in ipairs(train.il_sections) do
+
+ local ts = item.ts_id and ildb.get_ts(item.ts_id)
+
+ if ts and ts.trains then
+ itremove(ts.trains, id)
+ end
+ end
+ train.il_sections = nil
+ end
+end)
diff --git a/advtrains_interlocking/tsr_rail.lua b/advtrains_interlocking/tsr_rail.lua
new file mode 100644
index 0000000..f302540
--- /dev/null
+++ b/advtrains_interlocking/tsr_rail.lua
@@ -0,0 +1,66 @@
+-- tsr_rail.lua
+-- Point speed restriction rails
+-- Simple rail whose only purpose is to place a TSR on the position, as a temporary solution until the timetable system covers everything.
+-- This code resembles the code in lines/stoprail.lua
+
+local function updateform(pos)
+ local meta = minetest.get_meta(pos)
+ local pe = advtrains.encode_pos(pos)
+ local npr = advtrains.interlocking.npr_rails[pe] or 2
+
+ meta:set_string("infotext", "Point speed restriction: "..npr)
+ meta:set_string("formspec", "field[npr;Set point speed restriction:;"..npr.."]")
+end
+
+
+local adefunc = function(def, preset, suffix, rotation)
+ return {
+ after_place_node=function(pos)
+ updateform(pos)
+ end,
+ after_dig_node=function(pos)
+ local pe = advtrains.encode_pos(pos)
+ advtrains.interlocking.npr_rails[pe] = nil
+ end,
+ on_receive_fields = function(pos, formname, fields, player)
+ local pname = player:get_player_name()
+ if not minetest.check_player_privs(pname, {interlocking=true}) then
+ minetest.chat_send_player(pname, "Interlocking privilege required!")
+ return
+ end
+ if minetest.is_protected(pos, pname) then
+ minetest.chat_send_player(pname, "This rail is protected!")
+ minetest.record_protection_violation(pos, pname)
+ return
+ end
+ if fields.npr then
+ local pe = advtrains.encode_pos(pos)
+ advtrains.interlocking.npr_rails[pe] = tonumber(fields.npr)
+ updateform(pos)
+ end
+ end,
+ advtrains = {
+ on_train_approach = function(pos,train_id, train, index)
+ if train.path_cn[index] == 1 then
+ local pe = advtrains.encode_pos(pos)
+ local npr = advtrains.interlocking.npr_rails[pe] or 2
+ advtrains.lzb_add_checkpoint(train, index, npr, nil)
+ end
+ end,
+ },
+ }
+end
+
+
+if minetest.get_modpath("advtrains_train_track") ~= nil then
+ advtrains.register_tracks("default", {
+ nodename_prefix="advtrains_interlocking:dtrack_npr",
+ texture_prefix="advtrains_dtrack_npr",
+ models_prefix="advtrains_dtrack",
+ models_suffix=".b3d",
+ shared_texture="advtrains_dtrack_shared_npr.png",
+ description="Point Speed Restriction Rail",
+ formats={},
+ get_additional_definiton = adefunc,
+ }, advtrains.trackpresets.t_30deg_straightonly)
+end
diff --git a/advtrains_itrainmap/init.lua b/advtrains_itrainmap/init.lua
new file mode 100644
index 0000000..0443609
--- /dev/null
+++ b/advtrains_itrainmap/init.lua
@@ -0,0 +1,149 @@
+
+
+local map_def={
+ example = {
+ p1x=168,
+ p1z=530,
+ p2x=780,
+ p2z=1016,
+ background="itm_example.png",
+ },
+}
+
+local itm_cache={}
+local itm_pdata={}
+local itm_conf_mindia=0.1
+
+minetest.register_privilege("itm", { description = "Allows to display train map", give_to_singleplayer = true, default = false })
+
+local function create_map_form_with_bg(d)
+ local minx, minz, maxx, maxz = math.min(d.p1x, d.p2x), math.min(d.p1z, d.p2z), math.max(d.p1x, d.p2x), math.max(d.p1z, d.p2z)
+ local form_x, form_z=10,10
+ local edge_x, edge_z = form_x/(maxx-minx), form_z/(maxz-minz)
+ local len_x, len_z=math.max(edge_x, itm_conf_mindia), math.max(edge_z, itm_conf_mindia)
+ local form="size["..(form_x+edge_x)..","..(form_z+edge_z).."] background[0,0;0,0;"..d.background..";true] "
+ local lbl={}
+
+ for pts, tid in pairs(advtrains.detector.on_node) do
+ local pos=minetest.string_to_pos(pts)
+ form=form.."box["..(edge_x*(pos.x-minx))..","..(form_z-(edge_z*(pos.z-minz)))..";"..len_x..","..len_z..";red]"
+ lbl[sid(tid)]=pos
+ end
+
+ for t_id, xz in pairs(lbl) do
+ form=form.."label["..(edge_x*(xz.x-minx))..","..(form_x-(edge_z*(xz.z-minz)))..";"..t_id.."]"
+ end
+ return form
+end
+
+local function create_map_form(d)
+ if d.background then
+ return create_map_form_with_bg(d)
+ end
+
+ local minx, minz, maxx, maxz = math.min(d.p1x, d.p2x), math.min(d.p1z, d.p2z), math.max(d.p1x, d.p2x), math.max(d.p1z, d.p2z)
+ local form_x, form_z=10,10
+ local edge_x, edge_z = form_x/(maxx-minx), form_z/(maxz-minz)
+ local len_x, len_z=math.max(edge_x, itm_conf_mindia), math.max(edge_z, itm_conf_mindia)
+ local form="size["..(form_x+edge_x)..","..(form_z+edge_z).."]"
+ local lbl={}
+
+ for x,itx in pairs(itm_cache) do
+ if x>=minx and x<=maxx then
+ for z,y in pairs(itx) do
+ if z>=minz and z<=maxz then
+ local adn=advtrains.detector.get({x=x, y=y, z=z})
+ local color="gray"
+ if adn then
+ color="red"
+ lbl[sid(adn)]={x=x, z=z}
+ end
+ form=form.."box["..(edge_x*(x-minx))..","..(form_z-(edge_z*(z-minz)))..";"..len_x..","..len_z..";"..color.."]"
+ end
+ end
+ end
+ end
+ for t_id, xz in pairs(lbl) do
+ form=form.."label["..(edge_x*(xz.x-minx))..","..(form_x-(edge_z*(xz.z-minz)))..";"..t_id.."]"
+ end
+ return form
+end
+
+local function cache_ndb()
+ itm_cache={}
+ local ndb_nodes=advtrains.ndb.get_nodes()
+ for y, xzt in pairs(ndb_nodes) do
+ for x, zt in pairs(xzt) do
+ for z, _ in pairs(zt) do
+ if not itm_cache[x] then
+ itm_cache[x]={}
+ end
+ itm_cache[x][z]=y
+ end
+ end
+ end
+end
+
+minetest.register_chatcommand("itm", {
+ params="[x1 z1 x2 z2] or [mdef]",
+ description="Display advtrains train map of given area.\nFirst form:[x1 z1 x2 z2] - specify area directly.\nSecond form:[mdef] - Use a predefined map background(see init.lua)\nThird form: No parameters - use WorldEdit position markers.",
+ privs={itm=true},
+ func = function(name, param)
+ local mdef=string.match(param, "^(%S+)$")
+ if mdef then
+ local d=map_def[mdef]
+ if not d then
+ return false, "Map definiton not found: "..mdef
+ end
+ itm_pdata[name]=map_def[mdef]
+ minetest.show_formspec(name, "itrainmap", create_map_form(d))
+ return true, "Showing train map: "..mdef
+ end
+ local x1, z1, x2, z2=string.match(param, "^(%S+) (%S+) (%S+) (%S+)$")
+ if not (x1 and z1 and x2 and z2) then
+ if worldedit then
+ local wep1, wep2=worldedit.pos1[name], worldedit.pos2[name]
+ if wep1 and wep2 then
+ x1, z1, x2, z2=wep1.x, wep1.z, wep2.x, wep2.z
+ end
+ end
+ end
+ if not (x1 and z1 and x2 and z2) then
+ return false, "Invalid parameters and no WE positions set"
+ end
+ local d={p1x=x1, p1z=z1, p2x=x2, p2z=z2}
+ itm_pdata[name]=d
+ minetest.show_formspec(name, "itrainmap", create_map_form(d))
+ return true, "Showing ("..x1..","..z1..")-("..x2..","..z2..")"
+ end,
+})
+minetest.register_chatcommand("itm_cache_ndb", {
+ params="",
+ description="Cache advtrains node database again. Run when tracks changed.",
+ privs={itm=true},
+ func = function(name, param)
+ cache_ndb()
+ return true, "Done caching node database."
+ end,
+})
+
+local timer=0
+function advtrains_itm_mainloop(dtime)
+ timer=timer-math.min(dtime, 0.1)
+ if timer<=0 then
+ for pname,d in pairs(itm_pdata) do
+ minetest.show_formspec(pname, "itrainmap", create_map_form(d))
+ end
+ timer=2
+ end
+end
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname=="itrainmap" and fields.quit then
+ itm_pdata[player:get_player_name()]=nil
+ end
+end)
+
+function advtrains_itm_init()
+ --automatically run itm_cache_ndb
+ minetest.after(2, cache_ndb)
+end
diff --git a/advtrains_itrainmap/mod.conf b/advtrains_itrainmap/mod.conf
new file mode 100644
index 0000000..6468fc4
--- /dev/null
+++ b/advtrains_itrainmap/mod.conf
@@ -0,0 +1,6 @@
+name=advtrains_itrainmap
+title=Advanced Trains Interactive Train Map (currently broken)
+description=Map formspec showing tracks and trains
+author=orwell96
+
+depends=advtrains
diff --git a/advtrains_itrainmap/textures/itm_example.png b/advtrains_itrainmap/textures/itm_example.png
new file mode 100644
index 0000000..caf084a
--- /dev/null
+++ b/advtrains_itrainmap/textures/itm_example.png
Binary files differ
diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua
new file mode 100644
index 0000000..7b758bc
--- /dev/null
+++ b/advtrains_line_automation/init.lua
@@ -0,0 +1,47 @@
+-- Advtrains line automation system
+
+advtrains.lines = {
+ -- [station code] = {name=..., owner=...}
+ stations = {},
+
+ --[[ [new pos hash] = {
+ stn = <station code>,
+ track = <platform identifier>,
+ doors = <door side L,R,C>
+ wait = <least wait time>
+ reverse = <boolean>
+ signal = <position of signal that is the "exit signal" for this platform>
+ }]]
+ stops = {},
+}
+
+
+local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM
+
+dofile(modpath.."railwaytime.lua")
+dofile(modpath.."scheduler.lua")
+dofile(modpath.."stoprail.lua")
+
+
+function advtrains.lines.load(data)
+ if data then
+ advtrains.lines.stations = data.stations or {}
+ advtrains.lines.stops = data.stops or {}
+ advtrains.lines.rwt.set_time(data.rwt_time)
+ advtrains.lines.sched.load(data.scheduler_queue)
+ end
+end
+
+function advtrains.lines.save()
+ return {
+ stations = advtrains.lines.stations,
+ stops = advtrains.lines.stops,
+ rwt_time = advtrains.lines.rwt.get_time(),
+ scheduler_queue = advtrains.lines.sched.save()
+ }
+end
+
+function advtrains.lines.step(dtime)
+ advtrains.lines.rwt.step(dtime)
+ advtrains.lines.sched.run()
+end
diff --git a/advtrains_line_automation/mod.conf b/advtrains_line_automation/mod.conf
new file mode 100644
index 0000000..e9851c8
--- /dev/null
+++ b/advtrains_line_automation/mod.conf
@@ -0,0 +1,7 @@
+name=advtrains_line_automation
+title=Advanced Trains Line Automation
+description=Tools for automatic train lines
+author=orwell96
+
+depends=advtrains_interlocking
+optional_depends=advtrains_train_track
diff --git a/advtrains_line_automation/railwaytime.lua b/advtrains_line_automation/railwaytime.lua
new file mode 100644
index 0000000..258009e
--- /dev/null
+++ b/advtrains_line_automation/railwaytime.lua
@@ -0,0 +1,308 @@
+-- railwaytime.lua
+-- Advtrains uses a desynchronized time for train movement. Everything is counted relative to this time counter.
+-- The advtrains-internal time is in no way synchronized to the real-life time, due to:
+-- - Lag
+-- - Server stops/restarts
+-- However, this means that implementing a "timetable" system using the "real time" is not practical. Therefore,
+-- we introduce a custom time system, the RWT(Railway Time), which has nothing to do with RLT(Real-Life Time)
+-- RWT has a time cycle of 1 hour. This should be sufficient for most train lines that will ever be built in Minetest.
+-- A RWT looks like this: 37;25
+-- The ; is to distinguish it from a normal RLT (which has colons e.g. 12:34:56). Left number is minutes, right number is seconds.
+-- The minimum RWT is 00;00, the maximum is 59;59.
+-- It is OK to leave one places out at either end, esp. when writing relative times, such as:
+-- 43;3 22;0 2;30 0;10 ;10
+-- Those places are then filled with zeroes. Indeed, ";" would be valid for 00;00 .
+
+-- There is an "adapt mode", which was proposed by gpcf, and results in RWT automatically adapting itself to real-world time.
+-- It works by shifting the minute/second after the realtime minute/second, adjusting the cycle value as needed.
+
+-- Using negative times is discouraged. If you need a negative time, you may insert a minus (-) ONLY in the "c" place
+
+--[[
+1;23;45 = {
+ s=45,
+ m=23,
+ c=1, -- Cycle(~hour), not displayed most time
+}
+
+Railway times can exist in 3 forms:
+- as table (see above)
+- as string (like "12;34")
+- as number (of seconds)
+
+Forms are automagically converted as needed by the converter functions to_*
+To be sure a rwt is in the required form, explicitly use a converter.
+
+]]
+
+local rwt = {}
+
+--Time Stamp (Seconds since start of world)
+local e_time = 0
+local e_has_loaded = false
+
+local setting_rwt_real = minetest.settings:get("advtrains_lines_rwt_realtime")
+if setting_rwt_real=="" then
+ setting_rwt_real = "independent"
+end
+
+local e_last_epoch -- last real-time timestamp
+
+-- Advance RWT to match minute/second to the current real-world time
+-- only accounts for the minute/second part, leaves hour/cycle untouched
+local function adapt_real_time()
+ local datetab = os.date("*t")
+ local real_sectotal = 60*datetab.min + datetab.sec
+
+ local rwttab = rwt.now()
+ local rwt_sectotal = 60*rwttab.m + rwttab.s
+
+ --calculate the difference and take it %3600 (seconds/hour) to always move forward
+ local secsfwd = (real_sectotal - rwt_sectotal) % 3600
+
+ atlog("[lines][rwt] Skipping",secsfwd,"seconds forward to sync rwt (",rwt.to_string(rwttab),") to real time (",os.date("%H:%M:%S"),")")
+
+ e_time = e_time + secsfwd
+end
+
+function rwt.set_time(t)
+ e_time = t or 0
+ if setting_rwt_real == "adapt_real" then
+ adapt_real_time()
+ end
+ atlog("[lines][rwt] Initialized railway time: ",rwt.to_string(e_time))
+ e_last_epoch = os.time()
+
+ e_has_loaded = true
+end
+
+function rwt.get_time()
+ return e_time
+end
+
+function rwt.step(dt)
+ if not e_has_loaded then
+ rwt.set_time(0)
+ end
+
+ if setting_rwt_real=="independent" then
+ -- Regular stepping with dtime
+ e_time = e_time + dt
+ else
+ -- advance with real-world time
+ local diff = os.time() - e_last_epoch
+ e_last_epoch = os.time()
+
+ if diff>0 then
+ e_time = e_time + diff
+ end
+ end
+end
+
+function rwt.now()
+ return rwt.to_table(e_time)
+end
+
+function rwt.new(c, m, s)
+ return {
+ c = c or 0,
+ m = m or 0,
+ s = s or 0
+ }
+end
+function rwt.copy(rwtime)
+ local rwtimet = rwt.to_table(rwtime)
+ return {
+ c = rwtimet.c or 0,
+ m = rwtimet.m or 0,
+ s = rwtimet.s or 0
+ }
+end
+
+function rwt.to_table(rwtime)
+ if type(rwtime) == "table" then
+ return rwtime
+ elseif type(rwtime) == "string" then
+ return rwt.parse(rwtime)
+ elseif type(rwtime) == "number" then
+ local res = {}
+ local seconds = atfloor(rwtime)
+ res.s = seconds % 60
+ local minutes = atfloor(seconds/60)
+ res.m = minutes % 60
+ res.c = atfloor(minutes/60)
+ return res
+ end
+end
+
+function rwt.to_secs(rwtime, c_over)
+ local res = rwtime
+ if type(rwtime) == "string" then
+ res = rwt.parse(rwtime)
+ elseif type(rwtime) == "number" then
+ return rwtime
+ end
+ if type(res)=="table" then
+ return (c_over or res.c)*60*60 + res.m*60 + res.s
+ end
+end
+
+function rwt.to_string(rwtime_p, no_cycle)
+ local rwtime = rwt.to_table(rwtime_p)
+ if rwtime.c~=0 and not no_cycle then
+ return string.format("%d;%02d;%02d", rwtime.c, rwtime.m, rwtime.s)
+ else
+ return string.format("%02d;%02d", rwtime.m, rwtime.s)
+ end
+end
+
+---
+
+local function v_n(str, cpl)
+ if not str then return nil end
+ if str == "" then
+ return 0
+ end
+ local n = tonumber(str)
+ if not cpl and (n<0 or n>59) then
+ return nil
+ end
+ return n
+end
+
+function rwt.parse(str)
+ --atdebug("parse",str)
+ --3-value form
+ local str_c, str_m, str_s = string.match(str, "^(%-?%d?%d?);(%d%d);(%d?%d?)$")
+ if str_c and str_m and str_s then
+ --atdebug("3v",str_c, str_m, str_s)
+ local c, m, s = v_n(str_c, true), v_n(str_m), v_n(str_s)
+ if c and m and s then
+ return rwt.new(c,m,s)
+ end
+ end
+ --2-value form
+ local str_m, str_s = string.match(str, "^(%d?%d?);(%d?%d?)$")
+ if str_m and str_s then
+ --atdebug("2v",str_m, str_s)
+ local m, s = v_n(str_m), v_n(str_s)
+ if m and s then
+ return rwt.new(0,m,s)
+ end
+ end
+end
+
+---
+
+function rwt.add(t1, t2)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2)
+ return rwt.to_table(t1s + t2s)
+end
+
+-- How many seconds FROM t1 TO t2
+function rwt.diff(t1, t2)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2)
+ return t2s - t1s
+end
+
+-- Subtract t2 from t1 (inverted argument order compared to diff())
+function rwt.sub(t1, t2)
+ return rwt.to_table(rwt.diff(t2, t1))
+end
+
+-- Adjusts t2 by thresh and then returns time from t1 to t2
+function rwt.adj_diff(t1, t2, thresh)
+ local newc = rwt.adjust_cycle(t2, thresh, t1)
+ local t1s = rwt.to_secs(t1)
+ local t2s = rwt.to_secs(t2, newc)
+ return t1s - t2s
+end
+
+
+
+-- Threshold values
+-- "reftime" is the time to which this is made relative and defaults to now.
+rwt.CA_FUTURE = 60*60 - 1 -- Selected so that time lies at or in the future of reftime (at nearest point in time)
+rwt.CA_FUTURES = 60*60 -- Same, except when times are equal, advances one full cycle
+rwt.CA_PAST = 0 -- Selected so that time lies at or in the past of reftime
+rwt.CA_PASTS = -1 -- Same, except when times are equal, goes back one full cycle
+rwt.CA_CENTER = 30*60 -- If time is within past 30 minutes of reftime, selected as past, else selected as future.
+
+-- Adjusts the "cycle" value of a railway time to be in some relation to reftime.
+-- Returns new cycle
+function rwt.adjust_cycle(rwtime, reftime_p, thresh)
+ local reftime = reftime_p or rwt.now()
+
+ local reftimes = rwt.to_secs(reftime)
+
+ local rwtimes = rwt.to_secs(rwtime, 0)
+ local timeres = reftimes + thresh - rwtimes
+ local cycles = atfloor(timeres / (60*60))
+
+ return cycles
+end
+
+function rwt.adjust(rwtime, reftime, thresh)
+ local cp = rwt.copy(rwtime)
+ cp.c = rwt.adjust_cycle(rwtime, reftime, thresh)
+ return cp
+end
+
+-- Useful for departure times: returns time (in seconds)
+-- until the next (adjusted FUTURE) occurence of deptime is reached
+-- in this case, rwtime is used as reftime and deptime should lie in the future of rwtime
+-- rwtime defaults to NOW
+function rwt.get_time_until(deptime, rwtime_p)
+ local rwtime = rwtime_p or rwt.now()
+ return rwt.adj_diff(rwtime, deptime, rwt.CA_FUTURE)
+end
+
+
+-- Helper functions for handling "repeating times" (rpt)
+-- Those are generic declarations for time intervals like "every 5 minutes", with an optional offset
+-- ( /02;00-00;45 in timetable syntax
+
+-- Get the time (in seconds) until the next time this rpt occurs
+function rwt.time_to_next_rpt(rwtime, rpt_interval, rpt_offset)
+ local rpti_s = rwt.to_secs(rpt_interval)
+
+ return (rpti_s - rwt.time_from_last_rpt(rwtime, rpti_s, rpt_offset)) % rpti_s
+ -- Modulo is just there to clip a false value of rpti_s to 0
+end
+
+
+-- Get the time (in seconds) since the last time this rpt occured
+function rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_secs(rwtime)
+ local rpti_s = rwt.to_secs(rpt_interval)
+ local rpto_s = rwt.to_secs(rpt_offset)
+
+ return ((rwtime_s - rpto_s) % rpti_s)
+end
+
+-- From rwtime, get the next time that is divisible by rpt_interval offset by rpt_offset
+function rwt.next_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_secs(rwtime)
+ local rpti_s = rwt.to_secs(rpt_interval)
+ local time_from_last = rwt.time_from_last_rpt(rwtime_s, rpti_s, rpt_offset)
+
+ local res_s = rwtime_s - time_from_last + rpti_s
+
+ return rwt.to_table(res_s)
+end
+
+-- from rwtime, get the last time that this rpt matched (which is actually just next_rpt - rpt_offset
+function rwt.last_rpt(rwtime, rpt_interval, rpt_offset)
+ local rwtime_s = rwt.to_sec(rwtime)
+ local rpti_s = rwt.to_sec(rpt_interval)
+ local time_from_last = rwt.time_from_last_rpt(rwtime, rpt_interval, rpt_offset)
+
+ local res_s = rwtime_s - time_from_last
+
+ return rwt.to_table(res_s)
+end
+
+
+advtrains.lines.rwt = rwt
diff --git a/advtrains_line_automation/readme.txt b/advtrains_line_automation/readme.txt
new file mode 100644
index 0000000..3280ce9
--- /dev/null
+++ b/advtrains_line_automation/readme.txt
@@ -0,0 +1,5 @@
+== advtrains_line_automation
+This mod provides an extension to the interlocking system which allows to automatically operate trains on train lines.
+
+This extension makes use of the table
+advtrains.lines \ No newline at end of file
diff --git a/advtrains_line_automation/scheduler.lua b/advtrains_line_automation/scheduler.lua
new file mode 100644
index 0000000..6025b02
--- /dev/null
+++ b/advtrains_line_automation/scheduler.lua
@@ -0,0 +1,133 @@
+-- scheduler.lua
+-- Implementation of a Railway time schedule queue
+-- In contrast to the LuaATC interrupt queue, this one can handle many different
+-- event receivers. This is done by registering a callback with the scheduler
+
+local ln = advtrains.lines
+local sched = {}
+
+local UNITS_THRESH = 10
+local MAX_PER_ITER = 10
+
+local callbacks = {}
+
+-- Register a handler callback to handle scheduler items.
+-- e - a handler identifier (corresponds to "handler" in enqueue() )
+-- func - a function(evtdata) to be executed when a schedule item expires
+-- evtdata - arbitrary data that has been passed into enqueue()
+function sched.register_callback(e, func)
+ callbacks[e] = func
+end
+
+--[[
+{
+ t = <railway time in seconds>
+ e = <handler callback>
+ d = <data table>
+ u = <unit identifier>
+}
+The "unit identifier" is there to prevent schedule overflows. It can be, for example, the position hash
+of a node or a train ID. If the number of schedules for a unit exceeds UNITS_THRESH, further schedules are
+blocked.
+]]--
+local queue = {}
+
+local units_cnt = {}
+
+function sched.load(data)
+ if data then
+ for i,elem in ipairs(data) do
+ table.insert(queue, elem)
+ units_cnt[elem.u] = (units_cnt[elem.u] or 0) + 1
+ end
+ atlog("[lines][scheduler] Loaded the schedule queue,",#data,"items.")
+ end
+end
+function sched.save()
+ return queue
+end
+
+function sched.run()
+ local ctime = ln.rwt.get_time()
+ local cnt = 0
+ local ucn, elem
+ while cnt <= MAX_PER_ITER do
+ elem = queue[1]
+ if elem and elem.t <= ctime then
+ table.remove(queue, 1)
+ if callbacks[elem.e] then
+ -- run it
+ callbacks[elem.e](elem.d)
+ else
+ atwarn("[lines][scheduler] No callback to handle schedule",elem)
+ end
+ cnt=cnt+1
+ ucn = units_cnt[elem.u]
+ if ucn and ucn>0 then
+ units_cnt[elem.u] = ucn - 1
+ end
+ else
+ break
+ end
+ end
+end
+
+-- Enqueue a new scheduled item to be executed at "rwtime"
+-- handler: a string identifying the handler to use (registered with sched.register_callback())
+-- evtdata: Arbitrary Lua data to be passed to the handler callback
+-- unitid: An arbitrary string uniquely identifying the thing that is issuing this enqueue.
+-- used to prevent expotentially growing "scheduler bombs"
+-- unitlim: Custom override for UNITS_THRESH (see there)
+function sched.enqueue(rwtime, handler, evtdata, unitid, unitlim)
+ local qtime = ln.rwt.to_secs(rwtime)
+ assert(type(handler)=="string")
+ assert(type(unitid)=="string")
+ assert(type(unitlim)=="number")
+
+ local cnt=1
+ local ucn, elem
+
+ ucn = (units_cnt[unitid] or 0)
+ local ulim=(unitlim or UNITS_THRESH)
+ if ucn >= ulim then
+ atlog("[lines][scheduler] discarding enqueue for",handler,"(limit",ulim,") because unit",unitid,"has already",ucn,"schedules enqueued")
+ return false
+ end
+
+ while true do
+ elem = queue[cnt]
+ if not elem or elem.t > qtime then
+ table.insert(queue, cnt, {
+ t=qtime,
+ e=handler,
+ d=evtdata,
+ u=unitid,
+ })
+ units_cnt[unitid] = ucn + 1
+ return true
+ end
+ cnt = cnt+1
+ end
+end
+
+-- See enqueue(). Same meaning, except that rwtime is relative to now.
+function sched.enqueue_in(rwtime, handler, evtdata, unitid, unitlim)
+ local ctime = ln.rwt.get_time()
+ local rwtime_s = ln.rwt.to_secs(rwtime)
+ sched.enqueue(ctime + rwtime_s, handler, evtdata, unitid, unitlim)
+end
+
+-- Discards all schedules for unit "unitid" (removes them from the queue)
+function sched.discard_all(unitid)
+ local i = 1
+ while i<=#queue do
+ if queue[i].u == unitid then
+ table.remove(queue,i)
+ else
+ i=i+1
+ end
+ end
+ units_cnt[unitid] = 0
+end
+
+ln.sched = sched
diff --git a/advtrains_line_automation/settingtypes.txt b/advtrains_line_automation/settingtypes.txt
new file mode 100644
index 0000000..ea3f66e
--- /dev/null
+++ b/advtrains_line_automation/settingtypes.txt
@@ -0,0 +1,6 @@
+# This controls how the Railway Time relates to real-world time:
+# *independent - RWT counts independent of real time synchronized to ingame steps. Recommended for singleplayer and ad-hoc servers.
+# *follow_real - RWT is independent of real time, but counts up in real-world time speed
+# *adapt_real - RWT adapts its minute and second to real-world time. When the server is stopped and restarted, this results in a "time jump".
+# Time will jump for 1 cycle (59;59) at maximum.
+advtrains_lines_rwt_realtime (RWT real time adaption) enum independent independent,follow_real,adapt_real \ No newline at end of file
diff --git a/advtrains_line_automation/stoprail.lua b/advtrains_line_automation/stoprail.lua
new file mode 100644
index 0000000..55a4785
--- /dev/null
+++ b/advtrains_line_automation/stoprail.lua
@@ -0,0 +1,225 @@
+-- stoprail.lua
+-- adds "stop rail". Recognized by lzb. (part of behavior is implemented there)
+
+
+local function to_int(n)
+ --- Disallow floating-point numbers
+ local k = tonumber(n)
+ if k then
+ return math.floor(k)
+ end
+end
+
+local function updatemeta(pos)
+ local meta = minetest.get_meta(pos)
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ meta:set_string("infotext", "Error")
+ end
+
+ meta:set_string("infotext", "Stn. "..stdata.stn.." T. "..stdata.track)
+end
+
+local door_dropdown = {L=1, R=2, C=3}
+local door_dropdown_rev = {Right="R", Left="L", Closed="C"}
+
+local function show_stoprailform(pos, player)
+ local pe = advtrains.encode_pos(pos)
+ local pname = player:get_player_name()
+ if minetest.is_protected(pos, pname) then
+ minetest.chat_send_player(pname, "Position is protected!")
+ return
+ end
+
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ advtrains.lines.stops[pe] = {
+ stn="", track="", doors="R", wait=10, ars={default=true}, ddelay=1,speed="M"
+ }
+ stdata = advtrains.lines.stops[pe]
+ end
+
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or ""
+ if not stdata.ddelay then
+ stdata.ddelay = 1
+ end
+ if not stdata.speed then
+ stdata.speed = "M"
+ end
+
+ local form = "size[8,7]"
+ form = form.."style[stn,ars;font=mono]"
+ form = form.."field[0.8,0.8;2,1;stn;"..attrans("Station Code")..";"..minetest.formspec_escape(stdata.stn).."]"
+ form = form.."field[2.8,0.8;5,1;stnname;"..attrans("Station Name")..";"..minetest.formspec_escape(stnname).."]"
+ form = form.."field[0.80,2.0;1.75,1;ddelay;"..attrans("Door Delay")..";"..minetest.formspec_escape(stdata.ddelay).."]"
+ form = form.."field[2.55,2.0;1.75,1;speed;"..attrans("Dep. Speed")..";"..minetest.formspec_escape(stdata.speed).."]"
+ form = form.."field[4.30,2.0;1.75,1;track;"..attrans("Track")..";"..minetest.formspec_escape(stdata.track).."]"
+ form = form.."field[6.05,2.0;1.75,1;wait;"..attrans("Stop Time")..";"..stdata.wait.."]"
+ form = form.."label[0.5,2.6;"..attrans("Door Side").."]"
+ form = form.."dropdown[0.51,3.0;2;doors;Left,Right,Closed;"..door_dropdown[stdata.doors].."]"
+ form = form.."checkbox[3.00,2.7;reverse;"..attrans("Reverse train")..";"..(stdata.reverse and "true" or "false").."]"
+ form = form.."checkbox[3.00,3.1;kick;"..attrans("Kick out passengers")..";"..(stdata.kick and "true" or "false").."]"
+ form = form.."textarea[0.8,4.2;7,2;ars;Trains stopping here (ARS rules);"..advtrains.interlocking.ars_to_text(stdata.ars).."]"
+ form = form.."button[0.5,6;7,1;save;"..attrans("Save").."]"
+
+ minetest.show_formspec(pname, "at_lines_stop_"..pe, form)
+end
+local tmp_checkboxes = {}
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pname = player:get_player_name()
+ local pe = string.match(formname, "^at_lines_stop_(............)$")
+ local pos = advtrains.decode_pos(pe)
+ if pos then
+ if minetest.is_protected(pos, pname) then
+ minetest.chat_send_player(pname, "Position is protected!")
+ return
+ end
+
+ local stdata = advtrains.lines.stops[pe]
+ if not tmp_checkboxes[pe] then
+ tmp_checkboxes[pe] = {}
+ end
+ if fields.kick then -- handle checkboxes due to MT's weird handling
+ tmp_checkboxes[pe].kick = (fields.kick == "true")
+ end
+ if fields.reverse then
+ tmp_checkboxes[pe].reverse = (fields.reverse == "true")
+ end
+ if fields.save then
+ if fields.stn and stdata.stn ~= fields.stn and fields.stn ~= "" then
+ local stn = advtrains.lines.stations[fields.stn]
+ if stn then
+ if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
+ stdata.stn = fields.stn
+ else
+ minetest.chat_send_player(pname, "Station code '"..fields.stn.."' does already exist and is owned by "..stn.owner)
+ show_stoprailform(pos,player)
+ return
+ end
+ else
+ advtrains.lines.stations[fields.stn] = {name = fields.stnname, owner = pname}
+ stdata.stn = fields.stn
+ end
+ end
+ local stn = advtrains.lines.stations[stdata.stn]
+ if stn and fields.stnname and fields.stnname ~= stn.name then
+ if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then
+ stn.name = fields.stnname
+ else
+ minetest.chat_send_player(pname, "Not allowed to edit station name, owned by "..stn.owner)
+ end
+ end
+
+ -- dropdowns
+ if fields.doors then
+ stdata.doors = door_dropdown_rev[fields.doors] or "C"
+ end
+
+ if fields.track then
+ stdata.track = fields.track
+ end
+ if fields.wait then
+ stdata.wait = to_int(fields.wait) or 10
+ end
+
+ if fields.ars then
+ stdata.ars = advtrains.interlocking.text_to_ars(fields.ars)
+ end
+
+ if fields.ddelay then
+ stdata.ddelay = to_int(fields.ddelay) or 1
+ end
+ if fields.speed then
+ stdata.speed = to_int(fields.speed) or "M"
+ end
+
+ for k,v in pairs(tmp_checkboxes[pe]) do --handle checkboxes
+ stdata[k] = v or nil
+ end
+ tmp_checkboxes[pe] = nil
+ --TODO: signal
+ updatemeta(pos)
+ show_stoprailform(pos, player)
+ end
+ end
+
+end)
+
+
+local adefunc = function(def, preset, suffix, rotation)
+ return {
+ after_place_node=function(pos)
+ local pe = advtrains.encode_pos(pos)
+ advtrains.lines.stops[pe] = {
+ stn="", track="", doors="R", wait=10
+ }
+ updatemeta(pos)
+ end,
+ after_dig_node=function(pos)
+ local pe = advtrains.encode_pos(pos)
+ advtrains.lines.stops[pe] = nil
+ end,
+ on_rightclick = function(pos, node, player)
+ show_stoprailform(pos, player)
+ end,
+ advtrains = {
+ on_train_approach = function(pos,train_id, train, index, has_entered)
+ if has_entered then return end -- do not stop again!
+ if train.path_cn[index] == 1 then
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if stdata and stdata.stn then
+
+ --TODO REMOVE AFTER SOME TIME (only migration)
+ if not stdata.ars then
+ stdata.ars = {default=true}
+ end
+ if stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then
+ advtrains.lzb_add_checkpoint(train, index, 2, nil)
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or "Unknown Station"
+ train.text_inside = "Next Stop:\n"..stnname
+ advtrains.interlocking.ars_set_disable(train, true)
+ end
+ end
+ end
+ end,
+ on_train_enter = function(pos, train_id, train, index)
+ if train.path_cn[index] == 1 then
+ local pe = advtrains.encode_pos(pos)
+ local stdata = advtrains.lines.stops[pe]
+ if not stdata then
+ return
+ end
+
+ if stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then
+ local stn = advtrains.lines.stations[stdata.stn]
+ local stnname = stn and stn.name or "Unknown Station"
+
+ -- Send ATC command and set text
+ advtrains.atc.train_set_command(train, "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..stdata.wait.." OC "..(stdata.reverse and "R" or "").."D"..(stdata.ddelay or 1) .. " A1 S" ..(stdata.speed or "M"), true)
+ train.text_inside = stnname
+ if tonumber(stdata.wait) then
+ minetest.after(tonumber(stdata.wait), function() train.text_inside = "" end)
+ end
+ end
+ end
+ end
+ },
+ }
+end
+
+if minetest.get_modpath("advtrains_train_track") ~= nil then
+ advtrains.register_tracks("default", {
+ nodename_prefix="advtrains_line_automation:dtrack_stop",
+ texture_prefix="advtrains_dtrack_stop",
+ models_prefix="advtrains_dtrack",
+ models_suffix=".b3d",
+ shared_texture="advtrains_dtrack_shared_stop.png",
+ description="Station/Stop Rail",
+ formats={},
+ get_additional_definiton = adefunc,
+ }, advtrains.trackpresets.t_30deg_straightonly)
+end
diff --git a/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png
new file mode 100644
index 0000000..b6629cf
--- /dev/null
+++ b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png
Binary files differ
diff --git a/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png b/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png
new file mode 100644
index 0000000..0d1c769
--- /dev/null
+++ b/advtrains_line_automation/textures/advtrains_dtrack_stop_placer.png
Binary files differ
diff --git a/advtrains_luaautomation/README.md b/advtrains_luaautomation/README.md
new file mode 100644
index 0000000..683e45c
--- /dev/null
+++ b/advtrains_luaautomation/README.md
@@ -0,0 +1,446 @@
+
+# Advtrains - Lua Automation features
+
+This mod offers components that run LUA code and interface with each other through a global environment. It makes complex automated railway systems possible. The mod is sometimes abbreviated as 'LuaATC' or 'atlatc'. This stands for AdvTrainsLuaATC. This short name has been chosen for user convenience, since the name of this mod ('advtrains_luaautomation') is very long.
+
+A probably more complete documentation of LuaATC is found on the [Advtrains Wiki](http://advtrains.de/wiki/doku.php?id=usage:atlatc:start)
+
+## Privileges
+To perform any operations using this mod (except executing operation panels), players need the "atlatc" privilege.
+This privilege should never be granted to anyone except trusted administrators. Even though the LUA environment is sandboxed, it is still possible to DoS the server by coding infinite loops or requesting expotentially growing interrupts.
+
+## Environments
+
+Each active component is assigned to an environment where all atlac data is held. Components in different environments can't inferface with each other.
+This system allows multiple independent automation systems to run simultaneously without polluting each other's environment.
+
+ - `/env_create <env_name>`:
+Create environment with the given name. To be able to do anything, you first need to create an environment. Choose the name wisely, you can't change it afterwards without deleting the environment and starting again.
+
+ - `/env_setup <env_name>`:
+Invoke the form to edit the environment's initialization code. For more information, see the section on active components. You can also delete an environment from here.
+
+ - `/env_subscribe <env_name>`, `/env_unsubscribe <env_name>`:
+Subscribe or unsubscribe from log/error messages originating from this environment
+
+ - `/env_subscriptions [env_name]`:
+List your subscriptions or players subscribed to an environment.
+
+
+## Functions and variables
+### General Functions and Variables
+The following standard Lua libraries are available:
+ - `string`
+ - `math`
+ - `table`
+ - `os`
+
+The following standard Lua functions are available:
+ - `assert`
+ - `error`
+ - `ipairs`
+ - `pairs`
+ - `next`
+ - `select`
+ - `tonumber`
+ - `tostring`
+ - `type`
+ - `unpack`
+
+Any attempt to overwrite the predefined values results in an error.
+
+### LuaAutomation Global Variables
+ - `S`
+The variable 'S' contains a table which is shared between all components of the environment. Its contents are persistent over server restarts. May not contain functions, every other value is allowed.
+
+ - `F`
+The variable 'F' also contains a table which is shared between all components of the environment. Its contents are discarded on server shutdown or when the init code gets re-run. Every data type is allowed, even functions.
+The purpose of this table is not to save data, but to provide static value and function definitions. The table should be populated by the init code.
+
+### LuaAutomation Global Functions
+> Note: in the following functions, all parameters named `pos` designate a position. You can use the following:
+> - a default Minetest position vector (eg. {x=34, y=2, z=-18})
+> - the POS(34,2,-18) shorthand below.
+> - A string, the passive component name. See 'passive component naming'.
+
+
+
+ - `POS(x,y,z)`
+Shorthand function to create a position vector {x=?, y=?, z=?} with less characters.
+
+ - `getstate(pos)`
+Get the state of the passive component at position `pos`.
+
+ - `setstate(pos, newstate)`
+Set the state of the passive component at position `pos`.
+
+ - `is_passive(pos)`
+Checks whether there is a passive component at the position pos (and/or whether a passive component with this name exists)
+
+ - `interrupt(time, message)`
+Cause LuaAutomation to trigger an `int` event on this component after the given time in seconds with the specified `message` field. `message` can be of any Lua data type. Returns true. *Not available in init code.*
+
+ - `interrupt_safe(time, message)`
+Like `interrupt()`, but does not add an interrupt and returns false when an interrupt (of any type) is already present for this component. Returns true when interrupt was successfully added.
+
+ - `interrupt_pos(pos, message)`
+Immediately trigger an `ext_int` event on the active component at position pos. `message` is like in interrupt(). Use with care, or better **_don't use_**! Incorrect use can result in **_expotential growth of interrupts_**.
+
+ - `clear_interrupts()`
+Removes any pending interrupts of this node.
+
+ - `digiline_send(channel, message)`
+Make this active component send a digiline message on the specified channel.
+Not available in init code.
+
+ - `atc_send_to_train(<train_id>, <atc_command>)`
+ Sends the specified ATC command to the train specified by its train id. This happens regardless of where the train is in the world, and can be used to remote-control trains. Returns true on success. If the train ID does not exist, returns false and does nothing. See [atc_command.txt](../atc_command.txt) for the ATC command syntax.
+
+#### Interlocking Route Management Functions
+If `advtrains_interlocking` is enabled, the following aditional functions can be used:
+
+ - `can_set_route(pos, route_name)`
+Returns whether it is possible to set the route designated by route_name from the signal at pos.
+
+ - `set_route(pos, route_name)`
+Requests the given route from the signal at pos. Has the same effect as clicking "Set Route" in the signalling dialog.
+
+ - `cancel_route(pos)`
+Cancels the route that is set from the signal at pos. Has the same effect as clicking "Cancel Route" in the signalling dialog.
+
+ - `get_aspect(pos)`
+Returns the signal aspect of the signal at pos. A signal aspect has the following format:
+```lua
+asp = {
+ main = <int speed>,
+ -- Main signal aspect, tells state and permitted speed of next section
+ -- 0 = section is blocked
+ -- >0 = section is free, speed limit is this value
+ -- -1 = section is free, maximum speed permitted
+ -- false = Signal doesn't provide main signal information, retain current speed limit.
+ shunt = <boolean>,
+ -- Whether train may proceed as shunt move, on sight
+ -- main aspect takes precedence over this
+ -- When main==0, train switches to shunt move and is restricted to speed 8
+ proceed_as_main = <boolean>,
+ -- If an approaching train is a shunt move and 'shunt' is false,
+ -- the train may proceed as a train move under the "main" aspect
+ -- if the main aspect permits it (i.e. main!=0)
+ -- If this is not set, shunt moves are NOT allowed to switch to
+ -- a train move, and must stop even if "main" would permit passing.
+ -- This is intended to be used for "Halt for shunt moves" signs.
+
+ dst = <int speed>,
+ -- Distant signal aspect, tells state and permitted speed of the section after next section
+ -- The character of these information is purely informational
+ -- At this time, this field is not actively used
+ -- 0 = section is blocked
+ -- >0 = section is free, speed limit is this value
+ -- -1 = section is free, maximum speed permitted
+ -- false = Signal doesn't provide distant signal information.
+
+ -- the character of call_on and dead_end is purely informative
+ call_on = <boolean>, -- Call-on route, expect train in track ahead (not implemented yet)
+ dead_end = <boolean>, -- Route ends on a dead end (e.g. bumper) (not implemented yet)
+
+ w_speed = <integer>,
+ -- "Warning speed restriction". Supposed for short-term speed
+ -- restrictions which always override any other restrictions
+ -- imposed by "speed" fields, until lifted by a value of -1
+ -- (Example: german Langsamfahrstellen-Signale)
+}
+```
+As of January 2020, the 'dst', 'call_on' and 'dead_end' fields are not used.
+
+#### Lines
+
+The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
+Part of its functionality is also available in LuaATC:
+
+- `rwt.*` - all Railway Time functions are included as documented in [the wiki](https://advtrains.de/wiki/doku.php?id=dev:lines:rwt)
+
+ - `schedule(rw_time, msg)`, `schedule_in(rw_dtime, msg)`
+Schedules an event of type {type="schedule", schedule=true, msg=msg} at (resp. after) the specified railway time (which can be in any format). You can only schedule one event this way. (uses the new lines-internal scheduler)
+
+Note: Using the lines scheduler is preferred over using `interrupt()`, as it's more performant and safer to use.
+
+## Events
+The event table is a variable created locally by the component being triggered. It is a table with the following format:
+```lua
+event = {
+ type = "<event type>",
+ <event type> = true,
+ --additional event-specific content
+}
+```
+You can check the event type by using the following:
+```lua
+if event.type == "wanted" then
+ --do stuff
+end
+```
+or
+```lua
+if event.wanted then
+ --do stuff
+end
+```
+where `wanted` is the event type to check for.
+See the "Active Components" section below for details on the various event types as not all of them are applicable to all components.
+
+## Components
+Atlac components introduce automation-capable components that fall within two categories:
+ - Active Components are components that are able to run Lua code, triggered by specific events.
+ - Passive Components can't perform actions themselves. Their state can be read and set by active components or manually by the player.
+
+### Lua ATC Rails
+Lua ATC rails are the only components that can actually interface with trains. The following event types are available to the Lua ATC rails:
+ - `{type="train", train=true, id="<train_id>"}`
+ * This event is fired when a train enters the rail. The field `id` is the unique train ID, which is 6-digit random numerical string.
+ * If the world contains trains from an older advtrains version, this string may be longer and contain a dot `.`
+
+ - `{type="int", int=true, msg=<message>}`
+ * Fired when an interrupt set by the `interrupt` function runs out. `<message>` is the message passed to the interrupt function.
+ * For backwards compatiblity reasons, `<message>` is also contained in an `event.message` variable.
+
+ - `{type="ext_int", ext_int=true, message=<message>}`
+ * Fired when another node called `interrupt_pos` on this position. `message` is the message passed to the interrupt_pos function.
+
+ - `{type="digiline", digiline=true, channel=<channel>, msg=<message>}`
+ * Fired when the controller receives a digiline message.
+
+#### Basic Lua Rail Functions and Variables
+In addition to the above environment functions, the following functions are available to whilst the train is in contact with the LuaATC rail:
+
+ - `atc_send(<atc_command>)`
+ Sends the specified ATC command to the train (a string) and returns true. If there is no train, returns false and does nothing. See [atc_command.txt](../atc_command.txt) for the ATC command syntax.
+
+ - `atc_reset()`
+ Resets the train's current ATC command. If there is no train, returns false and does nothing.
+
+ - `atc_arrow`
+ Boolean, true when the train is driving in the direction of the arrows of the ATC rail. Nil if there is no train.
+
+ - `atc_id`
+ Train ID of the train currently passing the controller. Nil if there's no train.
+
+ - `atc_speed`
+ Speed of the train, or nil if there is no train.
+
+ - `atc_set_text_outside(text)`
+ Set text shown on the outside of the train. Pass nil to show no text. `text` must be a string.
+
+ - `atc_set_text_inside(text)`
+ Set text shown to train passengers. Pass nil to show no text. `text` must be a string.
+
+ - `atc_set_text_inside(text) / atc_set_text_outside(text)`
+ Getters for inside/outside text, return nil when no train is there.
+
+ - `get_line()`
+ Returns the "Line" property of the train (a string).
+ This can be used to distinguish between trains of different lines and route them appropriately.
+ The interlocking system also uses this property for Automatic Routesetting.
+
+ - `set_line(line)`
+ Sets the "Line" property of the train (a string).
+ If the first digit of this string is a number (0-9), any subway wagons on the train (from advtrains_train_subway) will have this one displayed as line number
+ (where "0" is actually shown as Line 10 on the train)
+
+ - `get_rc()`
+ Returns the "Routingcode" property of the train (a string).
+ The interlocking system uses this property for Automatic Routesetting.
+
+ - `set_rc(routingcode)`
+ Sets the "Routingcode" property of the train (a string).
+ The interlocking system uses this property for Automatic Routesetting.
+
+#### Shunting Functions and Variables
+There are several functions available especially for shunting operations. Some of these functions make use of Freight Codes (FC) set in the Wagon Properties of each wagon and/or locomotive:
+
+ - `split_at_index(index, atc_command)`
+ Splits the train at the specified index, into a train with index-1 wagons and a second train starting with the index-th wagon. The `atc_command` specified is sent to the second train after decoupling. `"S0"` or `"B0"` is common to ensure any locomotives in the remaining train don't continue to move.
+
+ Example: train has wagons `"foo","foo","foo","bar","bar","bar"`
+ Command: `split_at_index(4,"S0")`
+ Result: first train (continues at previous speed): `"foo","foo","foo"`, second train (slows at S0): `"bar","bar","bar"`
+
+ - `split_at_fc(atc_command, len)`
+ Splits the train in such a way that all cars with non-empty current FC of the first part of the train have the same FC. The
+ `atc_command` specified is sent to the rear part, as with split_at_index. It returns the fc of the cars of the first part.
+
+ Example : Train has current FCs `"" "" "bar" "foo" "bar"`
+ Command: `split_at_fc(<atc_command>)`
+ Result: `train "" "" "bar"` and `train "foo" "bar"`
+ The function returns `"bar"` in this case.
+
+ The optional argument `len` specifies the maximum length for the
+ first part of the train.
+ Example: Train has current FCs `"foo" "foo" "foo" "foo" "bar" "bar"`
+ Command: `split_at_fc(<atc_command>,3)`
+ Result: `"foo" "foo" "foo"` and `"foo" "bar" "bar"`
+ The function returns `"foo"` in this case.
+
+ - `split_off_locomotive(command, len)`
+ Splits off the locomotives at the front of the train, which are
+ identified by an empty FC. `command` specifies the ATC command to be
+ executed by the rear half of the train. The optional argument `len` specifies the maximum length for the
+ first part of the train as above.
+
+ - `step_fc()`
+ Steps the FCs of all train cars forward. FCs are composed of codes
+ separated by exclamation marks (`!`), for instance
+ `"foo!bar!baz"`. Each wagon has a current FC, indicating its next
+ destination. Stepping the freight code forward, selects the next
+ code after the !. If the end of the string is reached, then the
+ first code is selected, except if the string ends with a question
+ mark (`?`), then the order is reversed.
+
+
+ - `train_length()`
+ returns the number of cars the train is composed of.
+
+ - `set_autocouple()`
+ Sets the train into autocouple mode. The train will couple to the next train it collides with.
+
+ - `unset_autocouple()`
+ Unsets autocouple mode
+
+Deprecated:
+
+ - `set_shunt()`, `unset_shunt()`
+ deprecated aliases for set_autocouple() and unset_autocouple(), will be removed from a later release.
+
+
+#### Interlocking
+This additional function is available when advtrains_interlocking is enabled:
+
+ - `atc_set_disable_ars(boolean)`
+ Disables (true) or enables (false) the use of ARS for this train. The train will not trigger ARS (automatic route setting) on signals then.
+
+ Note: If you want to disable ARS from an approach callback, the call to `atc_set_disable_ars(true)` *must* happen during the approach callback, and may not be deferred to an interrupt(). Else the train might trigger an ARS before the interrupt fires.
+
+#### Approach callbacks
+The LuaATC interface provides a way to hook into the approach callback system, which is for example used in the TSR rails (provided by advtrains_interlocking) or the station tracks (provided by advtrains_lines). However, for compatibility reasons, this behavior needs to be explicitly enabled.
+
+Enabling the receiving of approach events works by setting a variable in the local environment of the ATC rail, by inserting the following code:
+
+```lua
+__approach_callback_mode = 1
+-- to receive approach callbacks only in arrow direction
+-- or alternatively
+__approach_callback_mode = 2
+-- to receive approach callbacks in both directions
+```
+
+The following event will be emitted when a train approaches:
+```lua
+{type="approach", approach=true, id="<train_id>"}
+```
+
+Please note these important considerations when using approach callbacks:
+
+ - Approach events might be generated multiple times for the same approaching train. If you are using atc_set_lzb_tsr(), you need to call this function on every run of the approach callback, even if you issued it before for the same train.
+ - A reference to the train is available while executing this event, so that functions such as atc_send() or atc_set_text_outside() can be called. On any consecutive interrupts, that reference will no longer be available until the train enters the track ("train" event)
+ - Unlike all other callbacks, approach callbacks are executed synchronous during the train step. This may cause unexpected side effects when performing certain actions (such as switching turnouts, setting signals/routes) from inside such a callback. I strongly encourage you to only run things that are absolutely necessary at this point in time, and defer anything else to an interrupt(). Be aware that certain things might trigger unexpected behavior.
+
+Operations that are safe to execute in approach callbacks:
+
+ - anything related only to the global environment (setting things in S)
+ - digiline_send()
+ - atc_set_text_*()
+ - atc_set_lzb_tsr() (see below)
+
+In the context of approach callbacks, one more function is available:
+
+ - `atc_set_lzb_tsr(speed)`
+Impose a Temporary Speed Restriction at the location of this rail, making the train pass this rail at the specified speed. (Causes the same behavior as the TSR rail)
+
+#### Timetable Automation
+
+The advtrains_line_automation component adds a few contraptions that should make creating timeable systems easier.
+Part of its functionality is also available in LuaATC:
+
+- `rwt.*`
+All Railway Time functions are included as documented in https://advtrains.de/wiki/doku.php?id=dev:lines:rwt
+
+- `schedule(rw_time, msg)`
+- `schedule_in(rw_dtime, msg)`
+Schedules the following event `{type="schedule", schedule=true, msg=msg}` at (resp. after) the specified railway time (which can be in any format). You can only schedule one event this way. Uses the new lines-internal scheduler.
+
+### Operator panel
+This simple node executes its actions when punched. It can be used to change a switch and update the corresponding signals or similar applications. It can also be connected to by the`digilines` mod.
+
+The event fired is `{type="punch", punch=true}` by default. In case of an interrupt or a digiline message, the events are similar to the ones of the ATC rail.
+
+### Init code
+The initialization code is not a component as such, but rather a part of the whole environment. It can (and should) be used to make definitions that other components can refer to.
+A basic example function to define behavior for trains in stations:
+```lua
+function F.station(station_name)
+ if event.train then
+ atc_send("B0WOL")
+ atc_set_text_inside(station_name)
+ interrupt(10,"depart")
+ end
+ if event.int and event.message="depart" then
+ atc_set_text_inside("") --an empty string clears the displayed text
+ atc_send("OCD1SM")
+ end
+end
+```
+
+The corresponding Lua ATC Rail(s) would then contain the following or similar:
+```lua
+F.station("Main Station")
+```
+
+The init code is run whenever the F table needs to be refilled with data. This is the case on server startup and whenever the init code is changed and you choose to run it.
+The event table of the init code is always `{type="init", init=true}` and can not be anything else.
+Functions are run in the environment of the currently active node, regardless of where they were defined.
+
+### Passive components
+
+All passive components can be interfaced with the `setstate()` and `getstate()` functions (see above).
+Each node below has been mapped to specific "states":
+
+#### Signals
+The red/green light signals `advtrains:signal_on/off` are interfaceable. Others such as `advtrains:retrosignal_on/off` are not. If advtrains_interlocking is enabled, trains will obey the signal if the influence point is set.
+
+ - "green" - Signal shows green light
+ - "red" - Signal shows red light
+
+#### Switches/Turnouts
+All default rail switches are interfaceable, independent of orientation.
+
+ - "cr" The switch is set in the direction that is not straight.
+ - "st" The switch is set in the direction that is straight.
+
+The "Y" and "3-Way" switches have custom states. Looking from the convergence point:
+
+ - "l" The switch is set towards the left.
+ - "c" The switch is set towards the center (3-way only).
+ - "r" The switch is set towards the right.
+
+
+#### Mesecon Switch
+The Mesecon switch can be switched using LuaAutomation. Note that this is not possible on levers or protected mesecon switches, only the unprotected full-node 'Switch' block `mesecons_switch:mesecon_switch_on/off`.
+
+ - "on" - the switch is switched on.
+ - "off" - the switch is switched off.
+
+#### Andrew's Cross
+
+ - "on" - it blinks.
+ - "off" - it does not blink.
+
+#### Passive Component Naming
+You can assign names to passive components using the Passive Component Naming tool.
+Once you set a name for any component, you can reference it by that name in the `getstate()` and `setstate()` functions.
+This way, you don't need to memorize positions.
+
+Example: signal named `"Stn_P1_out"` at `(1,2,3)`
+Use `setstate("Stn_P1_out", "green")` instead of `setstate(POS(1,2,3), "green")`
+
+If `advtrains_interlocking` is enabled, PC-Naming can also be used to name interlocking signals for route setting via the `set_route()` functions.
+**Important**: The "Signal Name" field in the signalling formspec is completely independent from PC-Naming and can't be used to look up the position. You need to explicitly use the PC-Naming tool.
+
diff --git a/advtrains_luaautomation/active_common.lua b/advtrains_luaautomation/active_common.lua
new file mode 100644
index 0000000..9bf8377
--- /dev/null
+++ b/advtrains_luaautomation/active_common.lua
@@ -0,0 +1,200 @@
+
+
+local ac = {nodes={}}
+
+function ac.load(data)
+ if data then
+ ac.nodes=data.nodes
+ end
+end
+function ac.save()
+ return {nodes = ac.nodes}
+end
+
+function ac.after_place_node(pos, player)
+ local meta=minetest.get_meta(pos)
+ meta:set_string("formspec", ac.getform(pos, meta))
+ meta:set_string("infotext", "LuaAutomation component, unconfigured.")
+ local ph=minetest.pos_to_string(pos)
+ --just get first available key!
+ for en,_ in pairs(atlatc.envs) do
+ ac.nodes[ph]={env=en}
+ return
+ end
+end
+function ac.getform(pos, meta_p)
+ local meta = meta_p or minetest.get_meta(pos)
+ local envs_asvalues={}
+
+ local ph=minetest.pos_to_string(pos)
+ local nodetbl = ac.nodes[ph]
+ local env, code, err = nil, "", ""
+ if nodetbl then
+ code=nodetbl.code or ""
+ err=nodetbl.err or ""
+ env=nodetbl.env or ""
+ end
+ local sel = 1
+ for n,_ in pairs(atlatc.envs) do
+ envs_asvalues[#envs_asvalues+1]=minetest.formspec_escape(n)
+ if n==env then
+ sel=#envs_asvalues
+ end
+ end
+ local form = "size["..atlatc.CODE_FORM_SIZE.."]"
+ .."style[code;font=mono]"
+ .."label[0,-0.1;Environment]"
+ .."dropdown[0,0.3;3;env;"..table.concat(envs_asvalues, ",")..";"..sel.."]"
+ .."button[5,0.2;2,1;save;Save]"
+ .."button[7,0.2;3,1;cle;Clear Local Env.]"
+ .."textarea[0.3,1.5;"..atlatc.CODE_FORM_SIZE..";code;Code;"..minetest.formspec_escape(code).."]"
+ .."label[0,9.7;"..err.."]"
+ return form
+end
+
+function ac.after_dig_node(pos, node, player)
+ advtrains.invalidate_all_paths(pos)
+ advtrains.ndb.clear(pos)
+ local ph=minetest.pos_to_string(pos)
+ ac.nodes[ph]=nil
+end
+
+function ac.on_receive_fields(pos, formname, fields, player)
+ if not minetest.check_player_privs(player:get_player_name(), {atlatc=true}) then
+ minetest.chat_send_player(player:get_player_name(), "Missing privilege: atlatc - Operation cancelled!")
+ return
+ end
+
+ local meta=minetest.get_meta(pos)
+ local ph=minetest.pos_to_string(pos)
+ local nodetbl = ac.nodes[ph] or {}
+ --if fields.quit then return end
+ if fields.env then
+ nodetbl.env=fields.env
+ end
+ if fields.code then
+ nodetbl.code=fields.code
+ end
+ if fields.save then
+ -- reset certain things
+ nodetbl.err=nil
+ if advtrains.lines and advtrains.lines.sched then
+ -- discard all schedules for this node
+ advtrains.lines.sched.discard_all(advtrains.encode_pos(pos))
+ end
+ end
+ if fields.cle then
+ nodetbl.data={}
+ end
+
+ ac.nodes[ph]=nodetbl
+
+ meta:set_string("formspec", ac.getform(pos, meta))
+ if nodetbl.env then
+ meta:set_string("infotext", "LuaAutomation component, assigned to environment '"..nodetbl.env.."'")
+ else
+ meta:set_string("infotext", "LuaAutomation component, invalid enviroment set!")
+ end
+end
+
+function ac.run_in_env(pos, evtdata, customfct_p)
+ local ph=minetest.pos_to_string(pos)
+ local nodetbl = ac.nodes[ph]
+ if not nodetbl then
+ atwarn("LuaAutomation component at",ph,": Data not in memory! Please visit component and click 'Save'!")
+ return
+ end
+
+ local meta
+ if advtrains.is_node_loaded(pos) then
+ meta=minetest.get_meta(pos)
+ end
+
+ if not nodetbl.env or not atlatc.envs[nodetbl.env] then
+ atwarn("LuaAutomation component at",ph,": Not an existing environment: "..(nodetbl.env or "<nil>"))
+ return false
+ end
+ local env = atlatc.envs[nodetbl.env]
+ if not nodetbl.code or nodetbl.code=="" then
+ env:log("warning", "LuaAutomation component at",ph,": No code to run! (insert -- to suppress warning)")
+ return false
+ end
+
+ local customfct=customfct_p or {}
+ -- add interrupt function
+ customfct.interrupt=function(t, imesg)
+ assertt(t, "number")
+ assert(t >= 0)
+ atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg, msg=imesg}) --Compatiblity "message" field.
+ end
+ customfct.interrupt_safe=function(t, imesg)
+ assertt(t, "number")
+ assert(t >= 0)
+ if atlatc.interrupt.has_at_pos(pos) then
+ return false
+ end
+ atlatc.interrupt.add(t, pos, {type="int", int=true, message=imesg, msg=imesg}) --Compatiblity "message" field.
+ return true
+ end
+ customfct.clear_interrupts=function()
+ atlatc.interrupt.clear_ints_at_pos(pos)
+ end
+ -- add digiline_send function, if digiline is loaded
+ if minetest.global_exists("digiline") then
+ customfct.digiline_send=function(channel, msg)
+ assertt(channel, "string")
+ if advtrains.is_node_loaded(pos) then
+ digiline:receptor_send(pos, digiline.rules.default, channel, msg)
+ end
+ end
+ end
+ -- add lines scheduler if enabled
+ if advtrains.lines and advtrains.lines.sched then
+ customfct.schedule = function(rwtime, msg)
+ return advtrains.lines.sched.enqueue(rwtime, "atlatc_env", {pos=pos, msg=msg}, advtrains.encode_pos(pos), 1)
+ end
+ customfct.schedule_in = function(rwtime, msg)
+ return advtrains.lines.sched.enqueue_in(rwtime, "atlatc_env", {pos=pos, msg=msg}, advtrains.encode_pos(pos), 1)
+ end
+ end
+
+ local datain=nodetbl.data or {}
+ local succ, dataout = env:execute_code(datain, nodetbl.code, evtdata, customfct)
+ if succ then
+ atlatc.active.nodes[ph].data=atlatc.remove_invalid_data(dataout)
+ else
+ atlatc.active.nodes[ph].err=dataout
+ env:log("error", "LuaATC component at",ph,": LUA Error:",dataout)
+ if meta then
+ meta:set_string("infotext", "LuaATC component, ERROR:"..dataout)
+ end
+ --TODO temporary
+ --if customfct.atc_id then
+ -- advtrains.drb_dump(customfct.atc_id)
+ -- error("Debug: LuaATC error hit!")
+ --end
+ end
+ if meta then
+ meta:set_string("formspec", ac.getform(pos, meta))
+ end
+end
+
+function ac.on_digiline_receive(pos, node, channel, msg)
+ atlatc.interrupt.add(0, pos, {type="digiline", digiline=true, channel = channel, msg = msg})
+end
+
+if advtrains.lines and advtrains.lines.sched then
+ advtrains.lines.sched.register_callback("atlatc_env", function(data)
+ -- This adds another interrupt to the atlatc queue... there might be a better way
+ atlatc.interrupt.add(0, data.pos, {type="schedule",schedule=true, msg=data.msg})
+ end)
+end
+
+ac.trackdef_advtrains_defs = {
+ on_train_enter = function(pos, train_id)
+ --do async. Event is fired in train steps
+ atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id})
+ end,
+}
+
+atlatc.active=ac
diff --git a/advtrains_luaautomation/atc_rail.lua b/advtrains_luaautomation/atc_rail.lua
new file mode 100644
index 0000000..2d6efe5
--- /dev/null
+++ b/advtrains_luaautomation/atc_rail.lua
@@ -0,0 +1,247 @@
+-- atc_rail.lua
+-- registers and handles the ATC rail. Active component.
+-- This is the only component that can interface with trains, so train interface goes here too.
+
+--Using subtable
+local r={}
+
+-- Note on appr_internal:
+-- The Approach callback is a special corner case: the train is not on the node, and it is executed synchronized
+-- (in the train step right during LZB traversal). We therefore need access to the train id and the lzbdata table
+function r.fire_event(pos, evtdata, appr_internal)
+
+ local ph=minetest.pos_to_string(pos)
+ local railtbl = atlatc.active.nodes[ph]
+
+ if not railtbl then
+ atwarn("LuaAutomation ATC interface rail at",ph,": Data not in memory! Please visit position and click 'Save'!")
+ return
+ end
+
+ --prepare ingame API for ATC. Regenerate each time since pos needs to be known
+ --If no train, then return false.
+
+ -- try to get the train from the event data
+ -- This workaround is required because the callback is one step delayed, and a fast train may have already left the node.
+ -- Also used for approach callback
+ local train_id = evtdata._train_id
+ local atc_arrow = evtdata._train_arrow
+ local train, tvel
+
+ if train_id then
+ train=advtrains.trains[train_id]
+ -- speed
+ tvel=train.velocity
+ -- if still no train_id available, try to get the train at my position
+ else
+ train_id=advtrains.get_train_at_pos(pos)
+ if train_id then
+ train=advtrains.trains[train_id]
+ advtrains.train_ensure_init(train_id, train)
+ -- look up atc_arrow
+ local index = advtrains.path_lookup(train, pos)
+ atc_arrow = (train.path_cn[index] == 1)
+ -- speed
+ tvel=train.velocity
+ end
+ end
+
+ local customfct={
+ atc_send = function(cmd)
+ if not train_id then return false end
+ assertt(cmd, "string")
+ advtrains.atc.train_set_command(train, cmd, atc_arrow)
+ return true
+ end,
+ split_at_index = function(index, cmd)
+ if not train_id then return false end
+ assertt(cmd, "string")
+ if type(index) ~= "number" then
+ return false
+ end
+ local new_id = advtrains.split_train_at_index(train, index)
+ if new_id then
+ minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
+ return true
+ end
+ return false
+ end,
+ split_at_fc = function(cmd, len)
+ assertt(cmd, "string")
+ if not train_id then return false end
+ local new_id, fc = advtrains.split_train_at_fc(train, false, len)
+ if new_id then
+ minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
+ end
+ return fc or ""
+ end,
+ split_off_locomotive = function(cmd, len)
+ assertt(cmd, "string")
+ if not train_id then return false end
+ local new_id, fc = advtrains.split_train_at_fc(train, true, len)
+ if new_id then
+ minetest.after(1,advtrains.atc.train_set_command,advtrains.trains[new_id], cmd, atc_arrow)
+ end
+ end,
+ train_length = function ()
+ if not train_id then return false end
+ return #train.trainparts
+ end,
+ step_fc = function()
+ if not train_id then return false end
+ advtrains.train_step_fc(train)
+ end,
+ set_shunt = function()
+ -- enable shunting mode
+ if not train_id then return false end
+ train.is_shunt = true
+ end,
+ unset_shunt = function()
+ if not train_id then return false end
+ train.is_shunt = nil
+ end,
+ set_autocouple = function ()
+ if not train_id then return false end
+ train.autocouple = true
+ end,
+ unset_autocouple = function ()
+ if not train_id then return false end
+ train.autocouple = nil
+ end,
+ set_line = function(line)
+ if type(line)~="string" and type(line)~="number" then
+ return false
+ end
+ train.line = line .. ""
+ minetest.after(0, advtrains.invalidate_path, train_id)
+ return true
+ end,
+ get_line = function()
+ return train.line
+ end,
+ set_rc = function(rc)
+ if type(rc)~="string"then
+ return false
+ end
+ train.routingcode = rc
+ minetest.after(0, advtrains.invalidate_path, train_id)
+ return true
+ end,
+ get_rc = function()
+ return train.routingcode
+ end,
+ atc_reset = function(cmd)
+ if not train_id then return false end
+ assertt(cmd, "string")
+ advtrains.atc.train_reset_command(train)
+ return true
+ end,
+ atc_arrow = atc_arrow,
+ atc_id = train_id,
+ atc_speed = tvel,
+ atc_set_text_outside = function(text)
+ if not train_id then return false end
+ if text then assertt(text, "string") end
+ advtrains.trains[train_id].text_outside=text
+ return true
+ end,
+ atc_set_text_inside = function(text)
+ if not train_id then return false end
+ if text then assertt(text, "string") end
+ advtrains.trains[train_id].text_inside=text
+ return true
+ end,
+ atc_get_text_outside = function()
+ if not train_id then return false end
+ return advtrains.trains[train_id].text_outside
+ end,
+ atc_get_text_inside = function(text)
+ if not train_id then return false end
+ return advtrains.trains[train_id].text_inside
+ end,
+ atc_set_lzb_tsr = function(speed)
+ if not appr_internal then
+ error("atc_set_lzb_tsr() can only be used during 'approach' events!")
+ end
+ assert(tonumber(speed), "Number expected!")
+
+ local index = appr_internal.index
+ advtrains.lzb_add_checkpoint(train, index, speed, nil)
+
+ return true
+ end,
+ }
+ -- interlocking specific
+ if advtrains.interlocking then
+ customfct.atc_set_ars_disable = function(value)
+ advtrains.interlocking.ars_set_disable(train, value)
+ end
+ end
+
+ atlatc.active.run_in_env(pos, evtdata, customfct)
+
+end
+
+advtrains.register_tracks("default", {
+ nodename_prefix="advtrains_luaautomation:dtrack",
+ texture_prefix="advtrains_dtrack_atc",
+ models_prefix="advtrains_dtrack",
+ models_suffix=".b3d",
+ shared_texture="advtrains_dtrack_shared_atc.png",
+ description=atltrans("LuaAutomation ATC Rail"),
+ formats={},
+ get_additional_definiton = function(def, preset, suffix, rotation)
+ return {
+ after_place_node = atlatc.active.after_place_node,
+ after_dig_node = atlatc.active.after_dig_node,
+
+ on_receive_fields = function(pos, ...)
+ atlatc.active.on_receive_fields(pos, ...)
+
+ --set arrowconn (for ATC)
+ local ph=minetest.pos_to_string(pos)
+ local _, conns=advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
+ atlatc.active.nodes[ph].arrowconn=conns[1].c
+ end,
+
+ advtrains = {
+ on_train_enter = function(pos, train_id, train, index)
+ --do async. Event is fired in train steps
+ atlatc.interrupt.add(0, pos, {type="train", train=true, id=train_id,
+ _train_id = train_id, _train_arrow = (train.path_cn[index] == 1)})
+ end,
+ on_train_approach = function(pos, train_id, train, index, has_entered, lzbdata)
+ -- Insert an event only if the rail indicated that it supports approach callbacks
+ local ph=minetest.pos_to_string(pos)
+ local railtbl = atlatc.active.nodes[ph]
+ -- uses a "magic variable" in the local environment of the node
+ -- This hack is necessary because code might not be prepared to get approach events...
+ if railtbl and railtbl.data and railtbl.data.__approach_callback_mode then
+ local acm = railtbl.data.__approach_callback_mode
+ local in_arrow = (train.path_cn[index] == 1)
+ if acm==2 or (acm==1 and in_arrow) then
+ local evtdata = {type="approach", approach=true, id=train_id, has_entered = has_entered,
+ _train_id = train_id, _train_arrow = in_arrow} -- reuses code from train_enter
+ -- This event is *required* to run synchronously, because it might set the ars_disable flag on the train and add LZB checkpoints,
+ -- although this is generally discouraged because this happens right in a train step
+ -- At this moment, I am not aware whether this may cause side effects, and I must encourage users not to do expensive calculations here.
+ r.fire_event(pos, evtdata, {train_id = train_id, train = train, index = index, lzbdata = lzbdata})
+ end
+ end
+ end,
+ },
+ luaautomation = {
+ fire_event=r.fire_event
+ },
+ digiline = {
+ receptor = {},
+ effector = {
+ action = atlatc.active.on_digiline_receive
+ },
+ },
+ }
+ end,
+}, advtrains.trackpresets.t_30deg_straightonly)
+
+
+atlatc.rail = r
diff --git a/advtrains_luaautomation/chatcmds.lua b/advtrains_luaautomation/chatcmds.lua
new file mode 100644
index 0000000..b6ffaee
--- /dev/null
+++ b/advtrains_luaautomation/chatcmds.lua
@@ -0,0 +1,141 @@
+--chatcmds.lua
+--Registers commands to modify the init and step code for LuaAutomation
+
+local function get_init_form(env, pname)
+ local err = env.init_err or ""
+ local code = env.init_code or ""
+
+ local form = "size["..atlatc.CODE_FORM_SIZE.."]"
+ .."style[code;font=mono]"
+ .."button[0.0,0.2;2.5,1;run;Run Init Code]"
+ .."button[2.5,0.2;2.5,1;cls;Clear S]"
+ .."button[5.0,0.2;2.5,1;save;Save]"
+ .."button[7.5,0.2;2.5,1;del;Delete Env.]"
+ .."textarea[0.3,1.5;"..atlatc.CODE_FORM_SIZE..";code;Environment initialization code;"..minetest.formspec_escape(code).."]"
+ .."label[0.0,9.7;"..err.."]"
+ return form
+end
+
+core.register_chatcommand("env_setup", {
+ params = "<environment name>",
+ description = "Set up and modify AdvTrains LuaAutomation environment",
+ privs = {atlatc=true},
+ func = function(name, param)
+ local env=atlatc.envs[param]
+ if not env then return false,"Invalid environment name!" end
+ minetest.show_formspec(name, "atlatc_envsetup_"..param, get_init_form(env, name))
+ return true
+ end,
+})
+
+core.register_chatcommand("env_create", {
+ params = "<environment name>",
+ description = "Create an AdvTrains LuaAutomation environment",
+ privs = {atlatc=true},
+ func = function(name, param)
+ if not param or param=="" then return false, "Name required!" end
+ if string.find(param, "[^a-zA-Z0-9-_]") then return false, "Invalid name (only common characters)" end
+ if atlatc.envs[param] then return false, "Environment already exists!" end
+ atlatc.envs[param] = atlatc.env_new(param)
+ atlatc.envs[param].subscribers = {name}
+ return true, "Created environment '"..param.."'. Use '/env_setup "..param.."' to define global initialization code, or start building LuaATC components!"
+ end,
+})
+core.register_chatcommand("env_subscribe", {
+ params = "<environment name>",
+ description = "Subscribe to the log of an Advtrains LuaATC environment",
+ privs = {atlatc=true},
+ func = function(name, param)
+ local env=atlatc.envs[param]
+ if not env then return false,"Invalid environment name!" end
+ for _,pname in ipairs(env.subscribers) do
+ if pname==name then
+ return false, "Already subscribed!"
+ end
+ end
+ table.insert(env.subscribers, name)
+ return true, "Subscribed to environment '"..param.."'."
+ end,
+})
+core.register_chatcommand("env_unsubscribe", {
+ params = "<environment name>",
+ description = "Unubscribe to the log of an Advtrains LuaATC environment",
+ privs = {atlatc=true},
+ func = function(name, param)
+ local env=atlatc.envs[param]
+ if not env then return false,"Invalid environment name!" end
+ for index,pname in ipairs(env.subscribers) do
+ if pname==name then
+ table.remove(env.subscribers, index)
+ return true, "Successfully unsubscribed!"
+ end
+ end
+ return false, "Not subscribed to environment '"..param.."'."
+ end,
+})
+core.register_chatcommand("env_subscriptions", {
+ params = "[environment name]",
+ description = "List Advtrains LuaATC environments you are subscribed to (no parameters) or subscribers of an environment (giving an env name).",
+ privs = {atlatc=true},
+ func = function(name, param)
+ if not param or param=="" then
+ local none=true
+ for envname, env in pairs(atlatc.envs) do
+ for _,pname in ipairs(env.subscribers) do
+ if pname==name then
+ none=false
+ minetest.chat_send_player(name, envname)
+ end
+ end
+ end
+ if none then
+ return false, "Not subscribed to any!"
+ end
+ return true
+ end
+ local env=atlatc.envs[param]
+ if not env then return false,"Invalid environment name!" end
+ local none=true
+ for index,pname in ipairs(env.subscribers) do
+ none=false
+ minetest.chat_send_player(name, pname)
+ end
+ if none then
+ return false, "No subscribers!"
+ end
+ return true
+ end,
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+
+ local pname=player:get_player_name()
+ if not minetest.check_player_privs(pname, {atlatc=true}) then return end
+
+ local envname=string.match(formname, "^atlatc_delconfirm_(.+)$")
+ if envname and fields.sure=="YES" then
+ atlatc.envs[envname]=nil
+ minetest.chat_send_player(pname, "Environment deleted!")
+ return
+ end
+
+ envname=string.match(formname, "^atlatc_envsetup_(.+)$")
+ if not envname then return end
+
+ local env=atlatc.envs[envname]
+ if not env then return end
+
+ if fields.del then
+ minetest.show_formspec(pname, "atlatc_delconfirm_"..envname, "field[sure;"..minetest.formspec_escape("SURE TO DELETE ENVIRONMENT "..envname.."? Type YES (all uppercase) to continue or just quit form to cancel.")..";]")
+ return
+ end
+
+ env.init_err=nil
+ if fields.code then
+ env.init_code=fields.code
+ end
+ if fields.run then
+ env:run_initcode()
+ minetest.show_formspec(pname, formname, get_init_form(env, pname))
+ end
+end)
diff --git a/advtrains_luaautomation/environment.lua b/advtrains_luaautomation/environment.lua
new file mode 100644
index 0000000..63aa68d
--- /dev/null
+++ b/advtrains_luaautomation/environment.lua
@@ -0,0 +1,372 @@
+-------------
+-- lua sandboxed environment
+
+-- function to cross out functions and userdata.
+-- modified from dump()
+function atlatc.remove_invalid_data(o, nested)
+ if o==nil then return nil end
+ local valid_dt={["nil"]=true, boolean=true, number=true, string=true}
+ if type(o) ~= "table" then
+ --check valid data type
+ if not valid_dt[type(o)] then
+ return nil
+ end
+ return o
+ end
+ -- Contains table -> true/nil of currently nested tables
+ nested = nested or {}
+ if nested[o] then
+ return nil
+ end
+ nested[o] = true
+ for k, v in pairs(o) do
+ v = atlatc.remove_invalid_data(v, nested)
+ end
+ nested[o] = nil
+ return o
+end
+
+
+local env_proto={
+ load = function(self, envname, data)
+ self.name=envname
+ self.sdata=data.sdata and atlatc.remove_invalid_data(data.sdata) or {}
+ self.fdata={}
+ self.init_code=data.init_code or ""
+ self.subscribers=data.subscribers or {}
+ end,
+ save = function(self)
+ -- throw any function values out of the sdata table
+ self.sdata = atlatc.remove_invalid_data(self.sdata)
+ return {sdata = self.sdata, init_code=self.init_code, subscribers=self.subscribers}
+ end,
+}
+
+--Environment
+--Code modified from mesecons_luacontroller (credit goes to Jeija and mesecons contributors)
+
+local safe_globals = {
+ "assert", "error", "ipairs", "next", "pairs", "select",
+ "tonumber", "tostring", "type", "unpack", "_VERSION"
+}
+
+local function safe_date(f, t)
+ if not f then
+ -- fall back to old behavior
+ return(os.date("*t",os.time()))
+ else
+ --pass parameters
+ return os.date(f,t)
+ end
+end
+
+-- string.rep(str, n) with a high value for n can be used to DoS
+-- the server. Therefore, limit max. length of generated string.
+local function safe_string_rep(str, n)
+ if #str * n > 2000 then
+ debug.sethook() -- Clear hook
+ error("string.rep: string length overflow", 2)
+ end
+
+ return string.rep(str, n)
+end
+
+-- string.find with a pattern can be used to DoS the server.
+-- Therefore, limit string.find to patternless matching.
+-- Note: Disabled security since there are enough security leaks and this would be unneccessary anyway to DoS the server
+local function safe_string_find(...)
+ --if (select(4, ...)) ~= true then
+ -- debug.sethook() -- Clear hook
+ -- error("string.find: 'plain' (fourth parameter) must always be true for security reasons.")
+ --end
+
+ return string.find(...)
+end
+
+local mp=minetest.get_modpath("advtrains_luaautomation")
+
+local static_env = {
+ --core LUA functions
+ string = {
+ byte = string.byte,
+ char = string.char,
+ format = string.format,
+ len = string.len,
+ lower = string.lower,
+ upper = string.upper,
+ rep = safe_string_rep,
+ reverse = string.reverse,
+ sub = string.sub,
+ find = safe_string_find,
+ },
+ math = {
+ abs = math.abs,
+ acos = math.acos,
+ asin = math.asin,
+ atan = math.atan,
+ atan2 = math.atan2,
+ ceil = math.ceil,
+ cos = math.cos,
+ cosh = math.cosh,
+ deg = math.deg,
+ exp = math.exp,
+ floor = math.floor,
+ fmod = math.fmod,
+ frexp = math.frexp,
+ huge = math.huge,
+ ldexp = math.ldexp,
+ log = math.log,
+ log10 = math.log10,
+ max = math.max,
+ min = math.min,
+ modf = math.modf,
+ pi = math.pi,
+ pow = math.pow,
+ rad = math.rad,
+ random = math.random,
+ sin = math.sin,
+ sinh = math.sinh,
+ sqrt = math.sqrt,
+ tan = math.tan,
+ tanh = math.tanh,
+ },
+ table = {
+ concat = table.concat,
+ insert = table.insert,
+ maxn = table.maxn,
+ remove = table.remove,
+ sort = table.sort,
+ },
+ os = {
+ clock = os.clock,
+ difftime = os.difftime,
+ time = os.time,
+ date = safe_date,
+ },
+ POS = function(x,y,z) return {x=x, y=y, z=z} end,
+ getstate = advtrains.getstate,
+ setstate = advtrains.setstate,
+ is_passive = advtrains.is_passive,
+ --interrupts are handled per node, position unknown. (same goes for digilines)
+ --however external interrupts can be set here.
+ interrupt_pos = function(parpos, imesg)
+ local pos=atlatc.pcnaming.resolve_pos(parpos)
+ atlatc.interrupt.add(0, pos, {type="ext_int", ext_int=true, message=imesg})
+ end,
+ -- sends an atc command to train regardless of where it is in the world
+ atc_send_to_train = function(train_id, command)
+ assertt(command, "string")
+ local train = advtrains.trains[train_id]
+ if train then
+ advtrains.atc.train_set_command(train, command, true)
+ return true
+ else
+ return false
+ end
+ end,
+}
+
+-- If interlocking is present, enable route setting functions
+if advtrains.interlocking then
+ local function gen_checks(signal, route_name, noroutesearch)
+ assertt(route_name, "string")
+ local pos = atlatc.pcnaming.resolve_pos(signal)
+ local sigd = advtrains.interlocking.db.get_sigd_for_signal(pos)
+ if not sigd then
+ error("There's no signal at "..minetest.pos_to_string(pos))
+ end
+ local tcbs = advtrains.interlocking.db.get_tcbs(sigd)
+ if not tcbs then
+ error("Inconsistent configuration, no tcbs for signal at "..minetest.pos_to_string(pos))
+ end
+
+ local routeid, route
+ if not noroutesearch then
+ for routeidt, routet in ipairs(tcbs.routes) do
+ if routet.name == route_name then
+ routeid = routeidt
+ route = routet
+ break
+ end
+ end
+ if not route then
+ error("No route called "..route_name.." at "..minetest.pos_to_string(pos))
+ end
+ end
+ return pos, sigd, tcbs, routeid, route
+ end
+
+
+ static_env.can_set_route = function(signal, route_name)
+ local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
+ -- if route is already set on signal, return whether it's committed
+ if tcbs.routeset == routeid then
+ return tcbs.route_committed
+ end
+ -- actually try setting route (parameter 'true' designates try-run
+ local ok = advtrains.interlocking.route.set_route(sigd, route, true)
+ return ok
+ end
+ static_env.set_route = function(signal, route_name)
+ local pos, sigd, tcbs, routeid, route = gen_checks(signal, route_name)
+ return advtrains.interlocking.route.update_route(sigd, tcbs, routeid)
+ end
+ static_env.cancel_route = function(signal)
+ local pos, sigd, tcbs, routeid, route = gen_checks(signal, "", true)
+ return advtrains.interlocking.route.update_route(sigd, tcbs, nil, true)
+ end
+ static_env.get_aspect = function(signal)
+ local pos = atlatc.pcnaming.resolve_pos(signal)
+ return advtrains.interlocking.signal_get_aspect(pos)
+ end
+ static_env.set_aspect = function(signal, asp)
+ local pos = atlatc.pcnaming.resolve_pos(signal)
+ return advtrains.interlocking.signal_set_aspect(pos)
+ end
+end
+
+-- Lines-specific:
+if advtrains.lines then
+ local atlrwt = advtrains.lines.rwt
+ static_env.rwt = {
+ now = atlrwt.now,
+ new = atlrwt.new,
+ copy = atlrwt.copy,
+ to_table = atlrwt.to_table,
+ to_secs = atlrwt.to_secs,
+ to_string = atlrwt.to_string,
+ add = atlrwt.add,
+ diff = atlrwt.diff,
+ sub = atlrwt.sub,
+ adj_diff = atlrwt.adj_diff,
+ adjust_cycle = atlrwt.adjust_cycle,
+ adjust = atlrwt.adjust,
+ to_string = atlrwt.to_string,
+ get_time_until = atlrwt.get_time_until,
+ next_rpt = atlrwt.next_rpt,
+ last_rpt = atlrwt.last_rpt,
+ time_from_last_rpt = atlrwt.time_from_last_rpt,
+ time_to_next_rpt = atlrwt.time_to_next_rpt,
+ }
+end
+
+for _, name in pairs(safe_globals) do
+ static_env[name] = _G[name]
+end
+
+--The environment all code calls get is a table that has set static_env as metatable.
+--In general, every variable is local to a single code chunk, but kept persistent over code re-runs. Data is also saved, but functions and userdata and circular references are removed
+--Init code and step code's environments are not saved
+-- S - Table that can contain any save data global to the environment. Will be saved statically. Can't contain functions or userdata or circular references.
+-- F - Table global to the environment, can contain volatile data that is deleted when server quits.
+-- The init code should populate this table with functions and other definitions.
+
+local proxy_env={}
+--proxy_env gets a new metatable in every run, but is the shared environment of all functions ever defined.
+
+-- returns: true, fenv if successful; nil, error if error
+function env_proto:execute_code(localenv, code, evtdata, customfct)
+ -- create us a print function specific for this environment
+ if not self.safe_print_func then
+ local myenv = self
+ self.safe_print_func = function(...)
+ myenv:log("info", ...)
+ end
+ end
+
+ local metatbl ={
+ __index = function(t, i)
+ if i=="S" then
+ return self.sdata
+ elseif i=="F" then
+ return self.fdata
+ elseif i=="event" then
+ return evtdata
+ elseif customfct and customfct[i] then
+ return customfct[i]
+ elseif localenv and localenv[i] then
+ return localenv[i]
+ elseif i=="print" then
+ return self.safe_print_func
+ end
+ return static_env[i]
+ end,
+ __newindex = function(t, i, v)
+ if i=="S" or i=="F" or i=="event" or (customfct and customfct[i]) or static_env[i] then
+ debug.sethook()
+ error("Trying to overwrite environment contents")
+ end
+ localenv[i]=v
+ end,
+ }
+ setmetatable(proxy_env, metatbl)
+ local fun, err=loadstring(code)
+ if not fun then
+ return false, err
+ end
+
+ setfenv(fun, proxy_env)
+ local succ, data = pcall(fun)
+ if succ then
+ data=localenv
+ end
+ return succ, data
+end
+
+function env_proto:run_initcode()
+ if self.init_code and self.init_code~="" then
+ local old_fdata=self.fdata
+ self.fdata = {}
+ --atprint("[atlatc]Running initialization code for environment '"..self.name.."'")
+ local succ, err = self:execute_code({}, self.init_code, {type="init", init=true})
+ if not succ then
+ self:log("error", "Executing InitCode for '"..self.name.."' failed:"..err)
+ self.init_err=err
+ if old_fdata then
+ self.fdata=old_fdata
+ self:log("warning", "The 'F' table has been restored to the previous state.")
+ end
+ end
+ end
+end
+
+-- log to environment subscribers. severity can be "error", "warning" or "info" (used by internal print)
+function env_proto:log(severity, ...)
+ local text=advtrains.print_concat_table({"[atlatc "..self.name.." "..severity.."]", ...})
+ minetest.log("action", text)
+ for _, pname in ipairs(self.subscribers) do
+ minetest.chat_send_player(pname, text)
+ end
+end
+
+-- env.subscribers table may be directly altered by callers.
+
+
+--- class interface
+
+function atlatc.env_new(name)
+ local newenv={
+ name=name,
+ init_code="",
+ sdata={},
+ subscribers={},
+ }
+ setmetatable(newenv, {__index=env_proto})
+ return newenv
+end
+function atlatc.env_load(name, data)
+ local newenv={}
+ setmetatable(newenv, {__index=env_proto})
+ newenv:load(name, data)
+ return newenv
+end
+
+function atlatc.run_initcode()
+ for envname, env in pairs(atlatc.envs) do
+ env:run_initcode()
+ end
+end
+
+
+
+
diff --git a/advtrains_luaautomation/init.lua b/advtrains_luaautomation/init.lua
new file mode 100644
index 0000000..b6fe464
--- /dev/null
+++ b/advtrains_luaautomation/init.lua
@@ -0,0 +1,116 @@
+-- advtrains_luaautomation/init.lua
+-- Lua automation features for advtrains
+-- Uses global table 'atlatc' (AdvTrains_LuaATC)
+
+-- Boilerplate to support localized strings if intllib mod is installed.
+if intllib then
+ atltrans = intllib.Getter()
+else
+ atltrans = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end
+end
+
+--Privilege
+--Only trusted players should be enabled to build stuff which can break the server.
+
+atlatc = { envs = {}}
+
+minetest.register_privilege("atlatc", { description = "Player can place and modify LUA ATC components. Grant with care! Allows to execute bad LUA code.", give_to_singleplayer = false, default= false })
+
+--Size of code input forms in X,Y notation. Must be at least 10x10
+atlatc.CODE_FORM_SIZE = "15,12"
+
+--assertt helper. error if a variable is not of a type
+function assertt(var, typ)
+ if type(var)~=typ then
+ error("Assertion failed, variable has to be of type "..typ)
+ end
+end
+
+local mp=minetest.get_modpath("advtrains_luaautomation")
+if not mp then
+ error("Mod name error: Mod folder is not named 'advtrains_luaautomation'!")
+end
+dofile(mp.."/environment.lua")
+dofile(mp.."/interrupt.lua")
+dofile(mp.."/active_common.lua")
+dofile(mp.."/atc_rail.lua")
+dofile(mp.."/operation_panel.lua")
+dofile(mp.."/pcnaming.lua")
+
+dofile(mp.."/chatcmds.lua")
+
+
+local filename=minetest.get_worldpath().."/advtrains_luaautomation"
+
+function atlatc.load(tbl)
+ if tbl.version==1 then
+ for envname, data in pairs(tbl.envs) do
+ atlatc.envs[envname]=atlatc.env_load(envname, data)
+ end
+ atlatc.active.load(tbl.active)
+ atlatc.interrupt.load(tbl.interrupt)
+ atlatc.pcnaming.load(tbl.pcnaming)
+ end
+ -- run init code of all environments
+ atlatc.run_initcode()
+end
+
+function atlatc.load_pre_v4()
+ minetest.log("action", "[atlatc] Loading pre-v4 save file")
+ local file, err = io.open(filename, "r")
+ if not file then
+ minetest.log("warning", " Failed to read advtrains_luaautomation save data from file "..filename..": "..(err or "Unknown Error"))
+ minetest.log("warning", " (this is normal when first enabling advtrains on this world)")
+ else
+ atprint("luaautomation reading file:",filename)
+ local tbl = minetest.deserialize(file:read("*a"))
+ if type(tbl) == "table" then
+ if tbl.version==1 then
+ for envname, data in pairs(tbl.envs) do
+ atlatc.envs[envname]=atlatc.env_load(envname, data)
+ end
+ atlatc.active.load(tbl.active)
+ atlatc.interrupt.load(tbl.interrupt)
+ atlatc.pcnaming.load(tbl.pcnaming)
+ end
+ else
+ minetest.log("error", " Failed to read advtrains_luaautomation save data from file "..filename..": Not a table!")
+ end
+ file:close()
+ end
+ -- run init code of all environments
+ atlatc.run_initcode()
+end
+
+
+atlatc.save = function()
+ --versions:
+ -- 1 - Initial save format.
+
+ local envdata={}
+ for envname, env in pairs(atlatc.envs) do
+ envdata[envname]=env:save()
+ end
+ local save_tbl={
+ version = 1,
+ envs=envdata,
+ active = atlatc.active.save(),
+ interrupt = atlatc.interrupt.save(),
+ pcnaming = atlatc.pcnaming.save(),
+ }
+
+ return save_tbl
+end
+
+--[[
+-- globalstep for step code
+local timer, step_int=0, 2
+
+function atlatc.mainloop_stepcode(dtime)
+ timer=timer+dtime
+ if timer>step_int then
+ timer=0
+ atlatc.run_stepcode()
+ end
+end
+]]
diff --git a/advtrains_luaautomation/interrupt.lua b/advtrains_luaautomation/interrupt.lua
new file mode 100644
index 0000000..2e54ad8
--- /dev/null
+++ b/advtrains_luaautomation/interrupt.lua
@@ -0,0 +1,73 @@
+-- interrupt.lua
+-- implements interrupt queue
+
+--to be saved: pos and evtdata
+local iq={}
+local queue={}
+local timer=0
+local run=false
+
+function iq.load(data)
+ local d=data or {}
+ queue = d.queue or {}
+ timer = d.timer or 0
+end
+function iq.save()
+ return {queue = queue, timer=timer}
+end
+
+function iq.has_at_pos(pos)
+ for i=1,#queue do
+ local qe=queue[i]
+ if vector.equals(pos, qe.p) then
+ return true
+ end
+ end
+ return false
+end
+
+function iq.clear_ints_at_pos(pos)
+ local i=1
+ while i<=#queue do
+ local qe=queue[i]
+ if not qe then
+ table.remove(queue, i)
+ elseif vector.equals(pos, qe.p) and (qe.e.int or qe.e.ext_int) then
+ table.remove(queue, i)
+ else
+ i=i+1
+ end
+ end
+end
+
+function iq.add(t, pos, evtdata)
+ queue[#queue+1]={t=t+timer, p=pos, e=evtdata}
+ run=true
+end
+
+function iq.mainloop(dtime)
+ timer=timer + math.min(dtime, 0.2)
+ local i=1
+ while i<=#queue do
+ local qe=queue[i]
+ if not qe then
+ table.remove(queue, i)
+ elseif timer>qe.t then
+ table.remove(queue, i)
+ local pos, evtdata=qe.p, qe.e
+ local node=advtrains.ndb.get_node(pos)
+ local ndef=minetest.registered_nodes[node.name]
+ if ndef and ndef.luaautomation and ndef.luaautomation.fire_event then
+ ndef.luaautomation.fire_event(pos, evtdata)
+ else
+ atwarn("[atlatc][interrupt] Couldn't run event",evtdata.type,"on",pos,", something wrong with the node",node)
+ end
+ else
+ i=i+1
+ end
+ end
+end
+
+
+
+atlatc.interrupt=iq
diff --git a/advtrains_luaautomation/mod.conf b/advtrains_luaautomation/mod.conf
new file mode 100644
index 0000000..a737603
--- /dev/null
+++ b/advtrains_luaautomation/mod.conf
@@ -0,0 +1,7 @@
+name=advtrains_luaautomation
+title=Advanced Trains LuaATC
+description=Lua control interface to Advanced Trains
+author=orwell96
+
+depends=advtrains
+optional_depends=advtrains_interlocking,advtrains_line_automation,mesecons_switch
diff --git a/advtrains_luaautomation/operation_panel.lua b/advtrains_luaautomation/operation_panel.lua
new file mode 100644
index 0000000..f8b93b5
--- /dev/null
+++ b/advtrains_luaautomation/operation_panel.lua
@@ -0,0 +1,28 @@
+
+local function on_punch(pos, player)
+ atlatc.interrupt.add(0, pos, {type="punch", punch=true})
+end
+
+
+minetest.register_node("advtrains_luaautomation:oppanel", {
+ drawtype = "normal",
+ tiles={"atlatc_oppanel.png"},
+ description = "LuaAutomation operation panel",
+ groups = {
+ cracky = 1,
+ save_in_at_nodedb=1,
+ },
+ after_place_node = atlatc.active.after_place_node,
+ after_dig_node = atlatc.active.after_dig_node,
+ on_receive_fields = atlatc.active.on_receive_fields,
+ on_punch = on_punch,
+ luaautomation = {
+ fire_event=atlatc.active.run_in_env
+ },
+ digiline = {
+ receptor = {},
+ effector = {
+ action = atlatc.active.on_digiline_receive
+ },
+ },
+})
diff --git a/advtrains_luaautomation/p_display.lua b/advtrains_luaautomation/p_display.lua
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/advtrains_luaautomation/p_display.lua
diff --git a/advtrains_luaautomation/passive_api.txt b/advtrains_luaautomation/passive_api.txt
new file mode 100644
index 0000000..5ae1df4
--- /dev/null
+++ b/advtrains_luaautomation/passive_api.txt
@@ -0,0 +1,24 @@
+Lua Automation - Passive Component API
+
+Passive components are nodes that do not have code running in them. However, active components can query these and request actions from them. Examples:
+Switches
+Signals
+Displays
+Mesecon Transmitter
+Those passive components can also be used inside interlocking systems.
+
+All passive components have a table called 'advtrains' in their node definition and have the group 'save_in_at_nodedb' set, so they work in unloaded chunks.
+Example for a switch:
+advtrains = {
+ getstate = function(pos, node)
+ return "st"
+ end,
+ -- OR
+ getstate = "st",
+
+ setstate = function(pos, node, newstate)
+ if newstate=="cr" then
+ advtrains.ndb.swap_node(pos, <corresponding switch alt>)
+ end
+ end
+} \ No newline at end of file
diff --git a/advtrains_luaautomation/pcnaming.lua b/advtrains_luaautomation/pcnaming.lua
new file mode 100644
index 0000000..ebb769f
--- /dev/null
+++ b/advtrains_luaautomation/pcnaming.lua
@@ -0,0 +1,76 @@
+--pcnaming.lua
+--a.k.a Passive component naming
+--Allows to assign names to passive components, so they can be called like:
+--setstate("iamasignal", "green")
+atlatc.pcnaming={name_map={}}
+function atlatc.pcnaming.load(stuff)
+ if type(stuff)=="table" then
+ atlatc.pcnaming.name_map=stuff
+ end
+end
+function atlatc.pcnaming.save()
+ return atlatc.pcnaming.name_map
+end
+
+function atlatc.pcnaming.resolve_pos(pos, func_name)
+ if type(pos)=="string" then
+ local e = atlatc.pcnaming.name_map[pos]
+ if e then return e end
+ elseif type(pos)=="table" and pos.x and pos.y and pos.z then
+ return pos
+ end
+ error("Invalid position supplied to " .. (func_name or "???")..": " .. dump(pos))
+end
+
+minetest.register_craftitem("advtrains_luaautomation:pcnaming",{
+ description = attrans("Passive Component Naming Tool\n\nRight-click to name a passive component."),
+ groups = {cracky=1}, -- key=name, value=rating; rating=1..3.
+ inventory_image = "atlatc_pcnaming.png",
+ wield_image = "atlatc_pcnaming.png",
+ stack_max = 1,
+ on_place = function(itemstack, placer, pointed_thing)
+ local pname = placer:get_player_name()
+ if not pname then
+ return
+ end
+ if not minetest.check_player_privs(pname, {atlatc=true}) then
+ minetest.chat_send_player(pname, "Missing privilege: atlatc")
+ return
+ end
+ if pointed_thing.type=="node" then
+ local pos=pointed_thing.under
+ if advtrains.is_protected(pos, pname) then
+ minetest.record_protection_violation(pos, pname)
+ return
+ end
+ local node = advtrains.ndb.get_node(pos)
+ if node.name and (minetest.get_item_group(node.name, "advtrains_signal")>0 or advtrains.is_passive(pos)) then
+ --look if this one already has a name
+ local pn=""
+ for name, npos in pairs(atlatc.pcnaming.name_map) do
+ if vector.equals(npos, pos) then
+ pn=name
+ end
+ end
+ minetest.show_formspec(pname, "atlatc_naming_"..minetest.pos_to_string(pos), "field[pn;Set name of component (empty to clear);"..minetest.formspec_escape(pn).."]")
+ end
+ end
+ end,
+})
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ local pts=string.match(formname, "^atlatc_naming_(.+)")
+ if pts then
+ local pos=minetest.string_to_pos(pts)
+ if fields.pn then
+ --first remove all occurences
+ for name, npos in pairs(atlatc.pcnaming.name_map) do
+ if vector.equals(npos, pos) then
+ atlatc.pcnaming.name_map[name]=nil
+ end
+ end
+ if fields.pn~="" then
+ atlatc.pcnaming.name_map[fields.pn]=pos
+ end
+ end
+ end
+end)
diff --git a/advtrains_luaautomation/textures/atlatc_oppanel.png b/advtrains_luaautomation/textures/atlatc_oppanel.png
new file mode 100644
index 0000000..96eb30e
--- /dev/null
+++ b/advtrains_luaautomation/textures/atlatc_oppanel.png
Binary files differ
diff --git a/advtrains_luaautomation/textures/atlatc_pcnaming.png b/advtrains_luaautomation/textures/atlatc_pcnaming.png
new file mode 100644
index 0000000..3fccdfc
--- /dev/null
+++ b/advtrains_luaautomation/textures/atlatc_pcnaming.png
Binary files differ
diff --git a/advtrains_signals_ks/init.lua b/advtrains_signals_ks/init.lua
new file mode 100755
index 0000000..b05506e
--- /dev/null
+++ b/advtrains_signals_ks/init.lua
@@ -0,0 +1,286 @@
+-- Ks signals
+-- Can display main aspects (no Zs) + Sht
+
+-- Note that the group value of advtrains_signal is 2, which means "step 2 of signal capabilities"
+-- advtrains_signal=1 is meant for signals that do not implement set_aspect.
+
+local setaspectf = function(rot)
+ return function(pos, node, asp)
+ if asp.main == 0 then
+ if asp.shunt then
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_shunt_"..rot, param2 = node.param2})
+ else
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_danger_"..rot, param2 = node.param2})
+ end
+ else
+ if asp.dst ~= 0 and asp.main == -1 then
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_free_"..rot, param2 = node.param2})
+ else
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:hs_slow_"..rot, param2 = node.param2})
+ end
+ end
+ end
+end
+
+
+local suppasp = {
+ main = {0, 6, -1},
+ dst = {0, false},
+ shunt = nil,
+ proceed_as_main = true,
+ info = {
+ call_on = false,
+ dead_end = false,
+ w_speed = nil,
+ }
+}
+
+--Rangiersignal
+local setaspectf_ra = function(rot)
+ return function(pos, node, asp)
+ if asp.shunt then
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_shuntd_"..rot, param2 = node.param2})
+ else
+ advtrains.ndb.swap_node(pos, {name="advtrains_signals_ks:ra_danger_"..rot, param2 = node.param2})
+ end
+ local meta = minetest.get_meta(pos)
+ if meta then
+ meta:set_string("infotext", minetest.serialize(asp))
+ end
+ end
+end
+
+local suppasp_ra = {
+ main = { false },
+ dst = { false },
+ shunt = nil,
+ proceed_as_main = false,
+
+ info = {
+ call_on = false,
+ dead_end = false,
+ w_speed = nil,
+ }
+}
+
+advtrains.trackplacer.register_tracktype("advtrains_signals_ks:hs")
+advtrains.trackplacer.register_tracktype("advtrains_signals_ks:ra")
+advtrains.trackplacer.register_tracktype("advtrains_signals_ks:sign")
+advtrains.trackplacer.register_tracktype("advtrains_signals_ks:mast")
+
+for _, rtab in ipairs({
+ {rot = "0", sbox = {-1/8, -1/2, -1/2, 1/8, 1/2, -1/4}, ici=true},
+ {rot = "30", sbox = {-3/8, -1/2, -1/2, -1/8, 1/2, -1/4},},
+ {rot = "45", sbox = {-1/2, -1/2, -1/2, -1/4, 1/2, -1/4},},
+ {rot = "60", sbox = {-1/2, -1/2, -3/8, -1/4, 1/2, -1/8},},
+ }) do
+ local rot = rtab.rot
+ for typ, prts in pairs({
+ danger = {asp = advtrains.interlocking.DANGER, n = "slow", ici=true},
+ slow = {asp = { main = 6, proceed_as_main = true} , n = "free"},
+ free = {asp = { main = -1, proceed_as_main = true} , n = "shunt"},
+ shunt = {asp = { main = 0, shunt = true} , n = "danger"},
+ }) do
+ minetest.register_node("advtrains_signals_ks:hs_"..typ.."_"..rot, {
+ description = "Ks Main Signal",
+ drawtype = "mesh",
+ mesh = "advtrains_signals_ks_main_smr"..rot..".obj",
+ tiles = {"advtrains_signals_ks_mast.png", "advtrains_signals_ks_head.png", "advtrains_signals_ks_head.png", "advtrains_signals_ks_ltm_"..typ..".png"},
+
+ paramtype="light",
+ sunlight_propagates=true,
+ light_source = 4,
+
+ paramtype2 = "facedir",
+ selection_box = {
+ type = "fixed",
+ fixed = {rtab.sbox, {-1/4, -1/2, -1/4, 1/4, -7/16, 1/4}}
+ },
+ groups = {
+ cracky = 2,
+ advtrains_signal = 2,
+ not_blocking_trains = 1,
+ save_in_at_nodedb = 1,
+ not_in_creative_inventory = (rtab.ici and prts.ici) and 0 or 1,
+ },
+ drop = "advtrains_signals_ks:hs_danger_0",
+ inventory_image = "advtrains_signals_ks_hs_inv.png",
+ advtrains = {
+ set_aspect = setaspectf(rot),
+ supported_aspects = suppasp,
+ get_aspect = function(pos, node)
+ return prts.asp
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+ })
+ -- rotatable by trackworker
+ advtrains.trackplacer.add_worked("advtrains_signals_ks:hs", typ, "_"..rot, prts.n)
+ end
+
+
+ --Rangiersignale:
+ for typ, prts in pairs({
+ danger = {asp = { main = false, shunt = false }, n = "shuntd", ici=true},
+ shuntd = {asp = { main = false, shunt = true } , n = "danger"},
+ }) do
+ minetest.register_node("advtrains_signals_ks:ra_"..typ.."_"..rot, {
+ description = "Ks Shunting Signal",
+ drawtype = "mesh",
+ mesh = "advtrains_signals_ks_sht_smr"..rot..".obj",
+ tiles = {"advtrains_signals_ks_mast.png", "advtrains_signals_ks_head.png", "advtrains_signals_ks_head.png", "advtrains_signals_ks_ltm_"..typ..".png"},
+
+ paramtype="light",
+ sunlight_propagates=true,
+ light_source = 4,
+
+ paramtype2 = "facedir",
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 0, 1/4}
+ },
+ groups = {
+ cracky = 2,
+ advtrains_signal = 2,
+ not_blocking_trains = 1,
+ save_in_at_nodedb = 1,
+ not_in_creative_inventory = (rtab.ici and prts.ici) and 0 or 1,
+ },
+ drop = "advtrains_signals_ks:ra_danger_0",
+ inventory_image = "advtrains_signals_ks_ra_inv.png",
+ advtrains = {
+ set_aspect = setaspectf_ra(rot),
+ supported_aspects = suppasp_ra,
+ get_aspect = function(pos, node)
+ return prts.asp
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+ })
+ -- rotatable by trackworker
+ advtrains.trackplacer.add_worked("advtrains_signals_ks:ra", typ, "_"..rot, prts.n)
+ end
+
+ --Schilder:
+ for typ, prts in pairs({
+ -- Speed restrictions:
+ ["8"] = {asp = { main = 8, shunt = true }, n = "12", ici=true},
+ ["12"] = {asp = { main = 12, shunt = true }, n = "16"},
+ ["16"] = {asp = { main = 16, shunt = true }, n = "e"},
+ -- Speed restriction lifted
+ ["e"] = {asp = { main = -1, shunt = true }, n = "hfs"},
+ -- Halt for shunt moves:
+ ["hfs"] = {asp = { main = false, shunt = false }, n = "pam"},
+ ["pam"] = {asp = { main = -1, shunt = false, proceed_as_main = true}, n = "8"},
+ }) do
+ minetest.register_node("advtrains_signals_ks:sign_"..typ.."_"..rot, {
+ description = "Signal Sign",
+ drawtype = "mesh",
+ mesh = "advtrains_signals_ks_sign_smr"..rot..".obj",
+ tiles = {"advtrains_signals_ks_signpost.png", "advtrains_signals_ks_sign_"..typ..".png"},
+
+ paramtype="light",
+ sunlight_propagates=true,
+ light_source = 4,
+
+ paramtype2 = "facedir",
+ selection_box = {
+ type = "fixed",
+ fixed = {rtab.sbox, {-1/4, -1/2, -1/4, 1/4, -7/16, 1/4}}
+ },
+ groups = {
+ cracky = 2,
+ advtrains_signal = 2,
+ not_blocking_trains = 1,
+ save_in_at_nodedb = 1,
+ not_in_creative_inventory = (rtab.ici and prts.ici) and 0 or 1,
+ },
+ drop = "advtrains_signals_ks:sign_8_0",
+ inventory_image = "advtrains_signals_ks_sign_8.png",
+ advtrains = {
+ -- This is a static signal! No set_aspect
+ get_aspect = function(pos, node)
+ return prts.asp
+ end,
+ },
+ on_rightclick = advtrains.interlocking.signal_rc_handler,
+ can_dig = advtrains.interlocking.signal_can_dig,
+ after_dig_node = advtrains.interlocking.signal_after_dig,
+ })
+ -- rotatable by trackworker
+ advtrains.trackplacer.add_worked("advtrains_signals_ks:sign", typ, "_"..rot, prts.n)
+ end
+
+ minetest.register_node("advtrains_signals_ks:mast_mast_"..rot, {
+ description = "Ks Mast",
+ drawtype = "mesh",
+ mesh = "advtrains_signals_ks_mast_smr"..rot..".obj",
+ tiles = {"advtrains_signals_ks_mast.png"},
+
+ paramtype="light",
+ sunlight_propagates=true,
+ --light_source = 4,
+
+ paramtype2 = "facedir",
+ selection_box = {
+ type = "fixed",
+ fixed = {rtab.sbox, {-1/4, -1/2, -1/4, 1/4, -7/16, 1/4}}
+ },
+ groups = {
+ cracky = 2,
+ not_blocking_trains = 1,
+ not_in_creative_inventory = (rtab.ici) and 0 or 1,
+ },
+ drop = "advtrains_signals_ks:mast_mast_0",
+ })
+ advtrains.trackplacer.add_worked("advtrains_signals_ks:mast","mast", "_"..rot)
+end
+
+-- Crafting
+
+minetest.register_craft({
+ output = "advtrains_signals_ks:hs_danger_0 2",
+ recipe = {
+ {'default:steel_ingot', 'dye:red', 'default:steel_ingot'},
+ {'dye:yellow', 'default:steel_ingot', 'dye:dark_green'},
+ {'default:steel_ingot', 'advtrains_signals_ks:mast_mast_0', 'default:steel_ingot'},
+ },
+})
+
+minetest.register_craft({
+ output = "advtrains_signals_ks:mast_mast_0 10",
+ recipe = {
+ {'default:steel_ingot'},
+ {'dye:cyan'},
+ {'default:steel_ingot'},
+ },
+})
+
+minetest.register_craft({
+ output = "advtrains_signals_ks:ra_danger_0 2",
+ recipe = {
+ {'dye:red', 'dye:white', 'dye:red'},
+ {'dye:white', 'default:steel_ingot', 'default:steel_ingot'},
+ {'default:steel_ingot', 'advtrains_signals_ks:mast_mast_0', 'default:steel_ingot'},
+ },
+})
+
+local sign_material = "default:sign_wall_steel" --fallback
+if minetest.get_modpath("basic_materials") then
+ sign_material = "basic_materials:plastic_sheet"
+end
+--print("Sign Material: "..sign_material)
+
+minetest.register_craft({
+ output = "advtrains_signals_ks:sign_8_0 2",
+ recipe = {
+ {sign_material, 'dye:black'},
+ {'default:stick', ''},
+ {'default:stick', ''},
+ },
+})
+sign_material = nil
diff --git a/advtrains_signals_ks/init_degrotate_nodes.lua b/advtrains_signals_ks/init_degrotate_nodes.lua
new file mode 100644
index 0000000..da1c980
--- /dev/null
+++ b/advtrains_signals_ks/init_degrotate_nodes.lua
@@ -0,0 +1,94 @@
+-- Ks Signals for advtrains
+-- will implement the advtrains signal API
+
+local function place_degrotate(pos, placer, itemstack, pointed_thing)
+ local yaw = placer:get_look_horizontal()
+ local param = math.floor(yaw * 90 / math.pi + 0.5)
+ local n = minetest.get_node(pos)
+ n.param2 = param
+ minetest.set_node(pos, n)
+end
+
+minetest.register_node("advtrains_signals_ks:mast", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="degrotate",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4},
+ },
+ mesh = "advtrains_signals_ks_mast.obj",
+ tiles = {"advtrains_signals_ks_mast.png"},
+ description="Ks Signal Mast",
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ --save_in_at_nodedb=2,
+ },
+ after_place_node = place_degrotate,
+})
+
+minetest.register_node("advtrains_signals_ks:head_main", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="degrotate",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4},
+ },
+ mesh = "advtrains_signals_ks_head_main.obj",
+ tiles = {"advtrains_signals_ks_mast.png", "advtrains_signals_ks_head.png"},
+ description="Ks Main Signal Screen",
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ --save_in_at_nodedb=2,
+ },
+ after_place_node = place_degrotate,
+})
+
+minetest.register_node("advtrains_signals_ks:zs_top", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="degrotate",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4},
+ },
+ mesh = "advtrains_signals_ks_zs_top.obj",
+ tiles = {"advtrains_signals_ks_mast.png", "advtrains_signals_ks_head.png"},
+ description="Ks Speed Restriction Signal (top)",
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ --save_in_at_nodedb=2,
+ },
+ after_place_node = place_degrotate,
+})
+
+minetest.register_node("advtrains_signals_ks:zs_bottom", {
+ drawtype = "mesh",
+ paramtype="light",
+ paramtype2="degrotate",
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/4, -1/2, -1/4, 1/4, 1/2, 1/4},
+ },
+ mesh = "advtrains_signals_ks_zs_bottom.obj",
+ tiles = {"advtrains_signals_ks_mast.png", "advtrains_signals_ks_head.png"},
+ description="Ks Speed Restriction Signal (bottom)",
+ sunlight_propagates=true,
+ groups = {
+ cracky=3,
+ not_blocking_trains=1,
+ --save_in_at_nodedb=2,
+ },
+ after_place_node = place_degrotate,
+})
diff --git a/advtrains_signals_ks/mod.conf b/advtrains_signals_ks/mod.conf
new file mode 100644
index 0000000..7036efa
--- /dev/null
+++ b/advtrains_signals_ks/mod.conf
@@ -0,0 +1,6 @@
+name=advtrains_signals_ks
+title=Advtrains Interlocking Signal Set - German 'Ks' signals
+description=Default signal set for the Advanced Trains Interlocking system
+author=orwell96
+
+depends=advtrains_interlocking
diff --git a/advtrains_signals_ks/models/advtrains_signals_ks_head_dist.obj b/advtrains_signals_ks/models/advtrains_signals_ks_head_dist.obj
new file mode 100644
index 0000000..28399e1
--- /dev/null
+++ b/advtrains_signals_ks/models/advtrains_signals_ks_head_dist.obj
@@ -0,0 +1,270 @@
+# Blender v2.76 (sub 0) OBJ File: 'signal_ks_export.blend'
+# www.blender.org
+mtllib advtrains_signals_ks_head_dist.mtl
+o VSSignalschirm_Cube.003
+v 0.028581 -0.498051 0.268412
+v -0.028581 -0.498051 0.268412
+v -0.028581 -0.498051 0.496774
+v 0.028581 -0.498051 0.496774
+v 0.028581 -0.498051 0.325575
+v 0.028581 -0.498051 0.439611
+v -0.028581 -0.498051 0.325575
+v -0.028581 -0.498051 0.439611
+v -0.028581 -0.292188 0.439611
+v -0.028581 -0.164298 0.439611
+v 0.028581 -0.164298 0.439611
+v 0.028581 -0.292188 0.439611
+v 0.028581 -0.292188 0.496774
+v 0.028581 -0.164298 0.496774
+v -0.028581 -0.164298 0.496774
+v -0.028581 -0.292188 0.496774
+v -0.028581 -0.292188 0.325575
+v -0.028581 -0.164298 0.325575
+v -0.028581 -0.164298 0.268412
+v -0.028581 -0.292188 0.268412
+v 0.028581 -0.292188 0.268412
+v 0.028581 -0.164298 0.268412
+v 0.028581 -0.164298 0.325575
+v 0.028581 -0.292188 0.325575
+v -0.028581 -0.163378 0.439611
+v 0.028581 -0.163378 0.439611
+v 0.028581 -0.163378 0.496774
+v -0.028581 -0.163378 0.496774
+v -0.028581 -0.163378 0.325575
+v -0.028581 -0.163378 0.268412
+v 0.028581 -0.163378 0.268412
+v 0.028581 -0.163378 0.325575
+v 0.028581 -0.163378 0.268412
+v -0.028581 -0.163378 0.268412
+v -0.028581 -0.163378 0.496774
+v 0.028581 -0.163378 0.496774
+v 0.028581 -0.163378 0.325575
+v 0.028581 -0.163378 0.439611
+v -0.028581 -0.163378 0.325575
+v -0.028581 -0.163378 0.439611
+v -0.028581 0.040885 0.439611
+v -0.028581 0.167782 0.439611
+v 0.028581 0.167782 0.439611
+v 0.028581 0.040885 0.439611
+v 0.028581 0.040885 0.496774
+v 0.028581 0.167782 0.496774
+v -0.028581 0.167782 0.496774
+v -0.028581 0.040885 0.496774
+v -0.028581 0.040885 0.325575
+v -0.028581 0.167782 0.325575
+v -0.028581 0.167782 0.268412
+v -0.028581 0.040885 0.268412
+v 0.028581 0.040885 0.268412
+v 0.028581 0.167782 0.268412
+v 0.028581 0.167782 0.325575
+v 0.028581 0.040885 0.325575
+v -0.269369 -0.249462 0.548486
+v 0.230631 -0.249462 0.548486
+v -0.269369 0.273111 0.548486
+v 0.230631 0.273111 0.548486
+v -0.269369 0.273111 0.501067
+v -0.269369 -0.249462 0.501068
+v 0.230631 -0.249462 0.501068
+v 0.230631 0.273111 0.501067
+vt 0.538102 0.501950
+vt 0.538102 0.192341
+vt 0.615085 0.192341
+vt 0.538102 0.000000
+vt 0.615085 0.000000
+vt 0.000000 0.501950
+vt 0.000000 0.192341
+vt 0.076983 0.192341
+vt 0.000000 0.000000
+vt 0.076983 0.000000
+vt 0.615085 0.501950
+vt 0.692068 0.192341
+vt 0.692068 0.000000
+vt 0.692068 0.501950
+vt 0.769051 0.192341
+vt 0.769051 0.000000
+vt 0.769051 0.501950
+vt 0.846034 0.192341
+vt 0.846034 0.000000
+vt 0.846034 0.996502
+vt 0.846034 0.686893
+vt 0.923017 0.686893
+vt 0.307543 0.501950
+vt 0.307542 0.192341
+vt 0.384525 0.192341
+vt 0.307542 0.000000
+vt 0.384525 0.000000
+vt 0.230559 0.501950
+vt 0.230559 0.192341
+vt 0.230559 0.000000
+vt 0.923017 0.478709
+vt 1.000000 0.478709
+vt 1.000000 0.650215
+vt 0.846034 0.600923
+vt 0.846034 0.429417
+vt 0.923017 0.429417
+vt 0.923017 0.600923
+vt 0.846034 0.343447
+vt 0.923017 0.343447
+vt 0.538102 1.000000
+vt 0.538102 0.692796
+vt 0.615085 0.692796
+vt 0.000000 1.000000
+vt 0.000000 0.692797
+vt 0.076983 0.692797
+vt 0.076983 0.501950
+vt 0.692068 1.000000
+vt 0.692068 0.692796
+vt 0.769051 0.692796
+vt 0.692068 0.809153
+vt 0.615085 0.809153
+vt 0.615085 1.000000
+vt 0.769051 1.000000
+vt 0.846034 0.692796
+vt 0.846034 0.501950
+vt 0.923017 0.307203
+vt 0.923017 0.000000
+vt 1.000000 0.000000
+vt 0.307543 1.000000
+vt 0.307542 0.692797
+vt 0.384525 0.692797
+vt 0.384525 0.501950
+vt 0.230559 1.000000
+vt 0.230559 0.692797
+vt 1.000000 0.307203
+vt 0.846034 0.257476
+vt 0.846034 0.085970
+vt 0.923017 0.085970
+vt 0.923017 0.257476
+vt 0.923017 0.996502
+vt 0.923017 0.650215
+vt 0.076983 1.000000
+vt 0.846034 1.000000
+vt 0.384526 1.000000
+vt 0.500000 0.500000
+vt 0.500000 0.000000
+vt 0.047418 1.000000
+vt 0.047418 0.500000
+vt 0.094836 0.500000
+vt 0.142255 0.500000
+vt 0.142255 0.978402
+vt 0.094836 0.978402
+vt 0.189673 0.500000
+vt 1.000000 0.500000
+vt 0.000000 0.500000
+vt 0.094836 1.000000
+vt 0.189673 0.978402
+vn 1.000000 0.000000 0.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+g VSSignalschirm_Cube.003_Mast
+usemtl Mast
+s off
+f 6/1/1 12/2/1 13/3/1
+f 12/2/1 11/4/1 14/5/1
+f 3/6/2 16/7/2 9/8/2
+f 16/7/2 15/9/2 10/10/2
+f 4/11/3 13/3/3 16/12/3
+f 13/3/3 14/5/3 15/13/3
+f 8/14/4 9/12/4 12/15/4
+f 9/12/4 10/13/4 11/16/4
+f 2/17/4 20/15/4 21/18/4
+f 20/15/4 19/16/4 22/19/4
+f 5/20/3 24/21/3 17/22/3
+f 1/23/1 21/24/1 24/25/1
+f 21/24/1 22/26/1 23/27/1
+f 7/28/2 17/29/2 20/24/2
+f 17/29/2 18/30/2 19/26/2
+f 23/27/1 11/4/1 12/2/1
+f 17/31/5 24/32/5 12/33/5
+f 17/29/2 9/8/2 10/10/2
+f 18/34/6 10/35/6 11/36/6
+f 18/34/6 23/37/6 22/22/6
+f 10/35/6 15/38/6 14/39/6
+f 29/9/6 25/9/6 26/9/6
+f 38/40/1 44/41/1 45/42/1
+f 44/41/1 43/1/1 46/11/1
+f 35/43/2 48/44/2 41/45/2
+f 48/44/2 47/6/2 42/46/2
+f 36/47/3 45/48/3 48/49/3
+f 45/48/3 46/14/3 47/17/3
+f 40/14/4 41/50/4 44/51/4
+f 41/50/4 42/47/4 43/52/4
+f 34/53/4 52/49/4 53/54/4
+f 52/49/4 51/17/4 54/55/4
+f 37/56/3 56/57/3 49/58/3
+f 33/59/1 53/60/1 56/61/1
+f 53/60/1 54/23/1 55/62/1
+f 39/63/2 49/64/2 52/60/2
+f 49/64/2 50/28/2 51/23/2
+f 55/62/1 43/1/1 44/41/1
+f 49/56/5 56/65/5 44/32/5
+f 49/64/2 41/45/2 42/46/2
+f 50/66/6 42/67/6 43/68/6
+f 50/66/6 55/69/6 54/39/6
+f 42/67/6 47/19/6 46/57/6
+f 4/11/1 6/1/1 13/3/1
+f 13/3/1 12/2/1 14/5/1
+f 8/46/2 3/6/2 9/8/2
+f 9/8/2 16/7/2 10/10/2
+f 3/14/3 4/11/3 16/12/3
+f 16/12/3 13/3/3 15/13/3
+f 6/17/4 8/14/4 12/15/4
+f 12/15/4 9/12/4 11/16/4
+f 1/55/4 2/17/4 21/18/4
+f 21/18/4 20/15/4 22/19/4
+f 7/70/3 5/20/3 17/22/3
+f 5/62/1 1/23/1 24/25/1
+f 24/25/1 21/24/1 23/27/1
+f 2/23/2 7/28/2 20/24/2
+f 20/24/2 17/29/2 19/26/2
+f 24/25/1 23/27/1 12/2/1
+f 9/71/5 17/31/5 12/33/5
+f 18/30/2 17/29/2 10/10/2
+f 23/37/6 18/34/6 11/36/6
+f 19/21/6 18/34/6 22/22/6
+f 11/36/6 10/35/6 14/39/6
+f 32/9/6 29/9/6 26/9/6
+f 36/52/1 38/40/1 45/42/1
+f 45/42/1 44/41/1 46/11/1
+f 40/72/2 35/43/2 41/45/2
+f 41/45/2 48/44/2 42/46/2
+f 35/53/3 36/47/3 48/49/3
+f 48/49/3 45/48/3 47/17/3
+f 38/11/4 40/14/4 44/51/4
+f 44/51/4 41/50/4 43/52/4
+f 33/73/4 34/53/4 53/54/4
+f 53/54/4 52/49/4 54/55/4
+f 39/65/3 37/56/3 49/58/3
+f 37/74/1 33/59/1 56/61/1
+f 56/61/1 53/60/1 55/62/1
+f 34/59/2 39/63/2 52/60/2
+f 52/60/2 49/64/2 51/23/2
+f 56/61/1 55/62/1 44/41/1
+f 41/31/5 49/56/5 44/32/5
+f 50/28/2 49/64/2 42/46/2
+f 55/69/6 50/66/6 43/68/6
+f 51/38/6 50/66/6 54/39/6
+f 43/68/6 42/67/6 46/57/6
+g VSSignalschirm_Cube.003_Schirm
+usemtl Schirm
+f 58/75/3 60/76/3 59/58/3
+f 61/9/4 64/76/4 63/75/4
+f 61/77/2 62/78/2 57/79/2
+f 64/80/6 61/81/6 59/82/6
+f 62/81/5 63/80/5 58/83/5
+f 63/78/1 64/77/1 60/43/1
+f 57/84/3 58/75/3 59/58/3
+f 62/85/4 61/9/4 63/75/4
+f 59/86/2 61/77/2 57/79/2
+f 60/79/6 64/80/6 59/82/6
+f 57/87/5 62/81/5 58/83/5
+f 58/85/1 63/78/1 60/43/1
+l 26 27
+l 28 25
+l 27 28
+l 30 31
+l 31 32
+l 29 30
diff --git a/advtrains_signals_ks/models/advtrains_signals_ks_head_main.obj b/advtrains_signals_ks/models/advtrains_signals_ks_head_main.obj
new file mode 100644
index 0000000..283ef46
--- /dev/null
+++ b/advtrains_signals_ks/models/advtrains_signals_ks_head_main.obj
@@ -0,0 +1,286 @@
+# Blender v2.76 (sub 0) OBJ File: 'signal_ks_export.blend'
+# www.blender.org
+mtllib advtrains_signals_ks_head_main.mtl
+o HSSignalschirm_Cube.002
+v -0.028581 -0.498051 0.439611
+v 0.028581 -0.498051 0.439611
+v 0.028581 -0.498051 0.496774
+v -0.028581 -0.498051 0.496774
+v -0.028581 -0.498051 0.325575
+v -0.028581 -0.498051 0.268412
+v 0.028581 -0.498051 0.268412
+v 0.028581 -0.498051 0.325575
+v 0.028581 -0.498051 0.268412
+v -0.028581 -0.498051 0.268412
+v -0.028581 -0.498051 0.496774
+v 0.028581 -0.498051 0.496774
+v 0.028581 -0.498051 0.325575
+v 0.028581 -0.498051 0.439611
+v -0.028581 -0.498051 0.325575
+v -0.028581 -0.498051 0.439611
+v -0.028581 -0.292188 0.439611
+v -0.028581 -0.164298 0.439611
+v 0.028581 -0.164298 0.439611
+v 0.028581 -0.292188 0.439611
+v 0.028581 -0.292188 0.496774
+v 0.028581 -0.164298 0.496774
+v -0.028581 -0.164298 0.496774
+v -0.028581 -0.292188 0.496774
+v -0.028581 -0.292188 0.325575
+v -0.028581 -0.164298 0.325575
+v -0.028581 -0.164298 0.268412
+v -0.028581 -0.292188 0.268412
+v 0.028581 -0.292188 0.268412
+v 0.028581 -0.164298 0.268412
+v 0.028581 -0.164298 0.325575
+v 0.028581 -0.292188 0.325575
+v -0.028581 -0.163378 0.439611
+v 0.028581 -0.163378 0.439611
+v 0.028581 -0.163378 0.496774
+v -0.028581 -0.163378 0.496774
+v -0.028581 -0.163378 0.325575
+v -0.028581 -0.163378 0.268412
+v 0.028581 -0.163378 0.268412
+v 0.028581 -0.163378 0.325575
+v 0.028581 -0.163378 0.268412
+v -0.028581 -0.163378 0.268412
+v -0.028581 -0.163378 0.496774
+v 0.028581 -0.163378 0.496774
+v 0.028581 -0.163378 0.325575
+v 0.028581 -0.163378 0.439611
+v -0.028581 -0.163378 0.325575
+v -0.028581 -0.163378 0.439611
+v -0.028581 0.040885 0.439611
+v -0.028581 0.167782 0.439611
+v 0.028581 0.167782 0.439611
+v 0.028581 0.040885 0.439611
+v 0.028581 0.040885 0.496774
+v 0.028581 0.167782 0.496774
+v -0.028581 0.167782 0.496774
+v -0.028581 0.040885 0.496774
+v -0.028581 0.040885 0.325575
+v -0.028581 0.167782 0.325575
+v -0.028581 0.167782 0.268412
+v -0.028581 0.040885 0.268412
+v 0.028581 0.040885 0.268412
+v 0.028581 0.167782 0.268412
+v 0.028581 0.167782 0.325575
+v 0.028581 0.040885 0.325575
+v -0.269369 -0.475166 0.548486
+v 0.230631 -0.475166 0.548486
+v -0.269369 0.273749 0.548486
+v 0.230631 0.273749 0.548486
+v -0.269369 0.273749 0.501067
+v -0.269369 -0.475166 0.501068
+v 0.230631 -0.475166 0.501068
+v 0.230631 0.273749 0.501067
+vt 0.000000 0.000000
+vt 0.538102 0.501950
+vt 0.538102 0.192341
+vt 0.615085 0.192341
+vt 0.538102 0.000000
+vt 0.615085 0.000000
+vt 0.000000 0.501950
+vt 0.000000 0.192341
+vt 0.076983 0.192341
+vt 0.076983 0.000000
+vt 0.615085 0.501950
+vt 0.692068 0.192341
+vt 0.692068 0.000000
+vt 0.692068 0.501950
+vt 0.769051 0.192341
+vt 0.769051 0.000000
+vt 0.769051 0.501950
+vt 0.846034 0.192341
+vt 0.846034 0.000000
+vt 0.846034 0.996502
+vt 0.846034 0.686893
+vt 0.923017 0.686893
+vt 0.307543 0.501950
+vt 0.307542 0.192341
+vt 0.384525 0.192341
+vt 0.307542 0.000000
+vt 0.384525 0.000000
+vt 0.230559 0.501950
+vt 0.230559 0.192341
+vt 0.230559 0.000000
+vt 0.923017 0.478709
+vt 1.000000 0.478709
+vt 1.000000 0.650215
+vt 0.846034 0.600923
+vt 0.846034 0.429417
+vt 0.923017 0.429417
+vt 0.923017 0.600923
+vt 0.846034 0.343447
+vt 0.923017 0.343447
+vt 0.538102 1.000000
+vt 0.538102 0.692796
+vt 0.615085 0.692796
+vt 0.000000 1.000000
+vt 0.000000 0.692797
+vt 0.076983 0.692797
+vt 0.076983 0.501950
+vt 0.692068 1.000000
+vt 0.692068 0.692796
+vt 0.769051 0.692796
+vt 0.692068 0.809153
+vt 0.615085 0.809153
+vt 0.615085 1.000000
+vt 0.769051 1.000000
+vt 0.846034 0.692796
+vt 0.846034 0.501950
+vt 0.923017 0.307203
+vt 0.923017 0.000000
+vt 1.000000 0.000000
+vt 0.307543 1.000000
+vt 0.307542 0.692797
+vt 0.384525 0.692797
+vt 0.384525 0.501950
+vt 0.230559 1.000000
+vt 0.230559 0.692797
+vt 1.000000 0.307203
+vt 0.846034 0.257476
+vt 0.846034 0.085970
+vt 0.923017 0.085970
+vt 0.923017 0.257476
+vt 0.923017 0.996502
+vt 0.923017 0.650215
+vt 0.076983 1.000000
+vt 0.846034 1.000000
+vt 0.384526 1.000000
+vt 0.437731 0.748915
+vt 0.437731 0.000000
+vt 0.875462 0.000000
+vt 0.000000 0.748915
+vt 0.916975 0.748915
+vt 0.916975 0.000000
+vt 0.958487 0.000000
+vt 1.000000 0.500000
+vt 0.958487 0.500000
+vt 0.958487 1.000000
+vt 0.875462 0.748915
+vt 0.958487 0.748915
+vt 1.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn -1.000000 0.000000 -0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+g HSSignalschirm_Cube.002_Mast
+usemtl Mast
+s off
+f 5/1/1 1/1/1 2/1/1
+f 14/2/2 20/3/2 21/4/2
+f 20/3/2 19/5/2 22/6/2
+f 11/7/3 24/8/3 17/9/3
+f 24/8/3 23/1/3 18/10/3
+f 12/11/4 21/4/4 24/12/4
+f 21/4/4 22/6/4 23/13/4
+f 16/14/5 17/12/5 20/15/5
+f 17/12/5 18/13/5 19/16/5
+f 10/17/5 28/15/5 29/18/5
+f 28/15/5 27/16/5 30/19/5
+f 13/20/4 32/21/4 25/22/4
+f 9/23/2 29/24/2 32/25/2
+f 29/24/2 30/26/2 31/27/2
+f 15/28/3 25/29/3 28/24/3
+f 25/29/3 26/30/3 27/26/3
+f 31/27/2 19/5/2 20/3/2
+f 25/31/6 32/32/6 20/33/6
+f 25/29/3 17/9/3 18/10/3
+f 26/34/1 18/35/1 19/36/1
+f 26/34/1 31/37/1 30/22/1
+f 18/35/1 23/38/1 22/39/1
+f 37/1/1 33/1/1 34/1/1
+f 46/40/2 52/41/2 53/42/2
+f 52/41/2 51/2/2 54/11/2
+f 43/43/3 56/44/3 49/45/3
+f 56/44/3 55/7/3 50/46/3
+f 44/47/4 53/48/4 56/49/4
+f 53/48/4 54/14/4 55/17/4
+f 48/14/5 49/50/5 52/51/5
+f 49/50/5 50/47/5 51/52/5
+f 42/53/5 60/49/5 61/54/5
+f 60/49/5 59/17/5 62/55/5
+f 45/56/4 64/57/4 57/58/4
+f 41/59/2 61/60/2 64/61/2
+f 61/60/2 62/23/2 63/62/2
+f 47/63/3 57/64/3 60/60/3
+f 57/64/3 58/28/3 59/23/3
+f 63/62/2 51/2/2 52/41/2
+f 57/56/6 64/65/6 52/32/6
+f 57/64/3 49/45/3 50/46/3
+f 58/66/1 50/67/1 51/68/1
+f 58/66/1 63/69/1 62/39/1
+f 50/67/1 55/19/1 54/57/1
+f 8/1/1 5/1/1 2/1/1
+f 12/11/2 14/2/2 21/4/2
+f 21/4/2 20/3/2 22/6/2
+f 16/46/3 11/7/3 17/9/3
+f 17/9/3 24/8/3 18/10/3
+f 11/14/4 12/11/4 24/12/4
+f 24/12/4 21/4/4 23/13/4
+f 14/17/5 16/14/5 20/15/5
+f 20/15/5 17/12/5 19/16/5
+f 9/55/5 10/17/5 29/18/5
+f 29/18/5 28/15/5 30/19/5
+f 15/70/4 13/20/4 25/22/4
+f 13/62/2 9/23/2 32/25/2
+f 32/25/2 29/24/2 31/27/2
+f 10/23/3 15/28/3 28/24/3
+f 28/24/3 25/29/3 27/26/3
+f 32/25/2 31/27/2 20/3/2
+f 17/71/6 25/31/6 20/33/6
+f 26/30/3 25/29/3 18/10/3
+f 31/37/1 26/34/1 19/36/1
+f 27/21/1 26/34/1 30/22/1
+f 19/36/1 18/35/1 22/39/1
+f 40/1/1 37/1/1 34/1/1
+f 44/52/2 46/40/2 53/42/2
+f 53/42/2 52/41/2 54/11/2
+f 48/72/3 43/43/3 49/45/3
+f 49/45/3 56/44/3 50/46/3
+f 43/53/4 44/47/4 56/49/4
+f 56/49/4 53/48/4 55/17/4
+f 46/11/5 48/14/5 52/51/5
+f 52/51/5 49/50/5 51/52/5
+f 41/73/5 42/53/5 61/54/5
+f 61/54/5 60/49/5 62/55/5
+f 47/65/4 45/56/4 57/58/4
+f 45/74/2 41/59/2 64/61/2
+f 64/61/2 61/60/2 63/62/2
+f 42/59/3 47/63/3 60/60/3
+f 60/60/3 57/64/3 59/23/3
+f 64/61/2 63/62/2 52/41/2
+f 49/31/6 57/56/6 52/32/6
+f 58/28/3 57/64/3 50/46/3
+f 63/69/1 58/66/1 51/68/1
+f 59/38/1 58/66/1 62/39/1
+f 51/68/1 50/67/1 54/57/1
+g HSSignalschirm_Cube.002_Schirm
+usemtl Schirm
+f 66/75/4 68/76/4 67/77/4
+f 69/75/5 72/78/5 71/1/5
+f 69/79/3 70/80/3 65/81/3
+f 72/58/1 69/82/1 67/83/1
+f 70/84/6 71/83/6 66/82/6
+f 71/80/2 72/79/2 68/85/2
+f 65/85/4 66/75/4 67/77/4
+f 70/76/5 69/75/5 71/1/5
+f 67/86/3 69/79/3 65/81/3
+f 68/81/1 72/58/1 67/83/1
+f 65/87/6 70/84/6 66/82/6
+f 66/77/2 71/80/2 68/85/2
+l 2 3
+l 4 1
+l 3 4
+l 6 7
+l 7 8
+l 5 6
+l 34 35
+l 36 33
+l 35 36
+l 38 39
+l 39 40
+l 37 38
diff --git a/advtrains_signals_ks/models/advtrains_signals_ks_lamps_dist.obj b/advtrains_signals_ks/models/advtrains_signals_ks_lamps_dist.obj
new file mode 100644
index 0000000..c4f8a61
--- /dev/null
+++ b/advtrains_signals_ks/models/advtrains_signals_ks_lamps_dist.obj
@@ -0,0 +1,1989 @@
+# Blender v2.76 (sub 0) OBJ File: 'signal_ks_export.blend'
+# www.blender.org
+mtllib advtrains_signals_ks_lamps_dist.mtl
+o VSLampen_Cube.004
+v 1.356099 0.195342 5.864285
+v 1.356098 0.189842 5.594683
+v 1.324589 0.300536 6.389298
+v 1.324588 0.295033 5.594215
+v 1.273160 0.397563 6.388865
+v 1.273160 0.392062 5.593781
+v 1.203793 0.482692 6.388486
+v 1.203794 0.477191 5.593402
+v 1.119149 0.552651 6.388174
+v 1.119148 0.547151 5.593091
+v 1.022483 0.604755 6.387940
+v 1.022485 0.599256 5.592857
+v 0.917511 0.636999 6.387797
+v 0.917511 0.631500 5.592714
+v 0.808263 0.648141 6.387748
+v 0.808264 0.642644 5.592665
+v 0.698939 0.637761 6.387794
+v 0.698940 0.632259 5.592710
+v 0.593745 0.606253 6.387936
+v 0.593744 0.600751 5.592852
+v 0.496716 0.554824 6.388164
+v 0.496716 0.549324 5.593081
+v 0.411588 0.485459 6.388473
+v 0.411586 0.479957 5.593389
+v 0.341628 0.400816 6.388850
+v 0.341625 0.395314 5.593767
+v 0.289521 0.304149 6.389282
+v 0.289522 0.298649 5.594199
+v 0.257278 0.199179 5.864267
+v 0.257278 0.193676 5.594666
+v 0.245926 0.085112 5.864237
+v 0.246134 0.084478 5.595152
+v 0.256836 -0.021166 5.862653
+v 0.256504 -0.024828 5.595598
+v 0.288031 -0.124601 5.863041
+v 0.288019 -0.130073 5.596006
+v 0.339461 -0.221624 5.863426
+v 0.339445 -0.227102 5.596392
+v 0.408828 -0.306751 5.863764
+v 0.408815 -0.312234 5.596729
+v 0.493473 -0.376709 5.864041
+v 0.493457 -0.382198 5.597005
+v 0.590136 -0.428808 5.864248
+v 0.590124 -0.434302 5.597212
+v 0.695107 -0.461050 5.864375
+v 0.695100 -0.466548 5.597339
+v 0.804351 -0.472194 5.864418
+v 0.804351 -0.477695 5.597383
+v 0.913672 -0.461815 5.864378
+v 0.913679 -0.467312 5.597343
+v 1.018864 -0.430305 5.864253
+v 1.018874 -0.435800 5.597218
+v 1.115892 -0.378882 5.864049
+v 1.115906 -0.384370 5.597014
+v 1.201019 -0.309515 5.863774
+v 1.201035 -0.315001 5.596738
+v 1.270979 -0.224876 5.863439
+v 1.270996 -0.230353 5.596403
+v 1.323086 -0.128213 5.863057
+v 1.323098 -0.133684 5.596019
+v 1.355004 -0.024994 5.862668
+v 1.355350 -0.028662 5.595614
+v 1.366655 0.081200 5.864254
+v 1.366481 0.080568 5.595167
+v 1.380815 0.199851 5.864238
+v 1.380815 0.194667 5.229348
+v 1.347889 0.309774 6.389203
+v 1.347889 0.304590 5.228831
+v 1.294148 0.411161 6.388727
+v 1.294148 0.405980 5.228355
+v 1.221662 0.500120 6.388309
+v 1.221663 0.494936 5.227938
+v 1.133213 0.573224 6.387966
+v 1.133212 0.568041 5.227594
+v 1.032202 0.627670 6.387711
+v 1.032202 0.622487 5.227339
+v 0.922510 0.661364 6.387552
+v 0.922510 0.656181 5.227180
+v 0.808350 0.673008 6.387497
+v 0.808351 0.667827 5.227125
+v 0.694112 0.662162 6.387548
+v 0.694114 0.656978 5.227177
+v 0.584189 0.629235 6.387703
+v 0.584189 0.624051 5.227331
+v 0.482798 0.575494 6.387955
+v 0.482799 0.570312 5.227584
+v 0.393842 0.503009 6.388296
+v 0.393842 0.497827 5.227924
+v 0.320737 0.414561 6.388711
+v 0.320736 0.409379 5.228340
+v 0.266289 0.313551 6.389186
+v 0.266290 0.308367 5.228814
+v 0.232596 0.203858 5.864219
+v 0.232596 0.198676 5.229330
+v 0.220950 0.089702 5.864219
+v 0.220951 0.084519 5.229865
+v 0.231799 -0.024533 5.862726
+v 0.231799 -0.029716 5.230402
+v 0.264725 -0.134459 5.863242
+v 0.264724 -0.139641 5.230918
+v 0.318466 -0.235846 5.863719
+v 0.318465 -0.241029 5.231395
+v 0.390952 -0.324803 5.864136
+v 0.390953 -0.329985 5.231813
+v 0.479402 -0.397909 5.864480
+v 0.479401 -0.403091 5.232156
+v 0.580414 -0.452354 5.864735
+v 0.580413 -0.457536 5.232411
+v 0.690105 -0.486047 5.864894
+v 0.690106 -0.491229 5.232570
+v 0.804264 -0.497692 5.864949
+v 0.804263 -0.502876 5.232625
+v 0.918500 -0.486845 5.864897
+v 0.918501 -0.492027 5.232574
+v 1.028426 -0.453917 5.864743
+v 1.028425 -0.459100 5.232419
+v 1.129816 -0.400180 5.864491
+v 1.129816 -0.405360 5.232167
+v 1.218773 -0.327692 5.864150
+v 1.218772 -0.332875 5.231826
+v 1.291878 -0.239244 5.863734
+v 1.291878 -0.244427 5.231411
+v 1.346324 -0.138234 5.863259
+v 1.346325 -0.143416 5.230936
+v 1.380017 -0.028543 5.862744
+v 1.380018 -0.033725 5.230421
+v 1.391665 0.085615 5.864237
+v 1.391664 0.080432 5.229884
+v -0.966289 1.760967 5.864285
+v -0.966289 1.758691 5.594683
+v -0.979322 1.804477 6.389298
+v -0.979322 1.802200 5.594215
+v -1.000594 1.844609 6.388865
+v -1.000594 1.842333 5.593781
+v -1.029285 1.879819 6.388486
+v -1.029285 1.877545 5.593402
+v -1.064296 1.908756 6.388174
+v -1.064296 1.906481 5.593091
+v -1.104278 1.930307 6.387940
+v -1.104278 1.928033 5.592857
+v -1.147697 1.943644 6.387797
+v -1.147697 1.941369 5.592714
+v -1.192884 1.948253 6.387748
+v -1.192883 1.945979 5.592665
+v -1.238102 1.943959 6.387794
+v -1.238102 1.941683 5.592710
+v -1.281613 1.930927 6.387936
+v -1.281613 1.928651 5.592852
+v -1.321745 1.909655 6.388164
+v -1.321746 1.907380 5.593081
+v -1.356956 1.880964 6.388473
+v -1.356957 1.878689 5.593389
+v -1.385893 1.845954 6.388850
+v -1.385894 1.843678 5.593767
+v -1.407445 1.805971 6.389282
+v -1.407444 1.803696 5.594199
+v -1.420781 1.762553 5.864267
+v -1.420781 1.760277 5.594666
+v -1.425477 1.715373 5.864237
+v -1.425391 1.715111 5.595152
+v -1.420964 1.671415 5.862653
+v -1.421102 1.669900 5.595598
+v -1.408062 1.628632 5.863041
+v -1.408066 1.626369 5.596006
+v -1.386789 1.588502 5.863426
+v -1.386796 1.586236 5.596392
+v -1.358097 1.553291 5.863764
+v -1.358103 1.551024 5.596729
+v -1.323087 1.524355 5.864041
+v -1.323093 1.522085 5.597005
+v -1.283105 1.502807 5.864248
+v -1.283110 1.500534 5.597212
+v -1.239687 1.489471 5.864375
+v -1.239690 1.487197 5.597339
+v -1.194502 1.484861 5.864418
+v -1.194502 1.482586 5.597383
+v -1.149285 1.489154 5.864378
+v -1.149282 1.486880 5.597343
+v -1.105775 1.502187 5.864253
+v -1.105771 1.499915 5.597218
+v -1.065643 1.523457 5.864049
+v -1.065637 1.521187 5.597014
+v -1.030433 1.552148 5.863774
+v -1.030426 1.549879 5.596738
+v -1.001496 1.587157 5.863439
+v -1.001489 1.584891 5.596403
+v -0.979944 1.627138 5.863057
+v -0.979939 1.624875 5.596019
+v -0.966742 1.669832 5.862668
+v -0.966598 1.668314 5.595614
+v -0.961923 1.713755 5.864254
+v -0.961995 1.713493 5.595167
+v -0.956066 1.762831 5.864238
+v -0.956066 1.760688 5.229348
+v -0.969685 1.808297 6.389203
+v -0.969685 1.806154 5.228831
+v -0.991913 1.850233 6.388727
+v -0.991913 1.848090 5.228355
+v -1.021894 1.887028 6.388309
+v -1.021894 1.884884 5.227938
+v -1.058478 1.917265 6.387966
+v -1.058479 1.915121 5.227594
+v -1.100258 1.939785 6.387711
+v -1.100258 1.937642 5.227339
+v -1.145629 1.953721 6.387552
+v -1.145629 1.951578 5.227180
+v -1.192848 1.958538 6.387497
+v -1.192847 1.956395 5.227125
+v -1.240099 1.954051 6.387548
+v -1.240098 1.951908 5.227177
+v -1.285565 1.940433 6.387703
+v -1.285565 1.938288 5.227331
+v -1.327502 1.918204 6.387955
+v -1.327502 1.916061 5.227584
+v -1.364296 1.888223 6.388296
+v -1.364296 1.886080 5.227924
+v -1.394534 1.851640 6.388711
+v -1.394534 1.849496 5.228340
+v -1.417054 1.809860 6.389186
+v -1.417054 1.807716 5.228814
+v -1.430991 1.764489 5.864219
+v -1.430990 1.762345 5.229330
+v -1.435807 1.717272 5.864219
+v -1.435807 1.715128 5.229865
+v -1.431320 1.670022 5.862726
+v -1.431320 1.667878 5.230402
+v -1.417701 1.624554 5.863242
+v -1.417701 1.622411 5.230918
+v -1.395473 1.582619 5.863719
+v -1.395473 1.580475 5.231395
+v -1.365491 1.545825 5.864136
+v -1.365491 1.543681 5.231813
+v -1.328907 1.515587 5.864480
+v -1.328907 1.513443 5.232156
+v -1.287126 1.493068 5.864735
+v -1.287127 1.490924 5.232411
+v -1.241756 1.479131 5.864894
+v -1.241756 1.476988 5.232570
+v -1.194538 1.474315 5.864949
+v -1.194538 1.472170 5.232625
+v -1.147288 1.478801 5.864897
+v -1.147287 1.476658 5.232574
+v -1.101820 1.492421 5.864743
+v -1.101821 1.490277 5.232419
+v -1.059884 1.514648 5.864491
+v -1.059884 1.512505 5.232167
+v -1.023089 1.544630 5.864150
+v -1.023090 1.542486 5.231826
+v -0.992852 1.581213 5.863734
+v -0.992852 1.579070 5.231411
+v -0.970332 1.622993 5.863259
+v -0.970331 1.620850 5.230936
+v -0.956396 1.668364 5.862744
+v -0.956395 1.666220 5.230421
+v -0.951578 1.715581 5.864237
+v -0.951578 1.713438 5.229884
+v -0.643901 0.195342 5.864285
+v -0.643902 0.189842 5.594683
+v -0.675411 0.300537 6.389298
+v -0.675412 0.295033 5.594215
+v -0.726840 0.397562 6.388865
+v -0.726840 0.392062 5.593781
+v -0.796207 0.482692 6.388486
+v -0.796206 0.477191 5.593402
+v -0.880851 0.552651 6.388174
+v -0.880852 0.547151 5.593091
+v -0.977517 0.604755 6.387940
+v -0.977515 0.599256 5.592857
+v -1.082489 0.636999 6.387797
+v -1.082489 0.631500 5.592714
+v -1.191737 0.648142 6.387748
+v -1.191736 0.642644 5.592665
+v -1.301062 0.637761 6.387794
+v -1.301060 0.632259 5.592710
+v -1.406255 0.606253 6.387936
+v -1.406256 0.600751 5.592852
+v -1.503284 0.554824 6.388164
+v -1.503284 0.549324 5.593081
+v -1.588412 0.485460 6.388473
+v -1.588414 0.479957 5.593389
+v -1.658372 0.400816 6.388850
+v -1.658375 0.395314 5.593767
+v -1.710479 0.304149 6.389282
+v -1.710478 0.298648 5.594199
+v -1.742722 0.199179 5.864267
+v -1.742722 0.193676 5.594666
+v -1.754074 0.085112 5.864237
+v -1.753866 0.084478 5.595152
+v -1.743164 -0.021166 5.862653
+v -1.743496 -0.024828 5.595598
+v -1.711969 -0.124601 5.863041
+v -1.711981 -0.130073 5.596006
+v -1.660540 -0.221624 5.863426
+v -1.660555 -0.227101 5.596392
+v -1.591172 -0.306751 5.863764
+v -1.591185 -0.312234 5.596729
+v -1.506527 -0.376709 5.864041
+v -1.506543 -0.382199 5.597005
+v -1.409864 -0.428808 5.864248
+v -1.409876 -0.434301 5.597212
+v -1.304893 -0.461050 5.864375
+v -1.304900 -0.466548 5.597339
+v -1.195649 -0.472194 5.864418
+v -1.195649 -0.477695 5.597383
+v -1.086329 -0.461815 5.864378
+v -1.086321 -0.467312 5.597343
+v -0.981136 -0.430305 5.864253
+v -0.981126 -0.435800 5.597218
+v -0.884108 -0.378882 5.864049
+v -0.884094 -0.384369 5.597014
+v -0.798981 -0.309516 5.863774
+v -0.798965 -0.315000 5.596738
+v -0.729021 -0.224876 5.863439
+v -0.729004 -0.230353 5.596403
+v -0.676914 -0.128213 5.863057
+v -0.676902 -0.133684 5.596019
+v -0.644996 -0.024994 5.862668
+v -0.644650 -0.028661 5.595614
+v -0.633345 0.081199 5.864254
+v -0.633519 0.080568 5.595167
+v -0.619185 0.199851 5.864238
+v -0.619185 0.194668 5.229348
+v -0.652111 0.309774 6.389203
+v -0.652111 0.304590 5.228831
+v -0.705852 0.411161 6.388727
+v -0.705852 0.405980 5.228355
+v -0.778338 0.500120 6.388309
+v -0.778337 0.494936 5.227938
+v -0.866787 0.573224 6.387966
+v -0.866788 0.568041 5.227594
+v -0.967798 0.627670 6.387711
+v -0.967798 0.622487 5.227339
+v -1.077490 0.661364 6.387552
+v -1.077490 0.656180 5.227180
+v -1.191650 0.673008 6.387497
+v -1.191649 0.667827 5.227125
+v -1.305888 0.662162 6.387548
+v -1.305886 0.656978 5.227177
+v -1.415811 0.629236 6.387703
+v -1.415811 0.624051 5.227331
+v -1.517202 0.575494 6.387955
+v -1.517201 0.570312 5.227584
+v -1.606158 0.503009 6.388296
+v -1.606158 0.497827 5.227924
+v -1.679263 0.414561 6.388711
+v -1.679264 0.409379 5.228340
+v -1.733711 0.313551 6.389186
+v -1.733710 0.308368 5.228814
+v -1.767405 0.203858 5.864219
+v -1.767404 0.198676 5.229330
+v -1.779050 0.089702 5.864219
+v -1.779049 0.084519 5.229865
+v -1.768201 -0.024533 5.862726
+v -1.768202 -0.029716 5.230402
+v -1.735275 -0.134459 5.863242
+v -1.735276 -0.139641 5.230918
+v -1.681534 -0.235847 5.863719
+v -1.681535 -0.241029 5.231395
+v -1.609048 -0.324803 5.864136
+v -1.609047 -0.329986 5.231813
+v -1.520598 -0.397909 5.864480
+v -1.520599 -0.403091 5.232156
+v -1.419586 -0.452353 5.864735
+v -1.419587 -0.457536 5.232411
+v -1.309895 -0.486047 5.864894
+v -1.309894 -0.491229 5.232570
+v -1.195736 -0.497692 5.864949
+v -1.195737 -0.502877 5.232625
+v -1.081500 -0.486845 5.864897
+v -1.081499 -0.492027 5.232574
+v -0.971574 -0.453917 5.864743
+v -0.971575 -0.459100 5.232419
+v -0.870184 -0.400180 5.864491
+v -0.870184 -0.405360 5.232167
+v -0.781227 -0.327693 5.864150
+v -0.781228 -0.332875 5.231826
+v -0.708122 -0.239244 5.863734
+v -0.708122 -0.244427 5.231411
+v -0.653676 -0.138234 5.863259
+v -0.653675 -0.143416 5.230936
+v -0.619983 -0.028543 5.862744
+v -0.619982 -0.033725 5.230421
+v -0.608335 0.085615 5.864237
+v -0.608336 0.080432 5.229884
+v -0.951578 -1.572011 5.229884
+v -0.951578 -1.569868 5.864237
+v -0.956395 -1.619229 5.230421
+v -0.956396 -1.617086 5.862744
+v -0.970331 -1.664599 5.230936
+v -0.970332 -1.662456 5.863259
+v -0.992852 -1.706379 5.231411
+v -0.992852 -1.704236 5.863734
+v -1.023090 -1.742963 5.231826
+v -1.023089 -1.740819 5.864150
+v -1.059884 -1.772945 5.232167
+v -1.059884 -1.770802 5.864491
+v -1.101821 -1.795172 5.232419
+v -1.101820 -1.793028 5.864743
+v -1.147287 -1.808791 5.232574
+v -1.147288 -1.806648 5.864897
+v -1.194538 -1.813279 5.232625
+v -1.194538 -1.811135 5.864949
+v -1.241756 -1.808461 5.232570
+v -1.241756 -1.806318 5.864894
+v -1.287127 -1.794525 5.232411
+v -1.287126 -1.792382 5.864735
+v -1.328907 -1.772006 5.232156
+v -1.328907 -1.769862 5.864480
+v -1.365491 -1.741768 5.231813
+v -1.365491 -1.739625 5.864136
+v -1.395473 -1.704974 5.231395
+v -1.395473 -1.702831 5.863719
+v -1.417701 -1.663038 5.230918
+v -1.417701 -1.660895 5.863242
+v -1.431320 -1.617571 5.230402
+v -1.431320 -1.615427 5.862726
+v -1.435807 -1.570321 5.229865
+v -1.435807 -1.568178 5.864219
+v -1.430990 -1.523104 5.229330
+v -1.430991 -1.520960 5.864219
+v -1.417054 -1.477733 5.228814
+v -1.417054 -1.475590 6.389186
+v -1.394534 -1.435953 5.228340
+v -1.394534 -1.433810 6.388711
+v -1.364296 -1.399369 5.227924
+v -1.364296 -1.397226 6.388296
+v -1.327502 -1.369388 5.227584
+v -1.327502 -1.367245 6.387955
+v -1.285565 -1.347161 5.227331
+v -1.285565 -1.345016 6.387703
+v -1.240098 -1.333542 5.227177
+v -1.240099 -1.331398 6.387548
+v -1.192847 -1.329054 5.227125
+v -1.192848 -1.326911 6.387497
+v -1.145629 -1.333871 5.227180
+v -1.145629 -1.331728 6.387552
+v -1.100258 -1.347808 5.227339
+v -1.100258 -1.345664 6.387711
+v -1.058479 -1.370328 5.227594
+v -1.058478 -1.368184 6.387966
+v -1.021894 -1.400565 5.227938
+v -1.021894 -1.398421 6.388309
+v -0.991913 -1.437359 5.228355
+v -0.991913 -1.435216 6.388727
+v -0.969685 -1.479295 5.228831
+v -0.969685 -1.477152 6.389203
+v -0.956066 -1.524762 5.229348
+v -0.956066 -1.522618 5.864238
+v -0.961995 -1.571956 5.595167
+v -0.961923 -1.571694 5.864254
+v -0.966598 -1.617135 5.595614
+v -0.966742 -1.615618 5.862668
+v -0.979939 -1.660574 5.596019
+v -0.979944 -1.658311 5.863057
+v -1.001489 -1.700558 5.596403
+v -1.001496 -1.698292 5.863439
+v -1.030426 -1.735570 5.596738
+v -1.030433 -1.733301 5.863774
+v -1.065637 -1.764262 5.597014
+v -1.065643 -1.761992 5.864049
+v -1.105771 -1.785535 5.597218
+v -1.105775 -1.783262 5.864253
+v -1.149282 -1.798569 5.597343
+v -1.149285 -1.796295 5.864378
+v -1.194502 -1.802863 5.597383
+v -1.194502 -1.800588 5.864418
+v -1.239690 -1.798252 5.597339
+v -1.239687 -1.795979 5.864375
+v -1.283110 -1.784915 5.597212
+v -1.283105 -1.782643 5.864248
+v -1.323093 -1.763364 5.597005
+v -1.323087 -1.761094 5.864041
+v -1.358103 -1.734425 5.596729
+v -1.358097 -1.732158 5.863764
+v -1.386796 -1.699213 5.596392
+v -1.386789 -1.696948 5.863426
+v -1.408066 -1.659081 5.596006
+v -1.408062 -1.656817 5.863041
+v -1.421102 -1.615549 5.595598
+v -1.420964 -1.614034 5.862653
+v -1.425391 -1.570338 5.595152
+v -1.425477 -1.570076 5.864237
+v -1.420781 -1.525172 5.594666
+v -1.420781 -1.522896 5.864267
+v -1.407444 -1.481753 5.594199
+v -1.407445 -1.479478 6.389282
+v -1.385894 -1.441771 5.593767
+v -1.385893 -1.439495 6.388850
+v -1.356957 -1.406760 5.593389
+v -1.356956 -1.404485 6.388473
+v -1.321746 -1.378070 5.593081
+v -1.321745 -1.375794 6.388164
+v -1.281613 -1.356798 5.592852
+v -1.281613 -1.354522 6.387936
+v -1.238102 -1.343766 5.592710
+v -1.238102 -1.341490 6.387794
+v -1.192883 -1.339470 5.592665
+v -1.192884 -1.337197 6.387748
+v -1.147697 -1.344080 5.592714
+v -1.147697 -1.341805 6.387797
+v -1.104278 -1.357417 5.592857
+v -1.104278 -1.355142 6.387940
+v -1.064296 -1.378968 5.593091
+v -1.064296 -1.376693 6.388174
+v -1.029285 -1.407905 5.593402
+v -1.029285 -1.405630 6.388486
+v -1.000594 -1.443116 5.593781
+v -1.000594 -1.440841 6.388865
+v -0.979322 -1.483249 5.594215
+v -0.979322 -1.480973 6.389298
+v -0.966289 -1.526758 5.594683
+v -0.966289 -1.524483 5.864285
+vt 0.442670 0.517822
+vt 0.228883 0.517823
+vt 0.228889 0.488295
+vt 0.797953 0.896311
+vt 0.741726 0.913364
+vt 0.594127 0.834472
+vt 0.577066 0.778233
+vt 0.655948 0.630639
+vt 0.770682 0.619333
+vt 0.859798 0.692466
+vt 0.876858 0.748707
+vt 0.683240 0.907605
+vt 0.655960 0.896305
+vt 0.582826 0.719746
+vt 0.610530 0.667916
+vt 0.683222 0.619339
+vt 0.822512 0.647036
+vt 0.843392 0.667915
+vt 0.871097 0.807194
+vt 0.843391 0.859025
+vt 0.770685 0.907604
+vt 0.712199 0.913364
+vt 0.631409 0.879901
+vt 0.610530 0.859023
+vt 0.582827 0.807192
+vt 0.577066 0.748705
+vt 0.594126 0.692467
+vt 0.631409 0.647037
+vt 0.712194 0.613573
+vt 0.741722 0.613573
+vt 0.797962 0.630631
+vt 0.871098 0.719747
+vt 0.876858 0.778235
+vt 0.859797 0.834474
+vt 0.822501 0.879912
+vt 0.442971 0.724510
+vt 0.443060 0.754024
+vt 0.370472 0.723575
+vt 0.442967 0.311134
+vt 0.370467 0.312068
+vt 0.443056 0.281620
+vt 0.443160 0.252098
+vt 0.371348 0.252525
+vt 0.443163 0.783546
+vt 0.371352 0.783117
+vt 0.006929 0.253167
+vt 0.177644 0.253134
+vt 0.006828 0.284020
+vt 0.007055 0.808541
+vt 0.006941 0.777687
+vt 0.177771 0.808498
+vt 0.006827 0.746834
+vt 0.176852 0.746796
+vt 0.006727 0.314875
+vt 0.176753 0.314837
+vt 0.371492 0.871219
+vt 0.371495 0.841694
+vt 0.443305 0.842613
+vt 0.615392 0.436018
+vt 0.610260 0.440534
+vt 0.592437 0.415287
+vt 0.816609 0.103895
+vt 0.836176 0.125250
+vt 0.831035 0.128981
+vt 0.706468 0.066881
+vt 0.736753 0.067631
+vt 0.735929 0.074111
+vt 0.647824 0.083609
+vt 0.650942 0.089526
+vt 0.626230 0.105756
+vt 0.765981 0.074365
+vt 0.763884 0.080481
+vt 0.579817 0.387126
+vt 0.586362 0.385333
+vt 0.621946 0.100560
+vt 0.605204 0.126737
+vt 0.706979 0.073452
+vt 0.678244 0.078632
+vt 0.792953 0.086670
+vt 0.812355 0.108585
+vt 0.676401 0.072253
+vt 0.789700 0.092175
+vt 0.599888 0.122511
+vt 0.589057 0.151745
+vt 0.582798 0.148853
+vt 0.579868 0.178984
+vt 0.850517 0.149568
+vt 0.844628 0.151667
+vt 0.572791 0.357181
+vt 0.571207 0.326153
+vt 0.578089 0.326242
+vt 0.579568 0.357437
+vt 0.892437 0.349226
+vt 0.885423 0.348954
+vt 0.885704 0.319257
+vt 0.886223 0.285996
+vt 0.879631 0.287703
+vt 0.636826 0.456466
+vt 0.632640 0.461897
+vt 0.874138 0.409852
+vt 0.856681 0.436209
+vt 0.851292 0.431762
+vt 0.661830 0.472412
+vt 0.658752 0.478560
+vt 0.689480 0.483245
+vt 0.687628 0.489885
+vt 0.718747 0.488548
+vt 0.718195 0.495436
+vt 0.779839 0.488518
+vt 0.749318 0.494983
+vt 0.748545 0.488099
+vt 0.808610 0.476251
+vt 0.777761 0.481894
+vt 0.834553 0.458608
+vt 0.805300 0.470133
+vt 0.830124 0.453224
+vt 0.573209 0.178195
+vt 0.886228 0.380492
+vt 0.867982 0.406513
+vt 0.879533 0.378395
+vt 0.370709 0.754396
+vt 0.319322 0.130795
+vt 0.319224 0.161649
+vt 0.007220 0.160604
+vt 0.007387 0.901104
+vt 0.319390 0.899923
+vt 0.319502 0.930776
+vt 0.442778 0.399714
+vt 0.228990 0.399716
+vt 0.229046 0.370189
+vt 0.007499 0.931958
+vt 0.319616 0.961631
+vt 0.006361 0.469146
+vt 0.176387 0.469108
+vt 0.176362 0.499962
+vt 0.442678 0.488295
+vt 0.228910 0.458769
+vt 0.007727 0.006334
+vt 0.319730 0.007378
+vt 0.319626 0.038233
+vt 0.228890 0.547350
+vt 0.007613 0.962812
+vt 0.319730 0.992485
+vt 0.228946 0.606403
+vt 0.228912 0.576877
+vt 0.442700 0.576876
+vt 0.228992 0.635930
+vt 0.442734 0.606402
+vt 0.442833 0.370188
+vt 0.229109 0.340661
+vt 0.229113 0.694984
+vt 0.229049 0.665457
+vt 0.442837 0.665456
+vt 0.442901 0.694984
+vt 0.370705 0.281246
+vt 0.371443 0.812167
+vt 0.443254 0.813086
+vt 0.892437 0.317355
+vt 0.371421 0.900745
+vt 0.443302 0.872141
+vt 0.371269 0.930272
+vt 0.443231 0.901668
+vt 0.007276 0.870250
+vt 0.319278 0.869069
+vt 0.006405 0.438291
+vt 0.176430 0.438254
+vt 0.443090 0.104449
+vt 0.371280 0.105372
+vt 0.371043 0.075847
+vt 0.443237 0.133976
+vt 0.371427 0.134897
+vt 0.443305 0.163503
+vt 0.371495 0.164423
+vt 0.443305 0.193031
+vt 0.371495 0.193949
+vt 0.443252 0.222558
+vt 0.371442 0.223476
+vt 0.177399 0.283987
+vt 0.006543 0.376583
+vt 0.176568 0.376545
+vt 0.176492 0.407399
+vt 0.006467 0.407437
+vt 0.007165 0.839396
+vt 0.319168 0.838215
+vt 0.370694 0.989321
+vt 0.371029 0.959797
+vt 0.442838 0.960722
+vt 0.442780 0.635930
+vt 0.442698 0.458768
+vt 0.228944 0.429242
+vt 0.006337 0.500000
+vt 0.176359 0.530816
+vt 0.319524 0.069087
+vt 0.007520 0.068042
+vt 0.319423 0.099941
+vt 0.007419 0.098896
+vt 0.007319 0.129750
+vt 0.442897 0.340661
+vt 0.319126 0.192504
+vt 0.007122 0.191458
+vt 0.319029 0.223358
+vt 0.007025 0.222313
+vt 0.177512 0.777645
+vt 0.443079 0.931196
+vt 0.176656 0.345691
+vt 0.006631 0.345729
+vt 0.442853 0.074922
+vt 0.370712 0.046322
+vt 0.442732 0.429241
+vt 0.442678 0.547349
+vt 0.176377 0.561671
+vt 0.006351 0.561708
+vt 0.176415 0.592525
+vt 0.006390 0.592562
+vt 0.176474 0.623379
+vt 0.006448 0.623417
+vt 0.176549 0.654233
+vt 0.006524 0.654271
+vt 0.176640 0.685087
+vt 0.006614 0.685125
+vt 0.176742 0.715941
+vt 0.006716 0.715979
+vt 0.598317 0.411848
+vt 0.007623 0.037188
+vt 0.007728 0.993666
+vt 0.442504 0.990249
+vt 0.006334 0.530854
+vt 0.442522 0.045396
+vn 0.094500 -0.995500 0.006900
+vn -0.000000 0.004400 1.000000
+vn 0.000000 0.004500 1.000000
+vn 0.000000 0.004000 1.000000
+vn 0.000300 0.004100 1.000000
+vn -0.000200 0.004400 1.000000
+vn 0.000100 0.004500 1.000000
+vn -0.000000 0.004600 1.000000
+vn -0.000100 0.004400 1.000000
+vn -0.000300 0.003800 1.000000
+vn 0.000100 0.004000 1.000000
+vn -0.000100 0.003900 1.000000
+vn -0.000000 0.004100 1.000000
+vn -0.000200 0.004000 1.000000
+vn -0.001200 0.004200 1.000000
+vn -0.995500 -0.094600 0.001900
+vn 0.994800 -0.101500 0.002100
+vn 0.995500 0.094400 -0.002500
+vn -0.994800 0.101400 -0.002700
+vn 0.995500 0.094500 -0.000800
+vn -0.994800 0.101500 -0.000800
+vn -0.995500 -0.094500 0.000800
+vn 0.994800 -0.101500 0.000800
+vn -0.770700 0.637000 -0.013100
+vn -0.008000 0.009000 0.999900
+vn -0.002300 0.006600 1.000000
+vn -0.000500 0.010000 1.000000
+vn 0.002400 0.009000 1.000000
+vn -0.001400 0.009500 1.000000
+vn -0.002300 0.005400 1.000000
+vn 0.003000 0.008100 1.000000
+vn 0.000600 0.010100 0.999900
+vn -0.002400 0.007600 1.000000
+vn 0.001500 0.009400 1.000000
+vn -0.002100 0.008700 1.000000
+vn 0.003100 0.007000 1.000000
+vn 0.002800 0.005900 1.000000
+vn -0.001800 0.005700 1.000000
+vn 0.001800 0.000200 1.000000
+vn 0.003100 -0.013400 0.999900
+vn 0.004900 -0.014400 0.999900
+vn -0.182400 -0.960000 0.212300
+vn -0.008800 0.012000 0.999900
+vn 0.008800 0.011900 0.999900
+vn -0.008500 0.015200 0.999800
+vn -0.007000 0.017900 0.999800
+vn -0.004600 0.019900 0.999800
+vn 0.001600 0.020800 0.999800
+vn 0.004600 0.019800 0.999800
+vn 0.007100 0.017800 0.999800
+vn 0.008500 0.015000 0.999900
+vn 0.361500 -0.909800 0.204000
+vn 0.007900 0.008900 0.999900
+vn 0.006200 0.005600 1.000000
+vn -0.000100 0.004300 1.000000
+vn -0.995800 -0.092100 0.000900
+vn 0.637100 0.770800 -0.003400
+vn -0.631700 0.775200 -0.003500
+vn 0.770800 -0.637100 0.004400
+vn -0.468300 0.883500 -0.003900
+vn 0.287000 -0.957900 0.007900
+vn 0.286900 -0.957900 0.006600
+vn -0.094500 0.995500 -0.004400
+vn -0.101500 -0.994800 0.006900
+vn -0.286900 0.957900 -0.004300
+vn -0.474500 -0.880200 0.006100
+vn -0.637100 -0.770800 0.005300
+vn 0.880300 -0.474500 0.003300
+vn -0.883600 -0.468300 0.003200
+vn -0.955100 -0.296300 0.002100
+vn 0.995100 -0.099000 0.001000
+vn 0.994800 0.102100 0.000500
+vn -0.880200 0.474500 -0.009800
+vn -0.001500 0.020800 0.999800
+vn -0.955400 0.295400 -0.005300
+vn -0.000800 -0.000200 1.000000
+vn -0.631600 0.775100 -0.016000
+vn -0.468200 0.883400 -0.018200
+vn -0.770800 0.637100 -0.002800
+vn 0.468300 -0.883500 0.007200
+vn 0.293600 0.955700 -0.019700
+vn 0.474400 0.880100 -0.018100
+vn 0.637000 0.770700 -0.015900
+vn 0.775200 0.631600 -0.013000
+vn 0.883500 0.468300 -0.009600
+vn 0.957400 0.288700 -0.005200
+vn -0.994000 0.109100 0.000400
+vn 0.770800 -0.637100 0.005200
+vn 0.631700 -0.775200 0.006400
+vn -0.880300 0.474500 -0.002100
+vn -0.094500 0.995300 -0.020500
+vn -0.775200 -0.631700 0.004400
+vn 0.468300 -0.883500 0.006100
+vn 0.094500 -0.995500 0.008200
+vn 0.101500 0.994800 -0.004400
+vn 0.293600 0.955900 -0.004300
+vn 0.474500 0.880300 -0.003900
+vn 0.952900 -0.303200 0.002100
+vn 0.775200 0.631700 -0.002800
+vn 0.883600 0.468300 -0.002100
+vn 0.956200 0.292700 -0.001300
+vn -0.995500 -0.094600 0.000800
+vn -0.286900 0.957800 -0.019700
+vn 0.955900 -0.293600 0.002400
+vn 0.880300 -0.474500 0.003900
+vn -0.955900 0.293600 -0.002400
+vn 0.101500 0.994600 -0.020500
+vn 0.631700 -0.775200 0.005400
+vn -0.293600 -0.955900 0.006600
+vn -0.101500 -0.994800 0.008200
+vn -0.293600 -0.955900 0.007800
+vn -0.474500 -0.880200 0.007200
+vn -0.637100 -0.770800 0.006300
+vn -0.775200 -0.631700 0.005200
+vn -0.883600 -0.468300 0.003800
+vn -0.958000 -0.286900 0.002400
+vn 0.094600 -0.995500 0.006900
+vn -0.770800 0.637000 -0.013100
+vn -0.005700 0.007000 1.000000
+vn -0.003100 0.007000 1.000000
+vn -0.000500 0.010100 0.999900
+vn 0.002100 0.008600 1.000000
+vn -0.001600 0.009800 1.000000
+vn -0.006300 0.005700 1.000000
+vn 0.002400 0.007600 1.000000
+vn 0.000500 0.010000 1.000000
+vn -0.003000 0.008100 1.000000
+vn 0.001600 0.009700 1.000000
+vn -0.002500 0.009100 1.000000
+vn 0.002300 0.006600 1.000000
+vn 0.001800 0.005700 1.000000
+vn -0.002800 0.006000 1.000000
+vn 0.000700 -0.000200 1.000000
+vn -0.005100 -0.014400 0.999900
+vn -0.003200 -0.013400 0.999900
+vn -0.368000 -0.907200 0.204000
+vn -0.007300 0.010000 0.999900
+vn 0.007200 0.009900 0.999900
+vn -0.007600 0.013300 0.999900
+vn -0.006600 0.016400 0.999800
+vn -0.004500 0.018900 0.999800
+vn 0.001700 0.020400 0.999800
+vn 0.004600 0.018800 0.999800
+vn 0.006600 0.016300 0.999800
+vn 0.007600 0.013100 0.999900
+vn 0.175700 -0.961300 0.212300
+vn 0.005700 0.007000 1.000000
+vn 0.002200 0.005400 1.000000
+vn -0.468300 0.883600 -0.003900
+vn -0.957900 -0.286900 0.005900
+vn -0.880300 0.474400 -0.009800
+vn -0.001600 0.020400 0.999800
+vn -0.955900 0.293500 -0.006100
+vn -0.002000 0.000200 1.000000
+vn -0.468300 0.883400 -0.018200
+vn 0.637100 0.770600 -0.015900
+vn 0.883500 0.468200 -0.009700
+vn 0.958000 0.286800 -0.005900
+vn 0.955900 -0.293600 0.006000
+vn 0.957900 0.286900 -0.002300
+vn -0.286900 0.957700 -0.019700
+vn -0.954100 0.299500 -0.001300
+vn -0.002400 0.006600 1.000000
+vn -0.008900 0.012000 0.999900
+vn -0.003000 0.007000 1.000000
+vn 0.002500 0.007600 1.000000
+vn 0.006700 0.016300 0.999800
+vn -0.001900 0.000200 1.000000
+vn 0.000200 0.010900 0.999900
+vn -0.019300 0.021700 0.999600
+vn -0.000000 0.009600 1.000000
+vn -0.000600 0.009200 1.000000
+vn 0.880300 -0.474500 0.001600
+vn 0.000100 0.010700 0.999900
+vn -0.003000 0.010200 0.999900
+vn -0.955400 0.295400 -0.002200
+vn -0.995500 -0.094600 0.000300
+vn -0.880300 0.474500 -0.000900
+vn 0.631700 -0.775200 0.002600
+vn 0.000300 0.009700 1.000000
+vn 0.952900 -0.303200 0.000900
+vn 0.000100 0.009600 1.000000
+vn 0.293600 0.955900 -0.001800
+vn 0.637100 0.770800 -0.001400
+vn 0.020700 0.036300 0.999100
+vn -0.094500 0.995500 -0.001800
+vn -0.775200 -0.631700 0.001800
+vn 0.994800 -0.101500 0.000900
+vn -0.101500 -0.994800 0.002800
+vn -0.000000 0.010800 0.999900
+vn 0.000100 0.009700 1.000000
+vn -0.000100 0.009900 1.000000
+vn -0.994800 0.101400 -0.001100
+vn 0.883600 0.468300 -0.000900
+vn -0.995800 -0.092100 0.000400
+vn 0.005900 0.021700 0.999700
+vn -0.005100 0.021000 0.999800
+vn -0.011000 0.048000 0.998800
+vn 0.000000 0.010900 0.999900
+vn 0.004000 0.050300 0.998700
+vn 0.994800 0.102100 0.000200
+vn -0.637100 -0.770800 0.002200
+vn 0.094500 -0.995500 0.002800
+vn -0.005500 0.013100 0.999900
+vn 0.094500 -0.995500 0.003400
+vn 0.011600 -0.034800 0.999300
+vn -0.001200 0.024100 0.999700
+vn 0.775200 0.631700 -0.005400
+vn 0.995500 0.094500 -0.000300
+vn -0.775200 -0.631700 0.002100
+vn 0.995500 0.094400 -0.001000
+vn -0.468300 0.883500 -0.007500
+vn -0.005600 0.016000 0.999900
+vn 0.007300 0.019500 0.999800
+vn 0.468300 -0.883500 0.002500
+vn 0.631700 -0.775200 0.002200
+vn -0.003700 0.050300 0.998700
+vn 0.021300 0.028700 0.999400
+vn -0.293600 -0.955900 0.002700
+vn -0.101500 -0.994800 0.003400
+vn -0.004400 0.013800 0.999900
+vn -0.287000 0.957900 -0.008200
+vn 0.101500 0.994800 -0.008500
+vn -0.000100 0.010800 0.999900
+vn -0.880300 0.474500 -0.004000
+vn 0.883600 0.468300 -0.004000
+vn -0.003500 0.023000 0.999700
+vn 0.293600 0.955900 -0.008100
+vn 0.017100 0.042900 0.998900
+vn 0.994800 -0.101500 0.000300
+vn -0.286900 0.957900 -0.001800
+vn -0.995500 -0.094500 0.000300
+vn -0.883600 -0.468300 0.001600
+vn -0.020600 0.036600 0.999100
+vn -0.994000 0.109100 0.000200
+vn -0.000300 0.009700 1.000000
+vn -0.000000 0.011200 0.999900
+vn 0.004400 0.000400 1.000000
+vn 0.770800 -0.637100 0.001800
+vn 0.995100 -0.099000 0.000400
+vn -0.474500 -0.880300 0.002500
+vn -0.637100 -0.770800 0.002600
+vn 0.770800 -0.637100 0.002200
+vn -0.883600 -0.468300 0.001300
+vn 0.101500 0.994800 -0.001800
+vn -0.000100 0.009500 1.000000
+vn 0.000800 0.009900 1.000000
+vn -0.631700 0.775200 -0.006600
+vn 0.956200 0.292700 -0.000500
+vn -0.016900 0.043300 0.998900
+vn 0.637100 0.770800 -0.006600
+vn -0.955900 0.293600 -0.001000
+vn -0.000000 0.010700 0.999900
+vn 0.286900 -0.957900 0.002700
+vn 0.011200 0.047900 0.998800
+vn -0.000300 0.010700 0.999900
+vn 0.367900 -0.925900 0.085900
+vn -0.958000 -0.286900 0.001000
+vn -0.021400 0.029100 0.999300
+vn 0.007400 -0.032300 0.999400
+vn -0.631700 0.775200 -0.001400
+vn -0.293600 -0.955900 0.003200
+vn 0.775200 0.631700 -0.001200
+vn 0.955900 -0.293600 0.001000
+vn -0.994800 0.101500 -0.000300
+vn -0.005800 0.018500 0.999800
+vn 0.006900 0.014300 0.999900
+vn -0.770800 0.637100 -0.001200
+vn 0.474500 0.880300 -0.001600
+vn -0.474500 -0.880300 0.003000
+vn 0.015100 0.013600 0.999800
+vn 0.880300 -0.474500 0.001400
+vn -0.094500 0.995500 -0.008500
+vn -0.185900 -0.978500 0.089500
+vn 0.007600 0.016800 0.999800
+vn 0.003500 0.022800 0.999700
+vn 0.474500 0.880200 -0.007500
+vn -0.770800 0.637100 -0.005400
+vn 0.019100 0.021500 0.999600
+vn -0.000300 0.010400 0.999900
+vn 0.468300 -0.883600 0.003000
+vn 0.957400 0.288700 -0.002100
+vn -0.001800 -0.000500 1.000000
+vn -0.955100 -0.296300 0.000800
+vn 0.287000 -0.957900 0.003200
+vn -0.468300 0.883600 -0.001600
+vn 0.001400 0.024400 0.999700
+vn 0.094600 -0.995500 0.002800
+vn -0.770800 0.637000 -0.005400
+vn -0.013700 0.017000 0.999800
+vn -0.007300 0.016900 0.999800
+vn -0.001300 0.024400 0.999700
+vn 0.005100 0.020900 0.999800
+vn -0.003800 0.023600 0.999700
+vn -0.015100 0.013800 0.999800
+vn 0.005900 0.018500 0.999800
+vn 0.001300 0.024100 0.999700
+vn -0.007200 0.019600 0.999800
+vn 0.004000 0.023500 0.999700
+vn -0.006000 0.022000 0.999700
+vn 0.005500 0.015900 0.999900
+vn 0.004400 0.013700 0.999900
+vn -0.006700 0.014400 0.999900
+vn 0.001800 -0.000500 1.000000
+vn -0.012400 -0.034700 0.999300
+vn -0.007700 -0.032300 0.999400
+vn -0.374500 -0.923300 0.085900
+vn -0.017600 0.024100 0.999600
+vn 0.017500 0.023800 0.999600
+vn -0.018300 0.032100 0.999300
+vn -0.016000 0.039700 0.999100
+vn -0.010800 0.045700 0.998900
+vn 0.004100 0.049300 0.998800
+vn 0.011000 0.045400 0.998900
+vn 0.016000 0.039400 0.999100
+vn 0.018400 0.031700 0.999300
+vn 0.179100 -0.979700 0.089500
+vn 0.013700 0.016800 0.999800
+vn 0.005400 0.012900 0.999900
+vn -0.957900 -0.286900 0.002400
+vn -0.880300 0.474400 -0.004000
+vn -0.003800 0.049400 0.998800
+vn -0.955900 0.293500 -0.002500
+vn -0.004700 0.000500 1.000000
+vn 0.637100 0.770700 -0.006600
+vn 0.958000 0.286800 -0.002400
+vn 0.468300 -0.883600 0.002500
+vn 0.955900 -0.293600 0.002500
+vn 0.957900 0.286900 -0.001000
+vn -0.954100 0.299500 -0.000600
+vn -0.004500 0.013800 0.999900
+vn 0.007500 0.016800 0.999800
+vn -0.000200 0.010700 0.999900
+vn 0.000100 0.010900 0.999900
+vn 0.004300 0.000400 1.000000
+vn -0.006600 0.014400 0.999900
+vn 0.000000 0.009500 1.000000
+vn 0.005600 0.015900 0.999900
+vn -0.019200 0.021700 0.999600
+vn -0.000500 0.009200 1.000000
+vn 0.003900 0.050300 0.998700
+vn 0.016100 0.039400 0.999100
+vn 0.000200 0.009700 1.000000
+vn -0.015900 0.039700 0.999100
+vn -0.004600 0.000500 1.000000
+vn 0.003900 0.023500 0.999700
+vn 0.015000 0.013600 0.999800
+vn 0.020600 0.036300 0.999100
+g VSLampen_Cube.004_Yellow
+usemtl Yellow
+s off
+f 16/1/1 15/2/1 17/3/1
+f 64/4/2 4/5/2 16/6/2
+f 16/6/3 20/7/3 32/8/3
+f 64/4/4 40/9/4 48/10/4
+f 48/10/4 52/11/4 64/4/4
+f 16/6/3 4/5/3 8/12/3
+f 8/12/3 10/13/3 16/6/3
+f 32/8/3 20/7/3 24/14/3
+f 24/14/2 28/15/2 32/8/2
+f 32/8/5 34/16/5 40/9/5
+f 40/9/4 44/17/4 46/18/4
+f 64/4/4 52/11/4 56/19/4
+f 56/19/4 60/20/4 64/4/4
+f 64/4/6 2/21/6 4/5/6
+f 4/5/7 6/22/7 8/12/7
+f 16/6/3 10/13/3 12/23/3
+f 12/23/3 14/24/3 16/6/3
+f 16/6/8 18/25/8 20/7/8
+f 20/7/2 22/26/2 24/14/2
+f 24/14/9 26/27/9 28/15/9
+f 28/15/7 30/28/7 32/8/7
+f 40/9/10 34/16/10 36/29/10
+f 36/29/11 38/30/11 40/9/11
+f 40/9/12 42/31/12 44/17/12
+f 40/9/4 46/18/4 48/10/4
+f 48/10/13 50/32/13 52/11/13
+f 52/11/4 54/33/4 56/19/4
+f 56/19/14 58/34/14 60/20/14
+f 60/20/15 62/35/15 64/4/15
+f 2/36/16 64/37/16 1/38/16
+f 30/39/17 29/40/17 32/41/17
+f 34/42/18 32/41/18 33/43/18
+f 62/44/19 61/45/19 64/37/19
+f 66/46/20 65/47/20 128/48/20
+f 94/49/21 96/50/21 93/51/21
+f 98/52/22 97/53/22 96/50/22
+f 126/54/23 128/48/23 125/55/23
+f 55/56/24 57/57/24 58/58/24
+f 57/59/25 121/60/25 123/61/25
+f 87/62/26 89/63/26 25/64/26
+f 79/65/27 81/66/27 17/67/27
+f 75/68/28 11/69/28 9/70/28
+f 81/66/29 83/71/29 19/72/29
+f 123/61/30 125/73/30 61/74/30
+f 73/75/31 9/70/31 7/76/31
+f 79/65/32 15/77/32 13/78/32
+f 85/79/33 87/62/33 23/80/33
+f 11/69/34 75/68/34 77/81/34
+f 83/71/35 85/79/35 21/82/35
+f 71/83/36 7/76/36 5/84/36
+f 69/85/37 5/84/37 3/86/37
+f 89/63/38 91/87/38 27/88/38
+f 127/89/39 65/90/39 1/91/39
+f 125/73/40 127/89/40 63/92/40
+f 97/93/41 33/94/41 31/95/41
+f 91/87/42 93/96/42 29/97/42
+f 55/98/43 119/99/43 121/60/43
+f 101/100/44 103/101/44 39/102/44
+f 53/103/45 117/104/45 119/99/45
+f 51/105/46 115/106/46 117/104/46
+f 49/107/47 113/108/47 115/106/47
+f 109/109/48 111/110/48 47/111/48
+f 107/112/49 109/109/49 45/113/49
+f 105/114/50 107/112/50 43/115/50
+f 103/101/51 105/114/51 41/116/51
+f 67/117/52 3/86/52 1/91/52
+f 99/118/53 101/100/53 37/119/53
+f 99/118/54 35/120/54 33/94/54
+f 40/9/55 64/4/55 16/6/55
+f 64/37/56 63/121/56 1/38/56
+f 73/122/57 71/123/57 72/124/57
+f 88/125/58 87/126/58 85/127/58
+f 24/128/59 23/129/59 25/130/59
+f 86/131/60 85/127/60 83/132/60
+f 116/133/61 115/134/61 113/135/61
+f 18/136/62 17/3/62 19/137/62
+f 82/138/63 81/139/63 79/140/63
+f 13/141/64 15/2/64 16/1/64
+f 84/142/65 83/132/65 81/143/65
+f 9/144/66 11/145/66 12/146/66
+f 7/147/67 9/144/67 10/148/67
+f 26/149/68 25/130/68 27/150/68
+f 3/151/69 5/152/69 6/153/69
+f 1/38/70 3/151/70 4/154/70
+f 29/40/71 31/155/71 32/41/71
+f 32/41/72 31/155/72 33/43/72
+f 57/57/73 59/156/73 60/157/73
+f 47/111/74 111/110/74 113/108/74
+f 59/156/75 61/45/75 62/44/75
+f 95/158/76 31/95/76 29/97/76
+f 53/159/77 55/56/77 56/160/77
+f 51/161/78 53/159/78 54/162/78
+f 90/163/79 89/164/79 87/126/79
+f 118/165/80 117/166/80 115/134/80
+f 44/167/81 43/168/81 45/169/81
+f 42/170/82 41/171/82 43/168/82
+f 40/172/83 39/173/83 41/171/83
+f 38/174/84 37/175/84 39/173/84
+f 36/176/85 35/177/85 37/175/85
+f 34/42/86 33/43/86 35/177/86
+f 61/45/87 63/121/87 64/37/87
+f 65/47/20 127/178/20 128/48/20
+f 122/179/88 121/180/88 119/181/88
+f 120/182/89 119/181/89 117/166/89
+f 92/183/90 91/184/90 89/164/90
+f 47/185/91 49/186/91 50/187/91
+f 5/152/92 7/147/92 8/188/92
+f 20/189/93 19/137/93 21/190/93
+f 114/191/94 113/135/94 111/192/94
+f 79/140/95 77/193/95 78/194/95
+f 77/193/96 75/195/96 76/196/96
+f 75/195/97 73/122/97 74/197/97
+f 28/198/98 27/150/98 29/40/98
+f 71/123/99 69/199/99 70/200/99
+f 69/199/100 67/201/100 68/202/100
+f 68/202/101 67/201/101 65/47/101
+f 96/50/21 95/203/21 93/51/21
+f 97/53/102 95/203/102 96/50/102
+f 49/186/103 51/161/103 52/204/103
+f 126/54/104 125/55/104 123/205/104
+f 124/206/105 123/205/105 121/180/105
+f 92/183/106 94/49/106 93/51/106
+f 46/207/107 45/169/107 47/208/107
+f 22/209/108 21/190/108 23/129/108
+f 11/145/109 13/141/109 14/210/109
+f 111/192/110 109/211/110 110/212/110
+f 109/211/111 107/213/111 108/214/111
+f 107/213/112 105/215/112 106/216/112
+f 105/215/113 103/217/113 104/218/113
+f 103/217/114 101/219/114 102/220/114
+f 101/219/115 99/221/115 100/222/115
+f 99/221/116 97/53/116 98/52/116
+f 128/48/23 127/178/23 125/55/23
+f 18/136/117 16/1/117 17/3/117
+f 56/160/118 55/56/118 58/58/118
+f 59/223/119 57/59/119 123/61/119
+f 23/80/120 87/62/120 25/64/120
+f 15/77/121 79/65/121 17/67/121
+f 73/75/122 75/68/122 9/70/122
+f 17/67/123 81/66/123 19/72/123
+f 59/223/124 123/61/124 61/74/124
+f 71/83/125 73/75/125 7/76/125
+f 77/81/126 79/65/126 13/78/126
+f 21/82/127 85/79/127 23/80/127
+f 13/78/128 11/69/128 77/81/128
+f 19/72/129 83/71/129 21/82/129
+f 69/85/130 71/83/130 5/84/130
+f 67/117/131 69/85/131 3/86/131
+f 25/64/132 89/63/132 27/88/132
+f 63/92/133 127/89/133 1/91/133
+f 61/74/134 125/73/134 63/92/134
+f 95/158/135 97/93/135 31/95/135
+f 27/88/136 91/87/136 29/97/136
+f 57/59/137 55/98/137 121/60/137
+f 37/119/138 101/100/138 39/102/138
+f 55/98/139 53/103/139 119/99/139
+f 53/103/140 51/105/140 117/104/140
+f 51/105/141 49/107/141 115/106/141
+f 45/113/142 109/109/142 47/111/142
+f 43/115/143 107/112/143 45/113/143
+f 41/116/144 105/114/144 43/115/144
+f 39/102/145 103/101/145 41/116/145
+f 65/90/146 67/117/146 1/91/146
+f 35/120/147 99/118/147 37/119/147
+f 97/93/148 99/118/148 33/94/148
+f 32/8/5 40/9/5 16/6/5
+f 74/197/57 73/122/57 72/124/57
+f 86/131/58 88/125/58 85/127/58
+f 26/149/59 24/128/59 25/130/59
+f 84/142/149 86/131/149 83/132/149
+f 114/191/61 116/133/61 113/135/61
+f 20/189/62 18/136/62 19/137/62
+f 80/224/63 82/138/63 79/140/63
+f 14/210/64 13/141/64 16/1/64
+f 82/225/65 84/142/65 81/143/65
+f 10/148/66 9/144/66 12/146/66
+f 8/188/67 7/147/67 10/148/67
+f 28/198/68 26/149/68 27/150/68
+f 4/154/69 3/151/69 6/153/69
+f 2/36/150 1/38/150 4/154/150
+f 58/58/151 57/57/151 60/157/151
+f 49/107/152 47/111/152 113/108/152
+f 60/157/153 59/156/153 62/44/153
+f 93/96/154 95/158/154 29/97/154
+f 54/162/77 53/159/77 56/160/77
+f 52/204/155 51/161/155 54/162/155
+f 88/125/79 90/163/79 87/126/79
+f 116/133/80 118/165/80 115/134/80
+f 46/207/81 44/167/81 45/169/81
+f 44/167/82 42/170/82 43/168/82
+f 42/170/156 40/172/156 41/171/156
+f 40/172/84 38/174/84 39/173/84
+f 38/174/157 36/176/157 37/175/157
+f 36/176/158 34/42/158 35/177/158
+f 120/182/88 122/179/88 119/181/88
+f 118/165/89 120/182/89 117/166/89
+f 90/163/90 92/183/90 89/164/90
+f 48/226/91 47/185/91 50/187/91
+f 6/153/92 5/152/92 8/188/92
+f 22/209/93 20/189/93 21/190/93
+f 112/227/94 114/191/94 111/192/94
+f 80/224/95 79/140/95 78/194/95
+f 78/194/96 77/193/96 76/196/96
+f 76/196/97 75/195/97 74/197/97
+f 30/39/159 28/198/159 29/40/159
+f 72/124/99 71/123/99 70/200/99
+f 70/200/100 69/199/100 68/202/100
+f 66/46/160 68/202/160 65/47/160
+f 50/187/161 49/186/161 52/204/161
+f 124/206/104 126/54/104 123/205/104
+f 122/179/105 124/206/105 121/180/105
+f 91/184/162 92/183/162 93/51/162
+f 48/228/107 46/207/107 47/208/107
+f 24/128/108 22/209/108 23/129/108
+f 12/146/109 11/145/109 14/210/109
+f 112/227/110 111/192/110 110/212/110
+f 110/212/111 109/211/111 108/214/111
+f 108/214/112 107/213/112 106/216/112
+f 106/216/113 105/215/113 104/218/113
+f 104/218/114 103/217/114 102/220/114
+f 102/220/115 101/219/115 100/222/115
+f 100/222/116 99/221/116 98/52/116
+g VSLampen_Cube.004_Green
+usemtl Green
+f 272/1/1 271/2/1 273/3/1
+f 320/4/2 260/5/2 272/6/2
+f 272/6/3 276/7/3 288/8/3
+f 320/4/4 296/9/4 304/10/4
+f 304/10/4 308/11/4 320/4/4
+f 272/6/3 260/5/3 264/12/3
+f 264/12/3 266/13/3 272/6/3
+f 288/8/3 276/7/3 280/14/3
+f 280/14/2 284/15/2 288/8/2
+f 288/8/5 290/16/5 296/9/5
+f 296/9/4 300/17/4 302/18/4
+f 320/4/4 308/11/4 312/19/4
+f 312/19/4 316/20/4 320/4/4
+f 320/4/6 258/21/6 260/5/6
+f 260/5/7 262/22/7 264/12/7
+f 272/6/3 266/13/3 268/23/3
+f 268/23/3 270/24/3 272/6/3
+f 272/6/8 274/25/8 276/7/8
+f 276/7/2 278/26/2 280/14/2
+f 280/14/9 282/27/9 284/15/9
+f 284/15/3 286/28/3 288/8/3
+f 296/9/10 290/16/10 292/29/10
+f 292/29/11 294/30/11 296/9/11
+f 296/9/12 298/31/12 300/17/12
+f 296/9/4 302/18/4 304/10/4
+f 304/10/13 306/32/13 308/11/13
+f 308/11/4 310/33/4 312/19/4
+f 312/19/14 314/34/14 316/20/14
+f 316/20/15 318/35/15 320/4/15
+f 258/36/16 320/37/16 257/38/16
+f 286/39/17 285/40/17 288/41/17
+f 290/42/18 288/41/18 289/43/18
+f 318/44/19 317/45/19 320/37/19
+f 322/46/20 321/47/20 384/48/20
+f 350/49/21 352/50/21 349/51/21
+f 354/52/22 353/53/22 352/50/22
+f 382/54/23 384/48/23 381/55/23
+f 311/56/24 313/57/24 314/58/24
+f 313/59/25 377/60/25 379/61/25
+f 343/62/163 345/63/163 281/64/163
+f 335/65/27 337/66/27 273/67/27
+f 331/68/28 267/69/28 265/70/28
+f 337/66/29 339/71/29 275/72/29
+f 379/61/30 381/73/30 317/74/30
+f 329/75/31 265/70/31 263/76/31
+f 335/65/32 271/77/32 269/78/32
+f 341/79/33 343/62/33 279/80/33
+f 267/69/34 331/68/34 333/81/34
+f 339/71/35 341/79/35 277/82/35
+f 327/83/36 263/76/36 261/84/36
+f 325/85/37 261/84/37 259/86/37
+f 345/63/38 347/87/38 283/88/38
+f 383/89/39 321/90/39 257/91/39
+f 381/73/40 383/89/40 319/92/40
+f 353/93/41 289/94/41 287/95/41
+f 347/87/42 349/96/42 285/97/42
+f 311/98/164 375/99/164 377/60/164
+f 357/100/44 359/101/44 295/102/44
+f 309/103/45 373/104/45 375/99/45
+f 307/105/46 371/106/46 373/104/46
+f 305/107/47 369/108/47 371/106/47
+f 365/109/48 367/110/48 303/111/48
+f 363/112/49 365/109/49 301/113/49
+f 361/114/50 363/112/50 299/115/50
+f 359/101/51 361/114/51 297/116/51
+f 323/117/52 259/86/52 257/91/52
+f 355/118/53 357/100/53 293/119/53
+f 355/118/54 291/120/54 289/94/54
+f 296/9/55 320/4/55 272/6/55
+f 320/37/56 319/121/56 257/38/56
+f 329/122/57 327/123/57 328/124/57
+f 344/125/58 343/126/58 341/127/58
+f 280/128/59 279/129/59 281/130/59
+f 342/131/60 341/127/60 339/132/60
+f 372/133/61 371/134/61 369/135/61
+f 274/136/62 273/3/62 275/137/62
+f 338/138/63 337/139/63 335/140/63
+f 269/141/64 271/2/64 272/1/64
+f 340/142/65 339/132/65 337/143/65
+f 265/144/66 267/145/66 268/146/66
+f 263/147/67 265/144/67 266/148/67
+f 282/149/68 281/130/68 283/150/68
+f 259/151/69 261/152/69 262/153/69
+f 257/38/70 259/151/70 260/154/70
+f 285/40/71 287/155/71 288/41/71
+f 288/41/72 287/155/72 289/43/72
+f 313/57/73 315/156/73 316/157/73
+f 303/111/74 367/110/74 369/108/74
+f 315/156/75 317/45/75 318/44/75
+f 351/158/76 287/95/76 285/97/76
+f 309/159/77 311/56/77 312/160/77
+f 307/161/78 309/159/78 310/162/78
+f 346/163/79 345/164/79 343/126/79
+f 374/165/80 373/166/80 371/134/80
+f 300/167/81 299/168/81 301/169/81
+f 298/170/82 297/171/82 299/168/82
+f 296/172/83 295/173/83 297/171/83
+f 294/174/84 293/175/84 295/173/84
+f 292/176/85 291/177/85 293/175/85
+f 290/42/86 289/43/86 291/177/86
+f 317/45/87 319/121/87 320/37/87
+f 321/47/20 383/178/20 384/48/20
+f 378/179/88 377/180/88 375/181/88
+f 376/182/89 375/181/89 373/166/89
+f 348/183/90 347/184/90 345/164/90
+f 303/185/91 305/186/91 306/187/91
+f 261/152/92 263/147/92 264/188/92
+f 276/189/93 275/137/93 277/190/93
+f 370/191/94 369/135/94 367/192/94
+f 335/140/95 333/193/95 334/194/95
+f 333/193/96 331/195/96 332/196/96
+f 331/195/97 329/122/97 330/197/97
+f 284/198/98 283/150/98 285/40/98
+f 327/123/99 325/199/99 326/200/99
+f 325/199/100 323/201/100 324/202/100
+f 324/202/101 323/201/101 321/47/101
+f 352/50/21 351/203/21 349/51/21
+f 353/53/102 351/203/102 352/50/102
+f 305/186/103 307/161/103 308/204/103
+f 382/54/104 381/55/104 379/205/104
+f 380/206/105 379/205/105 377/180/105
+f 348/183/106 350/49/106 349/51/106
+f 302/207/107 301/169/107 303/208/107
+f 278/209/108 277/190/108 279/129/108
+f 267/145/109 269/141/109 270/210/109
+f 367/192/110 365/211/110 366/212/110
+f 365/211/111 363/213/111 364/214/111
+f 363/213/112 361/215/112 362/216/112
+f 361/215/113 359/217/113 360/218/113
+f 359/217/114 357/219/114 358/220/114
+f 357/219/115 355/221/115 356/222/115
+f 355/221/116 353/53/116 354/52/116
+f 384/48/23 383/178/23 381/55/23
+f 274/136/117 272/1/117 273/3/117
+f 312/160/118 311/56/118 314/58/118
+f 315/223/119 313/59/119 379/61/119
+f 279/80/165 343/62/165 281/64/165
+f 271/77/121 335/65/121 273/67/121
+f 329/75/122 331/68/122 265/70/122
+f 273/67/123 337/66/123 275/72/123
+f 315/223/124 379/61/124 317/74/124
+f 327/83/166 329/75/166 263/76/166
+f 333/81/126 335/65/126 269/78/126
+f 277/82/127 341/79/127 279/80/127
+f 269/78/128 267/69/128 333/81/128
+f 275/72/129 339/71/129 277/82/129
+f 325/85/130 327/83/130 261/84/130
+f 323/117/131 325/85/131 259/86/131
+f 281/64/132 345/63/132 283/88/132
+f 319/92/133 383/89/133 257/91/133
+f 317/74/134 381/73/134 319/92/134
+f 351/158/135 353/93/135 287/95/135
+f 283/88/136 347/87/136 285/97/136
+f 313/59/137 311/98/137 377/60/137
+f 293/119/138 357/100/138 295/102/138
+f 311/98/139 309/103/139 375/99/139
+f 309/103/140 307/105/140 373/104/140
+f 307/105/141 305/107/141 371/106/141
+f 301/113/142 365/109/142 303/111/142
+f 299/115/143 363/112/143 301/113/143
+f 297/116/167 361/114/167 299/115/167
+f 295/102/145 359/101/145 297/116/145
+f 321/90/146 323/117/146 257/91/146
+f 291/120/147 355/118/147 293/119/147
+f 353/93/148 355/118/148 289/94/148
+f 288/8/5 296/9/5 272/6/5
+f 330/197/57 329/122/57 328/124/57
+f 342/131/58 344/125/58 341/127/58
+f 282/149/59 280/128/59 281/130/59
+f 340/142/149 342/131/149 339/132/149
+f 370/191/61 372/133/61 369/135/61
+f 276/189/62 274/136/62 275/137/62
+f 336/224/63 338/138/63 335/140/63
+f 270/210/64 269/141/64 272/1/64
+f 338/225/65 340/142/65 337/143/65
+f 266/148/66 265/144/66 268/146/66
+f 264/188/67 263/147/67 266/148/67
+f 284/198/68 282/149/68 283/150/68
+f 260/154/69 259/151/69 262/153/69
+f 258/36/150 257/38/150 260/154/150
+f 314/58/151 313/57/151 316/157/151
+f 305/107/152 303/111/152 369/108/152
+f 316/157/153 315/156/153 318/44/153
+f 349/96/168 351/158/168 285/97/168
+f 310/162/77 309/159/77 312/160/77
+f 308/204/155 307/161/155 310/162/155
+f 344/125/79 346/163/79 343/126/79
+f 372/133/80 374/165/80 371/134/80
+f 302/207/81 300/167/81 301/169/81
+f 300/167/82 298/170/82 299/168/82
+f 298/170/156 296/172/156 297/171/156
+f 296/172/84 294/174/84 295/173/84
+f 294/174/157 292/176/157 293/175/157
+f 292/176/158 290/42/158 291/177/158
+f 376/182/88 378/179/88 375/181/88
+f 374/165/89 376/182/89 373/166/89
+f 346/163/90 348/183/90 345/164/90
+f 304/226/91 303/185/91 306/187/91
+f 262/153/92 261/152/92 264/188/92
+f 278/209/93 276/189/93 277/190/93
+f 368/227/94 370/191/94 367/192/94
+f 336/224/95 335/140/95 334/194/95
+f 334/194/96 333/193/96 332/196/96
+f 332/196/97 331/195/97 330/197/97
+f 286/39/159 284/198/159 285/40/159
+f 328/124/99 327/123/99 326/200/99
+f 326/200/100 325/199/100 324/202/100
+f 322/46/160 324/202/160 321/47/160
+f 306/187/161 305/186/161 308/204/161
+f 380/206/104 382/54/104 379/205/104
+f 378/179/105 380/206/105 377/180/105
+f 347/184/162 348/183/162 349/51/162
+f 304/228/107 302/207/107 303/208/107
+f 280/128/108 278/209/108 279/129/108
+f 268/146/109 267/145/109 270/210/109
+f 368/227/110 367/192/110 366/212/110
+f 366/212/111 365/211/111 364/214/111
+f 364/214/112 363/213/112 362/216/112
+f 362/216/113 361/215/113 360/218/113
+f 360/218/114 359/217/114 358/220/114
+f 358/220/115 357/219/115 356/222/115
+f 356/222/116 355/221/116 354/52/116
+g VSLampen_Cube.004_Distance
+usemtl Distance
+f 132/5/169 134/22/169 136/12/169
+f 185/59/170 249/60/170 251/61/170
+f 176/10/171 180/11/171 192/4/171
+f 168/9/172 162/16/172 164/29/172
+f 252/206/173 251/205/173 249/180/173
+f 152/14/174 156/15/174 160/8/174
+f 188/20/175 190/35/175 192/4/175
+f 187/156/176 189/45/176 190/44/176
+f 225/53/177 223/203/177 224/50/177
+f 220/183/178 219/184/178 217/164/178
+f 248/182/179 247/181/179 245/166/179
+f 164/29/180 166/30/180 168/9/180
+f 156/198/181 155/150/181 157/40/181
+f 168/9/182 172/17/182 174/18/182
+f 205/193/183 203/195/183 204/196/183
+f 201/122/184 199/123/184 200/124/184
+f 231/101/185 233/114/185 169/116/185
+f 210/138/186 209/139/186 207/140/186
+f 133/152/187 135/147/187 136/188/187
+f 158/39/188 157/40/188 160/41/188
+f 141/141/189 143/2/189 144/1/189
+f 160/8/190 148/7/190 152/14/190
+f 180/11/191 182/33/191 184/19/191
+f 176/10/192 178/32/192 180/11/192
+f 190/44/193 189/45/193 192/37/193
+f 197/199/194 195/201/194 196/202/194
+f 192/37/195 191/121/195 129/38/195
+f 203/68/196 139/69/196 137/70/196
+f 211/71/197 213/79/197 149/82/197
+f 192/4/171 168/9/171 176/10/171
+f 177/107/198 241/108/198 243/106/198
+f 144/6/199 138/13/199 140/23/199
+f 237/109/200 239/110/200 175/111/200
+f 160/41/201 159/155/201 161/43/201
+f 135/147/202 137/144/202 138/148/202
+f 140/23/190 142/24/190 144/6/190
+f 144/1/203 143/2/203 145/3/203
+f 251/61/204 253/73/204 189/74/204
+f 242/191/205 241/135/205 239/192/205
+f 225/93/206 161/94/206 159/95/206
+f 207/65/207 209/66/207 145/67/207
+f 166/174/208 165/175/208 167/173/208
+f 193/47/209 255/178/209 256/48/209
+f 136/12/190 138/13/190 144/6/190
+f 231/217/210 229/219/210 230/220/210
+f 162/42/211 160/41/211 161/43/211
+f 179/161/212 181/159/212 182/162/212
+f 215/62/213 217/63/213 153/64/213
+f 201/75/214 137/70/214 135/76/214
+f 148/189/215 147/137/215 149/190/215
+f 150/209/216 149/190/216 151/129/216
+f 175/111/217 239/110/217 241/108/217
+f 229/100/218 231/101/218 167/102/218
+f 139/145/219 141/141/219 142/210/219
+f 239/192/220 237/211/220 238/212/220
+f 217/63/221 219/87/221 155/88/221
+f 177/186/222 179/161/222 180/204/222
+f 174/207/223 173/169/223 175/208/223
+f 152/14/224 154/27/224 156/15/224
+f 185/57/225 187/156/225 188/157/225
+f 164/176/226 163/177/226 165/175/226
+f 209/66/227 211/71/227 147/72/227
+f 172/167/228 171/168/228 173/169/228
+f 233/114/229 235/112/229 171/115/229
+f 254/54/230 256/48/230 253/55/230
+f 212/142/231 211/132/231 209/143/231
+f 226/52/232 225/53/232 224/50/232
+f 229/219/233 227/221/233 228/222/233
+f 181/103/234 245/104/234 247/99/234
+f 189/45/235 191/121/235 192/37/235
+f 184/19/236 186/34/236 188/20/236
+f 130/36/102 192/37/102 129/38/102
+f 194/46/209 193/47/209 256/48/209
+f 144/6/237 146/25/237 148/7/237
+f 144/6/190 148/7/190 160/8/190
+f 144/6/190 132/5/190 136/12/190
+f 156/15/190 158/28/190 160/8/190
+f 255/89/238 193/90/238 129/91/238
+f 152/128/239 151/129/239 153/130/239
+f 184/19/171 188/20/171 192/4/171
+f 192/4/171 180/11/171 184/19/171
+f 157/40/240 159/155/240 160/41/240
+f 137/144/241 139/145/241 140/146/241
+f 256/48/230 255/178/230 253/55/230
+f 233/215/242 231/217/242 232/218/242
+f 250/179/243 249/180/243 247/181/243
+f 131/151/244 133/152/244 134/153/244
+f 207/140/245 205/193/245 206/194/245
+f 168/9/246 170/31/246 172/17/246
+f 160/8/247 162/16/247 168/9/247
+f 181/159/248 183/56/248 184/160/248
+f 168/9/171 174/18/171 176/10/171
+f 196/202/249 195/201/249 193/47/249
+f 179/105/250 243/106/250 245/104/250
+f 168/172/251 167/173/251 169/171/251
+f 220/183/252 222/49/252 221/51/252
+f 192/4/253 132/5/253 144/6/253
+f 146/136/254 145/3/254 147/137/254
+f 235/112/255 237/109/255 173/113/255
+f 192/4/256 130/21/256 132/5/256
+f 195/117/257 131/86/257 129/91/257
+f 227/221/258 225/53/258 226/52/258
+f 183/98/259 247/99/259 249/60/259
+f 253/73/260 255/89/260 191/92/260
+f 216/125/261 215/126/261 213/127/261
+f 237/211/262 235/213/262 236/214/262
+f 199/123/263 197/199/263 198/200/263
+f 254/54/264 253/55/264 251/205/264
+f 224/50/265 223/203/265 221/51/265
+f 213/79/266 215/62/266 151/80/266
+f 148/7/253 150/26/253 152/14/253
+f 222/49/265 224/50/265 221/51/265
+f 197/85/267 133/84/267 131/86/267
+f 218/163/268 217/164/268 215/126/268
+f 203/195/269 201/122/269 202/197/269
+f 235/213/270 233/215/270 234/216/270
+f 227/118/271 163/120/271 161/94/271
+f 154/149/272 153/130/272 155/150/272
+f 175/185/273 177/186/273 178/187/273
+f 219/87/274 221/96/274 157/97/274
+f 199/83/275 135/76/275 133/84/275
+f 139/69/276 203/68/276 205/81/276
+f 170/170/277 169/171/277 171/168/277
+f 183/56/278 185/57/278 186/58/278
+f 227/118/279 229/100/279 165/119/279
+f 168/9/280 192/4/280 144/6/280
+f 246/165/281 245/166/281 243/134/281
+f 162/42/282 161/43/282 163/177/282
+f 223/158/283 159/95/283 157/97/283
+f 129/38/284 131/151/284 132/154/284
+f 244/133/285 243/134/285 241/135/285
+f 214/131/286 213/127/286 211/132/286
+f 207/65/287 143/77/287 141/78/287
+f 146/136/288 144/1/288 145/3/288
+f 184/160/289 183/56/289 186/58/289
+f 187/223/290 185/59/290 251/61/290
+f 151/80/291 215/62/291 153/64/291
+f 143/77/292 207/65/292 145/67/292
+f 201/75/293 203/68/293 137/70/293
+f 145/67/294 209/66/294 147/72/294
+f 187/223/295 251/61/295 189/74/295
+f 199/83/296 201/75/296 135/76/296
+f 205/81/297 207/65/297 141/78/297
+f 149/82/298 213/79/298 151/80/298
+f 141/78/299 139/69/299 205/81/299
+f 147/72/300 211/71/300 149/82/300
+f 197/85/301 199/83/301 133/84/301
+f 195/117/302 197/85/302 131/86/302
+f 153/64/303 217/63/303 155/88/303
+f 191/92/304 255/89/304 129/91/304
+f 189/74/305 253/73/305 191/92/305
+f 223/158/306 225/93/306 159/95/306
+f 155/88/307 219/87/307 157/97/307
+f 185/59/308 183/98/308 249/60/308
+f 165/119/309 229/100/309 167/102/309
+f 183/98/310 181/103/310 247/99/310
+f 181/103/311 179/105/311 245/104/311
+f 179/105/312 177/107/312 243/106/312
+f 173/113/313 237/109/313 175/111/313
+f 171/115/314 235/112/314 173/113/314
+f 169/116/315 233/114/315 171/115/315
+f 167/102/316 231/101/316 169/116/316
+f 193/90/317 195/117/317 129/91/317
+f 163/120/318 227/118/318 165/119/318
+f 225/93/319 227/118/319 161/94/319
+f 160/8/247 168/9/247 144/6/247
+f 202/197/184 201/122/184 200/124/184
+f 214/131/261 216/125/261 213/127/261
+f 154/149/239 152/128/239 153/130/239
+f 212/142/286 214/131/286 211/132/286
+f 242/191/285 244/133/285 241/135/285
+f 148/189/254 146/136/254 147/137/254
+f 208/224/186 210/138/186 207/140/186
+f 142/210/189 141/141/189 144/1/189
+f 210/225/231 212/142/231 209/143/231
+f 138/148/241 137/144/241 140/146/241
+f 136/188/202 135/147/202 138/148/202
+f 156/198/272 154/149/272 155/150/272
+f 132/154/244 131/151/244 134/153/244
+f 130/36/320 129/38/320 132/154/320
+f 186/58/321 185/57/321 188/157/321
+f 177/107/322 175/111/322 241/108/322
+f 188/157/323 187/156/323 190/44/323
+f 221/96/324 223/158/324 157/97/324
+f 182/162/248 181/159/248 184/160/248
+f 180/204/212 179/161/212 182/162/212
+f 216/125/268 218/163/268 215/126/268
+f 244/133/281 246/165/281 243/134/281
+f 174/207/228 172/167/228 173/169/228
+f 172/167/277 170/170/277 171/168/277
+f 170/170/325 168/172/325 169/171/325
+f 168/172/208 166/174/208 167/173/208
+f 166/174/226 164/176/226 165/175/226
+f 164/176/326 162/42/326 163/177/326
+f 248/182/243 250/179/243 247/181/243
+f 246/165/179 248/182/179 245/166/179
+f 218/163/178 220/183/178 217/164/178
+f 176/226/273 175/185/273 178/187/273
+f 134/153/187 133/152/187 136/188/187
+f 150/209/327 148/189/327 149/190/327
+f 240/227/205 242/191/205 239/192/205
+f 208/224/245 207/140/245 206/194/245
+f 206/194/183 205/193/183 204/196/183
+f 204/196/269 203/195/269 202/197/269
+f 158/39/328 156/198/328 157/40/328
+f 200/124/263 199/123/263 198/200/263
+f 198/200/194 197/199/194 196/202/194
+f 194/46/329 196/202/329 193/47/329
+f 178/187/222 177/186/222 180/204/222
+f 252/206/264 254/54/264 251/205/264
+f 250/179/173 252/206/173 249/180/173
+f 219/184/330 220/183/330 221/51/330
+f 176/228/223 174/207/223 175/208/223
+f 152/128/216 150/209/216 151/129/216
+f 140/146/219 139/145/219 142/210/219
+f 240/227/220 239/192/220 238/212/220
+f 238/212/262 237/211/262 236/214/262
+f 236/214/270 235/213/270 234/216/270
+f 234/216/242 233/215/242 232/218/242
+f 232/218/210 231/217/210 230/220/210
+f 230/220/233 229/219/233 228/222/233
+f 228/222/258 227/221/258 226/52/258
+g VSLampen_Cube.004_Repeater
+usemtl Repeater
+f 447/46/329 445/202/329 448/47/329
+f 485/15/190 483/28/190 481/8/190
+f 495/136/254 496/3/254 494/137/254
+f 495/136/288 497/1/288 496/3/288
+f 389/206/173 390/205/173 392/180/173
+f 462/105/312 464/107/312 398/106/312
+f 452/45/235 450/121/235 449/37/235
+f 511/36/102 449/37/102 512/38/102
+f 497/6/199 503/13/199 501/23/199
+f 497/1/203 498/2/203 496/3/203
+f 416/53/177 418/203/177 417/50/177
+f 418/158/283 482/95/283 484/97/283
+f 452/74/305 388/73/305 450/92/305
+f 466/185/273 464/186/273 463/187/273
+f 466/111/217 402/110/217 400/108/217
+f 424/63/331 422/87/331 486/88/331
+f 438/195/269 440/122/269 439/197/269
+f 462/161/212 460/159/212 459/162/212
+f 437/196/269 438/195/269 439/197/269
+f 494/72/300 430/71/300 492/82/300
+f 459/162/248 460/159/248 457/160/248
+f 487/149/239 489/128/239 488/130/239
+f 434/140/245 436/193/245 435/194/245
+f 406/112/255 404/109/255 468/113/255
+f 412/219/233 414/221/233 413/222/233
+f 456/57/225 454/156/225 453/157/225
+f 411/220/233 412/219/233 413/222/233
+f 393/182/243 391/179/243 394/181/243
+f 416/93/319 414/118/319 480/94/319
+f 442/83/332 506/76/332 508/84/332
+f 449/4/253 509/5/253 497/6/253
+f 475/174/226 477/176/226 476/175/226
+f 458/98/259 394/99/259 392/60/259
+f 496/67/294 432/66/294 494/72/294
+f 507/153/187 508/152/187 505/188/187
+f 397/133/281 395/165/281 398/134/281
+f 464/107/322 466/111/322 400/108/322
+f 429/142/231 430/132/231 432/143/231
+f 436/193/183 438/195/183 437/196/183
+f 454/223/295 390/61/295 452/74/295
+f 499/210/189 500/141/189 497/1/189
+f 479/42/282 480/43/282 478/177/282
+f 432/66/227 430/71/227 494/72/227
+f 465/226/273 466/185/273 463/187/273
+f 444/199/194 446/201/194 445/202/194
+f 440/75/293 438/68/293 504/70/293
+f 460/159/248 458/56/248 457/160/248
+f 456/59/308 458/98/308 392/60/308
+f 491/209/327 493/189/327 492/190/327
+f 429/142/286 427/131/286 430/132/286
+f 478/120/318 414/118/318 476/119/318
+f 388/73/260 386/89/260 450/92/260
+f 471/170/277 472/171/277 470/168/277
+f 467/207/228 469/167/228 468/169/228
+f 431/138/186 432/139/186 434/140/186
+f 450/92/304 386/89/304 512/91/304
+f 473/172/251 474/173/251 472/171/251
+f 387/54/264 388/55/264 390/205/264
+f 468/113/313 404/109/313 466/111/313
+f 497/6/237 495/25/237 493/7/237
+f 427/131/286 428/127/286 430/132/286
+f 399/191/285 397/133/285 400/135/285
+f 428/79/266 426/62/266 490/80/266
+f 493/7/333 491/26/333 489/14/333
+f 509/5/334 507/22/334 505/12/334
+f 410/217/210 412/219/210 411/220/210
+f 464/107/198 400/108/198 398/106/198
+f 438/68/196 502/69/196 504/70/196
+f 385/48/230 386/178/230 388/55/230
+f 460/103/234 396/104/234 394/99/234
+f 386/89/335 448/90/335 512/91/335
+f 409/218/210 410/217/210 411/220/210
+f 469/167/228 470/168/228 468/169/228
+f 509/154/244 510/151/244 507/153/244
+f 511/36/320 512/38/320 509/154/320
+f 443/200/194 444/199/194 445/202/194
+f 488/64/336 424/63/336 486/88/336
+f 405/214/270 406/213/270 407/216/270
+f 470/115/314 406/112/314 468/113/314
+f 489/128/239 490/129/239 488/130/239
+f 473/9/337 471/31/337 469/17/337
+f 444/85/338 442/83/338 508/84/338
+f 395/165/179 393/182/179 396/166/179
+f 456/59/339 392/60/339 390/61/339
+f 465/10/192 463/32/192 461/11/192
+f 453/20/175 451/35/175 449/4/175
+f 389/206/264 387/54/264 390/205/264
+f 493/189/215 494/137/215 492/190/215
+f 422/87/274 420/96/274 484/97/274
+f 498/77/292 434/65/292 496/67/292
+f 408/215/242 410/217/242 409/218/242
+f 487/149/272 488/130/272 486/150/272
+f 408/114/229 406/112/229 470/115/229
+f 445/202/249 446/201/249 448/47/249
+f 502/69/276 438/68/276 436/81/276
+f 506/147/202 504/144/202 503/148/202
+f 463/187/222 464/186/222 461/204/222
+f 503/148/241 504/144/241 501/146/241
+f 404/211/262 406/213/262 405/214/262
+f 489/128/216 491/209/216 490/129/216
+f 501/146/219 502/145/219 499/210/219
+f 449/4/171 473/9/171 465/10/171
+f 504/144/241 502/145/241 501/146/241
+f 387/54/230 385/48/230 388/55/230
+f 434/65/207 432/66/207 496/67/207
+f 483/39/188 484/40/188 481/41/188
+f 467/207/223 468/169/223 466/208/223
+f 473/9/340 479/16/340 477/29/340
+f 404/109/341 402/110/341 466/111/341
+f 472/116/342 408/114/342 470/115/342
+f 439/197/184 440/122/184 441/124/184
+f 481/41/201 482/155/201 480/43/201
+f 391/179/173 389/206/173 392/180/173
+f 399/191/205 400/135/205 402/192/205
+f 502/145/219 500/141/219 499/210/219
+f 481/8/190 493/7/190 489/14/190
+f 457/160/289 458/56/289 455/58/289
+f 476/119/309 412/100/309 474/102/309
+f 449/4/256 511/21/256 509/5/256
+f 492/82/298 428/79/298 490/80/298
+f 483/39/328 485/198/328 484/40/328
+f 473/172/208 475/174/208 474/173/208
+f 423/163/268 424/164/268 426/126/268
+f 457/19/171 453/20/171 449/4/171
+f 473/9/280 449/4/280 497/6/280
+f 505/188/202 506/147/202 503/148/202
+f 477/29/343 475/30/343 473/9/343
+f 454/223/290 456/59/290 390/61/290
+f 436/81/297 434/65/297 500/78/297
+f 401/227/220 402/192/220 403/212/220
+f 473/9/171 469/17/171 467/18/171
+f 448/47/209 386/178/209 385/48/209
+f 461/204/212 462/161/212 459/162/212
+f 485/198/272 487/149/272 486/150/272
+f 484/40/240 482/155/240 481/41/240
+f 497/6/190 493/7/190 481/8/190
+f 474/102/316 410/101/316 472/116/316
+f 453/157/323 454/156/323 451/44/323
+f 477/176/326 479/42/326 478/177/326
+f 431/225/231 429/142/231 432/143/231
+f 447/46/209 448/47/209 385/48/209
+f 433/224/245 434/140/245 435/194/245
+f 425/125/268 423/163/268 426/126/268
+f 481/8/247 473/9/247 497/6/247
+f 469/167/277 471/170/277 470/168/277
+f 493/189/254 495/136/254 494/137/254
+f 441/124/263 442/123/263 443/200/263
+f 465/228/223 467/207/223 466/208/223
+f 458/98/310 460/103/310 394/99/310
+f 413/222/258 414/221/258 415/52/258
+f 415/52/232 416/53/232 417/50/232
+f 442/83/296 440/75/296 506/76/296
+f 418/158/306 416/93/306 482/95/306
+f 449/4/171 461/11/171 457/19/171
+f 395/165/281 396/166/281 398/134/281
+f 391/179/243 392/180/243 394/181/243
+f 473/9/171 467/18/171 465/10/171
+f 421/183/252 419/49/252 420/51/252
+f 442/123/263 444/199/263 443/200/263
+f 419/49/265 417/50/265 420/51/265
+f 406/213/270 408/215/270 407/216/270
+f 458/56/278 456/57/278 455/58/278
+f 407/216/242 408/215/242 409/218/242
+f 397/133/285 398/134/285 400/135/285
+f 490/80/291 426/62/291 488/64/291
+f 455/58/321 456/57/321 453/157/321
+f 402/192/220 404/211/220 403/212/220
+f 475/174/208 476/175/208 474/173/208
+f 460/103/344 462/105/344 396/104/344
+f 505/12/190 503/13/190 497/6/190
+f 485/198/181 486/150/181 484/40/181
+f 433/224/186 431/138/186 434/140/186
+f 426/62/213 424/63/213 488/64/213
+f 420/96/345 418/158/345 484/97/345
+f 479/42/211 481/41/211 480/43/211
+f 423/163/178 421/183/178 424/164/178
+f 401/227/205 399/191/205 402/192/205
+f 486/88/307 422/87/307 484/97/307
+f 422/184/330 421/183/330 420/51/330
+f 403/212/262 404/211/262 405/214/262
+f 497/6/190 509/5/190 505/12/190
+f 446/117/257 510/86/257 512/91/257
+f 457/19/236 455/34/236 453/20/236
+f 500/78/346 502/69/346 436/81/346
+f 510/151/244 508/152/244 507/153/244
+f 481/8/247 479/16/247 473/9/247
+f 416/93/206 480/94/206 482/95/206
+f 393/182/179 394/181/179 396/166/179
+f 417/50/265 418/203/265 420/51/265
+f 444/85/267 508/84/267 510/86/267
+f 414/118/347 478/120/347 480/94/347
+f 414/118/279 412/100/279 476/119/279
+f 512/38/284 510/151/284 509/154/284
+f 434/65/287 498/77/287 500/78/287
+f 454/156/176 452/45/176 451/44/176
+f 421/183/178 422/184/178 424/164/178
+f 390/61/204 388/73/204 452/74/204
+f 414/221/258 416/53/258 415/52/258
+f 435/194/183 436/193/183 437/196/183
+f 427/131/261 425/125/261 428/127/261
+f 440/122/184 442/123/184 441/124/184
+f 440/75/214 504/70/214 506/76/214
+f 491/209/216 492/190/216 490/129/216
+f 412/100/218 410/101/218 474/102/218
+f 446/117/302 444/85/302 510/86/302
+f 464/186/222 462/161/222 461/204/222
+f 489/14/224 487/27/224 485/15/224
+f 477/176/226 478/177/226 476/175/226
+f 462/105/250 398/106/250 396/104/250
+f 465/10/171 461/11/171 449/4/171
+f 489/14/174 485/15/174 481/8/174
+f 501/23/190 499/24/190 497/6/190
+f 425/125/261 426/126/261 428/127/261
+f 448/90/317 446/117/317 512/91/317
+f 471/170/325 473/172/325 472/171/325
+f 410/101/348 408/114/348 472/116/348
+f 508/152/187 506/147/187 505/188/187
+f 500/141/189 498/2/189 497/1/189
+f 461/11/191 459/33/191 457/19/191
+f 451/44/193 452/45/193 449/37/193
+f 449/37/195 450/121/195 512/38/195
+f 430/71/197 428/79/197 492/82/197
diff --git a/advtrains_signals_ks/models/advtrains_signals_ks_lamps_main.obj b/advtrains_signals_ks/models/advtrains_signals_ks_lamps_main.obj
new file mode 100644
index 0000000..4949466
--- /dev/null
+++ b/advtrains_signals_ks/models/advtrains_signals_ks_lamps_main.obj
@@ -0,0 +1,2719 @@
+# Blender v2.76 (sub 0) OBJ File: 'signal_ks_export.blend'
+# www.blender.org
+mtllib advtrains_signals_ks_lamps_main.mtl
+o HSLampen_Cube.000
+v 1.356099 -0.866613 5.864285
+v 1.356098 -0.872114 5.594683
+v 1.324589 -0.761419 6.389298
+v 1.324588 -0.766923 5.594215
+v 1.273160 -0.664393 6.388865
+v 1.273160 -0.669894 5.593781
+v 1.203793 -0.579264 6.388486
+v 1.203794 -0.584764 5.593402
+v 1.119149 -0.509304 6.388174
+v 1.119148 -0.514805 5.593091
+v 1.022483 -0.457200 6.387940
+v 1.022485 -0.462700 5.592857
+v 0.917511 -0.424956 6.387797
+v 0.917511 -0.430456 5.592714
+v 0.808263 -0.413814 6.387748
+v 0.808264 -0.419312 5.592665
+v 0.698939 -0.424194 6.387794
+v 0.698940 -0.429696 5.592710
+v 0.593745 -0.455702 6.387936
+v 0.593744 -0.461204 5.592852
+v 0.496716 -0.507132 6.388164
+v 0.496716 -0.512632 5.593081
+v 0.411588 -0.576496 6.388473
+v 0.411586 -0.581998 5.593389
+v 0.341628 -0.661139 6.388850
+v 0.341625 -0.666642 5.593767
+v 0.289521 -0.757806 6.389282
+v 0.289522 -0.763307 5.594199
+v 0.257278 -0.862776 5.864267
+v 0.257278 -0.868280 5.594666
+v 0.245926 -0.976844 5.864237
+v 0.246134 -0.977477 5.595152
+v 0.256836 -1.083121 5.862653
+v 0.256504 -1.086783 5.595598
+v 0.288031 -1.186557 5.863041
+v 0.288019 -1.192029 5.596006
+v 0.339461 -1.283579 5.863426
+v 0.339445 -1.289057 5.596392
+v 0.408828 -1.368706 5.863764
+v 0.408815 -1.374189 5.596729
+v 0.493473 -1.438664 5.864041
+v 0.493457 -1.444154 5.597005
+v 0.590136 -1.490763 5.864248
+v 0.590124 -1.496257 5.597212
+v 0.695107 -1.523005 5.864375
+v 0.695100 -1.528503 5.597339
+v 0.804351 -1.534149 5.864418
+v 0.804351 -1.539651 5.597383
+v 0.913672 -1.523771 5.864378
+v 0.913679 -1.529268 5.597343
+v 1.018864 -1.492260 5.864253
+v 1.018874 -1.497755 5.597218
+v 1.115892 -1.440837 5.864049
+v 1.115906 -1.446325 5.597014
+v 1.201019 -1.371471 5.863774
+v 1.201035 -1.376956 5.596738
+v 1.270979 -1.286831 5.863439
+v 1.270996 -1.292309 5.596403
+v 1.323086 -1.190168 5.863057
+v 1.323098 -1.195639 5.596019
+v 1.355004 -1.086950 5.862668
+v 1.355350 -1.090617 5.595614
+v 1.366655 -0.980756 5.864254
+v 1.366481 -0.981388 5.595167
+v 1.380815 -0.862105 5.864238
+v 1.380815 -0.867288 5.229348
+v 1.347889 -0.752182 6.389203
+v 1.347889 -0.757365 5.228831
+v 1.294148 -0.650795 6.388727
+v 1.294148 -0.655976 5.228355
+v 1.221662 -0.561836 6.388309
+v 1.221663 -0.567019 5.227938
+v 1.133213 -0.488731 6.387966
+v 1.133212 -0.493915 5.227594
+v 1.032202 -0.434285 6.387711
+v 1.032202 -0.439468 5.227339
+v 0.922510 -0.400592 6.387552
+v 0.922510 -0.405775 5.227180
+v 0.808350 -0.388948 6.387497
+v 0.808351 -0.394128 5.227125
+v 0.694112 -0.399794 6.387548
+v 0.694114 -0.404978 5.227177
+v 0.584189 -0.432720 6.387703
+v 0.584189 -0.437905 5.227331
+v 0.482798 -0.486462 6.387955
+v 0.482799 -0.491644 5.227584
+v 0.393842 -0.558946 6.388296
+v 0.393842 -0.564128 5.227924
+v 0.320737 -0.647394 6.388711
+v 0.320736 -0.652577 5.228340
+v 0.266289 -0.748405 6.389186
+v 0.266290 -0.753588 5.228814
+v 0.232596 -0.858097 5.864219
+v 0.232596 -0.863279 5.229330
+v 0.220950 -0.972254 5.864219
+v 0.220951 -0.977436 5.229865
+v 0.231799 -1.086489 5.862726
+v 0.231799 -1.091671 5.230402
+v 0.264725 -1.196414 5.863242
+v 0.264724 -1.201597 5.230918
+v 0.318466 -1.297802 5.863719
+v 0.318465 -1.302985 5.231395
+v 0.390952 -1.386758 5.864136
+v 0.390953 -1.391941 5.231813
+v 0.479402 -1.459865 5.864480
+v 0.479401 -1.465047 5.232156
+v 0.580414 -1.514309 5.864735
+v 0.580413 -1.519492 5.232411
+v 0.690105 -1.548002 5.864894
+v 0.690106 -1.553185 5.232570
+v 0.804264 -1.559648 5.864949
+v 0.804263 -1.564832 5.232625
+v 0.918500 -1.548801 5.864897
+v 0.918501 -1.553983 5.232574
+v 1.028426 -1.515873 5.864743
+v 1.028425 -1.521055 5.232419
+v 1.129816 -1.462135 5.864491
+v 1.129816 -1.467316 5.232167
+v 1.218773 -1.389648 5.864150
+v 1.218772 -1.394831 5.231826
+v 1.291878 -1.301200 5.863734
+v 1.291878 -1.306382 5.231411
+v 1.346324 -1.200189 5.863259
+v 1.346325 -1.205372 5.230936
+v 1.380017 -1.090498 5.862744
+v 1.380018 -1.095680 5.230421
+v 1.391665 -0.976340 5.864237
+v 1.391664 -0.981523 5.229884
+v -0.966289 1.760967 5.864285
+v -0.966289 1.758691 5.594683
+v -0.979322 1.804477 6.389298
+v -0.979322 1.802200 5.594215
+v -1.000594 1.844609 6.388865
+v -1.000594 1.842333 5.593781
+v -1.029285 1.879819 6.388486
+v -1.029285 1.877545 5.593402
+v -1.064296 1.908756 6.388174
+v -1.064296 1.906481 5.593091
+v -1.104278 1.930307 6.387940
+v -1.104278 1.928033 5.592857
+v -1.147697 1.943644 6.387797
+v -1.147697 1.941369 5.592714
+v -1.192884 1.948253 6.387748
+v -1.192883 1.945979 5.592665
+v -1.238102 1.943959 6.387794
+v -1.238102 1.941683 5.592710
+v -1.281613 1.930927 6.387936
+v -1.281613 1.928651 5.592852
+v -1.321745 1.909655 6.388164
+v -1.321746 1.907380 5.593081
+v -1.356956 1.880964 6.388473
+v -1.356957 1.878689 5.593389
+v -1.385893 1.845954 6.388850
+v -1.385894 1.843678 5.593767
+v -1.407445 1.805971 6.389282
+v -1.407444 1.803696 5.594199
+v -1.420781 1.762553 5.864267
+v -1.420781 1.760277 5.594666
+v -1.425477 1.715373 5.864237
+v -1.425391 1.715111 5.595152
+v -1.420964 1.671415 5.862653
+v -1.421102 1.669900 5.595598
+v -1.408062 1.628632 5.863041
+v -1.408066 1.626369 5.596006
+v -1.386789 1.588502 5.863426
+v -1.386796 1.586236 5.596392
+v -1.358097 1.553291 5.863764
+v -1.358103 1.551024 5.596729
+v -1.323087 1.524355 5.864041
+v -1.323093 1.522085 5.597005
+v -1.283105 1.502807 5.864248
+v -1.283110 1.500534 5.597212
+v -1.239687 1.489471 5.864375
+v -1.239690 1.487197 5.597339
+v -1.194502 1.484861 5.864418
+v -1.194502 1.482586 5.597383
+v -1.149285 1.489154 5.864378
+v -1.149282 1.486880 5.597343
+v -1.105775 1.502187 5.864253
+v -1.105771 1.499915 5.597218
+v -1.065643 1.523457 5.864049
+v -1.065637 1.521187 5.597014
+v -1.030433 1.552148 5.863774
+v -1.030426 1.549879 5.596738
+v -1.001496 1.587157 5.863439
+v -1.001489 1.584891 5.596403
+v -0.979944 1.627138 5.863057
+v -0.979939 1.624875 5.596019
+v -0.966742 1.669832 5.862668
+v -0.966598 1.668314 5.595614
+v -0.961923 1.713755 5.864254
+v -0.961995 1.713493 5.595167
+v -0.956066 1.762831 5.864238
+v -0.956066 1.760688 5.229348
+v -0.969685 1.808297 6.389203
+v -0.969685 1.806154 5.228831
+v -0.991913 1.850233 6.388727
+v -0.991913 1.848090 5.228355
+v -1.021894 1.887028 6.388309
+v -1.021894 1.884884 5.227938
+v -1.058478 1.917265 6.387966
+v -1.058479 1.915121 5.227594
+v -1.100258 1.939785 6.387711
+v -1.100258 1.937642 5.227339
+v -1.145629 1.953721 6.387552
+v -1.145629 1.951578 5.227180
+v -1.192848 1.958538 6.387497
+v -1.192847 1.956395 5.227125
+v -1.240099 1.954051 6.387548
+v -1.240098 1.951908 5.227177
+v -1.285565 1.940433 6.387703
+v -1.285565 1.938288 5.227331
+v -1.327502 1.918204 6.387955
+v -1.327502 1.916061 5.227584
+v -1.364296 1.888223 6.388296
+v -1.364296 1.886080 5.227924
+v -1.394534 1.851640 6.388711
+v -1.394534 1.849496 5.228340
+v -1.417054 1.809860 6.389186
+v -1.417054 1.807716 5.228814
+v -1.430991 1.764489 5.864219
+v -1.430990 1.762345 5.229330
+v -1.435807 1.717272 5.864219
+v -1.435807 1.715128 5.229865
+v -1.431320 1.670022 5.862726
+v -1.431320 1.667878 5.230402
+v -1.417701 1.624554 5.863242
+v -1.417701 1.622411 5.230918
+v -1.395473 1.582619 5.863719
+v -1.395473 1.580475 5.231395
+v -1.365491 1.545825 5.864136
+v -1.365491 1.543681 5.231813
+v -1.328907 1.515587 5.864480
+v -1.328907 1.513443 5.232156
+v -1.287126 1.493068 5.864735
+v -1.287127 1.490924 5.232411
+v -1.241756 1.479131 5.864894
+v -1.241756 1.476988 5.232570
+v -1.194538 1.474315 5.864949
+v -1.194538 1.472170 5.232625
+v -1.147288 1.478801 5.864897
+v -1.147287 1.476658 5.232574
+v -1.101820 1.492421 5.864743
+v -1.101821 1.490277 5.232419
+v -1.059884 1.514648 5.864491
+v -1.059884 1.512505 5.232167
+v -1.023089 1.544630 5.864150
+v -1.023090 1.542486 5.231826
+v -0.992852 1.581213 5.863734
+v -0.992852 1.579070 5.231411
+v -0.970332 1.622993 5.863259
+v -0.970331 1.620850 5.230936
+v -0.956396 1.668364 5.862744
+v -0.956395 1.666220 5.230421
+v -0.951578 1.715581 5.864237
+v -0.951578 1.713438 5.229884
+v -0.643901 -0.866613 5.864285
+v -0.643902 -0.872114 5.594683
+v -0.675411 -0.761419 6.389298
+v -0.675412 -0.766923 5.594215
+v -0.726840 -0.664393 6.388865
+v -0.726840 -0.669894 5.593781
+v -0.796207 -0.579264 6.388486
+v -0.796206 -0.584764 5.593402
+v -0.880851 -0.509304 6.388174
+v -0.880852 -0.514805 5.593091
+v -0.977517 -0.457200 6.387940
+v -0.977515 -0.462699 5.592857
+v -1.082489 -0.424957 6.387797
+v -1.082489 -0.430456 5.592714
+v -1.191737 -0.413814 6.387748
+v -1.191736 -0.419312 5.592665
+v -1.301062 -0.424194 6.387794
+v -1.301060 -0.429696 5.592710
+v -1.406255 -0.455702 6.387936
+v -1.406256 -0.461204 5.592852
+v -1.503284 -0.507132 6.388164
+v -1.503284 -0.512632 5.593081
+v -1.588412 -0.576496 6.388473
+v -1.588414 -0.581998 5.593389
+v -1.658372 -0.661139 6.388850
+v -1.658375 -0.666641 5.593767
+v -1.710479 -0.757806 6.389282
+v -1.710478 -0.763307 5.594199
+v -1.742722 -0.862777 5.864267
+v -1.742722 -0.868280 5.594666
+v -1.754074 -0.976844 5.864237
+v -1.753866 -0.977477 5.595152
+v -1.743164 -1.083121 5.862653
+v -1.743496 -1.086783 5.595598
+v -1.711969 -1.186557 5.863041
+v -1.711981 -1.192029 5.596006
+v -1.660540 -1.283579 5.863426
+v -1.660555 -1.289057 5.596392
+v -1.591172 -1.368706 5.863764
+v -1.591185 -1.374189 5.596729
+v -1.506527 -1.438665 5.864041
+v -1.506543 -1.444154 5.597005
+v -1.409864 -1.490763 5.864248
+v -1.409876 -1.496257 5.597212
+v -1.304893 -1.523005 5.864375
+v -1.304900 -1.528503 5.597339
+v -1.195649 -1.534150 5.864418
+v -1.195649 -1.539650 5.597383
+v -1.086329 -1.523771 5.864378
+v -1.086321 -1.529268 5.597343
+v -0.981136 -1.492261 5.864253
+v -0.981126 -1.497756 5.597218
+v -0.884108 -1.440837 5.864049
+v -0.884094 -1.446325 5.597014
+v -0.798981 -1.371471 5.863774
+v -0.798965 -1.376956 5.596738
+v -0.729021 -1.286831 5.863439
+v -0.729004 -1.292308 5.596403
+v -0.676914 -1.190168 5.863057
+v -0.676902 -1.195640 5.596019
+v -0.644996 -1.086950 5.862668
+v -0.644650 -1.090617 5.595614
+v -0.633345 -0.980756 5.864254
+v -0.633519 -0.981388 5.595167
+v -0.619185 -0.862105 5.864238
+v -0.619185 -0.867288 5.229348
+v -0.652111 -0.752182 6.389203
+v -0.652111 -0.757365 5.228831
+v -0.705852 -0.650795 6.388727
+v -0.705852 -0.655976 5.228355
+v -0.778338 -0.561836 6.388309
+v -0.778337 -0.567019 5.227938
+v -0.866787 -0.488731 6.387966
+v -0.866788 -0.493915 5.227594
+v -0.967798 -0.434285 6.387711
+v -0.967798 -0.439468 5.227339
+v -1.077490 -0.400592 6.387552
+v -1.077490 -0.405775 5.227180
+v -1.191650 -0.388948 6.387497
+v -1.191649 -0.394128 5.227125
+v -1.305888 -0.399794 6.387548
+v -1.305886 -0.404978 5.227177
+v -1.415811 -0.432720 6.387703
+v -1.415811 -0.437904 5.227331
+v -1.517202 -0.486461 6.387955
+v -1.517201 -0.491643 5.227584
+v -1.606158 -0.558947 6.388296
+v -1.606158 -0.564129 5.227924
+v -1.679263 -0.647395 6.388711
+v -1.679264 -0.652577 5.228340
+v -1.733711 -0.748405 6.389186
+v -1.733710 -0.753588 5.228814
+v -1.767405 -0.858098 5.864219
+v -1.767404 -0.863280 5.229330
+v -1.779050 -0.972254 5.864219
+v -1.779049 -0.977436 5.229865
+v -1.768201 -1.086489 5.862726
+v -1.768202 -1.091671 5.230402
+v -1.735275 -1.196415 5.863242
+v -1.735276 -1.201597 5.230918
+v -1.681534 -1.297802 5.863719
+v -1.681535 -1.302985 5.231395
+v -1.609048 -1.386759 5.864136
+v -1.609047 -1.391941 5.231813
+v -1.520598 -1.459865 5.864480
+v -1.520599 -1.465047 5.232156
+v -1.419586 -1.514309 5.864735
+v -1.419587 -1.519492 5.232411
+v -1.309895 -1.548002 5.864894
+v -1.309894 -1.553185 5.232570
+v -1.195736 -1.559648 5.864949
+v -1.195737 -1.564832 5.232625
+v -1.081500 -1.548801 5.864897
+v -1.081499 -1.553983 5.232574
+v -0.971574 -1.515873 5.864743
+v -0.971575 -1.521056 5.232419
+v -0.870184 -1.462135 5.864491
+v -0.870184 -1.467316 5.232167
+v -0.781227 -1.389648 5.864150
+v -0.781228 -1.394831 5.231826
+v -0.708122 -1.301200 5.863734
+v -0.708122 -1.306382 5.231411
+v -0.653676 -1.200189 5.863259
+v -0.653675 -1.205371 5.230936
+v -0.619983 -1.090498 5.862744
+v -0.619982 -1.095680 5.230421
+v -0.608335 -0.976341 5.864237
+v -0.608336 -0.981523 5.229884
+v -0.951578 -3.358915 5.229884
+v -0.951578 -3.356772 5.864237
+v -0.956395 -3.406133 5.230421
+v -0.956396 -3.403990 5.862744
+v -0.970331 -3.451504 5.230936
+v -0.970332 -3.449360 5.863259
+v -0.992852 -3.493283 5.231411
+v -0.992852 -3.491140 5.863734
+v -1.023090 -3.529868 5.231826
+v -1.023089 -3.527724 5.864150
+v -1.059884 -3.559849 5.232167
+v -1.059884 -3.557706 5.864491
+v -1.101821 -3.582077 5.232419
+v -1.101820 -3.579932 5.864743
+v -1.147287 -3.595695 5.232574
+v -1.147288 -3.593553 5.864897
+v -1.194538 -3.600183 5.232625
+v -1.194538 -3.598039 5.864949
+v -1.241756 -3.595365 5.232570
+v -1.241756 -3.593222 5.864894
+v -1.287127 -3.581429 5.232411
+v -1.287126 -3.579286 5.864735
+v -1.328907 -3.558910 5.232156
+v -1.328907 -3.556767 5.864480
+v -1.365491 -3.528672 5.231813
+v -1.365491 -3.526528 5.864136
+v -1.395473 -3.491878 5.231395
+v -1.395473 -3.489735 5.863719
+v -1.417701 -3.449943 5.230918
+v -1.417701 -3.447799 5.863242
+v -1.431320 -3.404475 5.230402
+v -1.431320 -3.402331 5.862726
+v -1.435807 -3.357225 5.229865
+v -1.435807 -3.355082 5.864219
+v -1.430990 -3.310008 5.229330
+v -1.430991 -3.307865 5.864219
+v -1.417054 -3.264637 5.228814
+v -1.417054 -3.262494 6.389186
+v -1.394534 -3.222857 5.228340
+v -1.394534 -3.220714 6.388711
+v -1.364296 -3.186274 5.227924
+v -1.364296 -3.184130 6.388296
+v -1.327502 -3.156292 5.227584
+v -1.327502 -3.154149 6.387955
+v -1.285565 -3.134065 5.227331
+v -1.285565 -3.131921 6.387703
+v -1.240098 -3.120446 5.227177
+v -1.240099 -3.118302 6.387548
+v -1.192847 -3.115959 5.227125
+v -1.192848 -3.113816 6.387497
+v -1.145629 -3.120776 5.227180
+v -1.145629 -3.118632 6.387552
+v -1.100258 -3.134712 5.227339
+v -1.100258 -3.132568 6.387711
+v -1.058479 -3.157232 5.227594
+v -1.058478 -3.155088 6.387966
+v -1.021894 -3.187469 5.227938
+v -1.021894 -3.185325 6.388309
+v -0.991913 -3.224263 5.228355
+v -0.991913 -3.222120 6.388727
+v -0.969685 -3.266200 5.228831
+v -0.969685 -3.264056 6.389203
+v -0.956066 -3.311666 5.229348
+v -0.956066 -3.309522 5.864238
+v -0.961995 -3.358860 5.595167
+v -0.961923 -3.358598 5.864254
+v -0.966598 -3.404039 5.595614
+v -0.966742 -3.402522 5.862668
+v -0.979939 -3.447478 5.596019
+v -0.979944 -3.445215 5.863057
+v -1.001489 -3.487463 5.596403
+v -1.001496 -3.485197 5.863439
+v -1.030426 -3.522474 5.596738
+v -1.030433 -3.520205 5.863774
+v -1.065637 -3.551166 5.597014
+v -1.065643 -3.548896 5.864049
+v -1.105771 -3.572439 5.597218
+v -1.105775 -3.570166 5.864253
+v -1.149282 -3.585473 5.597343
+v -1.149285 -3.583200 5.864378
+v -1.194502 -3.589768 5.597383
+v -1.194502 -3.587492 5.864418
+v -1.239690 -3.585157 5.597339
+v -1.239687 -3.582883 5.864375
+v -1.283110 -3.571819 5.597212
+v -1.283105 -3.569547 5.864248
+v -1.323093 -3.550268 5.597005
+v -1.323087 -3.547998 5.864041
+v -1.358103 -3.521330 5.596729
+v -1.358097 -3.519062 5.863764
+v -1.386796 -3.486117 5.596392
+v -1.386789 -3.483851 5.863426
+v -1.408066 -3.445985 5.596006
+v -1.408062 -3.443721 5.863041
+v -1.421102 -3.402453 5.595598
+v -1.420964 -3.400939 5.862653
+v -1.425391 -3.357242 5.595152
+v -1.425477 -3.356980 5.864237
+v -1.420781 -3.312076 5.594666
+v -1.420781 -3.309800 5.864267
+v -1.407444 -3.268657 5.594199
+v -1.407445 -3.266382 6.389282
+v -1.385894 -3.228675 5.593767
+v -1.385893 -3.226399 6.388850
+v -1.356957 -3.193665 5.593389
+v -1.356956 -3.191389 6.388473
+v -1.321746 -3.164974 5.593081
+v -1.321745 -3.162699 6.388164
+v -1.281613 -3.143702 5.592852
+v -1.281613 -3.141427 6.387936
+v -1.238102 -3.130670 5.592710
+v -1.238102 -3.128394 6.387794
+v -1.192883 -3.126374 5.592665
+v -1.192884 -3.124101 6.387748
+v -1.147697 -3.130984 5.592714
+v -1.147697 -3.128710 6.387797
+v -1.104278 -3.144321 5.592857
+v -1.104278 -3.142046 6.387940
+v -1.064296 -3.165872 5.593091
+v -1.064296 -3.163597 6.388174
+v -1.029285 -3.194809 5.593402
+v -1.029285 -3.192534 6.388486
+v -1.000594 -3.230020 5.593781
+v -1.000594 -3.227745 6.388865
+v -0.979322 -3.270153 5.594215
+v -0.979322 -3.267877 6.389298
+v -0.966289 -3.313662 5.594683
+v -0.966289 -3.311387 5.864285
+v 0.356099 0.705847 5.864285
+v 0.356098 0.700347 5.594683
+v 0.324589 0.811042 6.389298
+v 0.324588 0.805538 5.594215
+v 0.273160 0.908067 6.388865
+v 0.273160 0.902567 5.593781
+v 0.203793 0.993196 6.388486
+v 0.203794 0.987696 5.593402
+v 0.119149 1.063156 6.388174
+v 0.119148 1.057656 5.593091
+v 0.022483 1.115260 6.387940
+v 0.022485 1.109761 5.592857
+v -0.082489 1.147504 6.387797
+v -0.082489 1.142005 5.592714
+v -0.191737 1.158646 6.387748
+v -0.191736 1.153148 5.592665
+v -0.301062 1.148266 6.387794
+v -0.301060 1.142764 5.592710
+v -0.406255 1.116758 6.387936
+v -0.406256 1.111256 5.592852
+v -0.503284 1.065329 6.388164
+v -0.503284 1.059828 5.593081
+v -0.588412 0.995964 6.388473
+v -0.588414 0.990462 5.593389
+v -0.658372 0.911321 6.388850
+v -0.658375 0.905819 5.593767
+v -0.710479 0.814654 6.389282
+v -0.710478 0.809153 5.594199
+v -0.742722 0.709684 5.864267
+v -0.742722 0.704181 5.594666
+v -0.754074 0.595617 5.864237
+v -0.753866 0.594983 5.595152
+v -0.743164 0.489339 5.862653
+v -0.743496 0.485677 5.595598
+v -0.711969 0.385904 5.863041
+v -0.711981 0.380432 5.596006
+v -0.660540 0.288881 5.863426
+v -0.660555 0.283404 5.596392
+v -0.591172 0.203754 5.863764
+v -0.591185 0.198271 5.596729
+v -0.506527 0.133796 5.864041
+v -0.506543 0.128306 5.597005
+v -0.409864 0.081697 5.864248
+v -0.409876 0.076203 5.597212
+v -0.304893 0.049455 5.864375
+v -0.304900 0.043957 5.597339
+v -0.195649 0.038311 5.864418
+v -0.195649 0.032810 5.597383
+v -0.086328 0.048690 5.864378
+v -0.086321 0.043193 5.597343
+v 0.018864 0.080200 5.864253
+v 0.018874 0.074705 5.597218
+v 0.115892 0.131623 5.864049
+v 0.115906 0.126135 5.597014
+v 0.201019 0.200989 5.863774
+v 0.201035 0.195505 5.596738
+v 0.270979 0.285629 5.863439
+v 0.270996 0.280152 5.596403
+v 0.323086 0.382292 5.863057
+v 0.323098 0.376821 5.596019
+v 0.355004 0.485511 5.862668
+v 0.355350 0.481844 5.595614
+v 0.366655 0.591704 5.864254
+v 0.366481 0.591073 5.595167
+v 0.380815 0.710356 5.864238
+v 0.380815 0.705173 5.229348
+v 0.347889 0.820279 6.389203
+v 0.347889 0.815095 5.228831
+v 0.294148 0.921665 6.388727
+v 0.294148 0.916485 5.228355
+v 0.221662 1.010625 6.388309
+v 0.221663 1.005441 5.227938
+v 0.133213 1.083729 6.387966
+v 0.133212 1.078545 5.227594
+v 0.032202 1.138175 6.387711
+v 0.032202 1.132992 5.227339
+v -0.077490 1.171868 6.387552
+v -0.077490 1.166685 5.227180
+v -0.191650 1.183513 6.387497
+v -0.191649 1.178332 5.227125
+v -0.305888 1.172667 6.387548
+v -0.305886 1.167483 5.227177
+v -0.415811 1.139740 6.387703
+v -0.415811 1.134556 5.227331
+v -0.517202 1.085999 6.387955
+v -0.517201 1.080817 5.227584
+v -0.606158 1.013514 6.388296
+v -0.606158 1.008332 5.227924
+v -0.679263 0.925066 6.388711
+v -0.679264 0.919884 5.228340
+v -0.733711 0.824056 6.389186
+v -0.733710 0.818873 5.228814
+v -0.767404 0.714363 5.864219
+v -0.767404 0.709181 5.229330
+v -0.779050 0.600207 5.864219
+v -0.779049 0.595024 5.229865
+v -0.768201 0.485972 5.862726
+v -0.768202 0.480789 5.230402
+v -0.735275 0.376046 5.863242
+v -0.735276 0.370864 5.230918
+v -0.681534 0.274658 5.863719
+v -0.681535 0.269476 5.231395
+v -0.609048 0.185702 5.864136
+v -0.609047 0.180519 5.231813
+v -0.520598 0.112596 5.864480
+v -0.520599 0.107414 5.232156
+v -0.419586 0.058152 5.864735
+v -0.419587 0.052969 5.232411
+v -0.309895 0.024458 5.864894
+v -0.309894 0.019276 5.232570
+v -0.195736 0.012813 5.864949
+v -0.195737 0.007628 5.232625
+v -0.081500 0.023660 5.864897
+v -0.081499 0.018478 5.232574
+v 0.028426 0.056588 5.864743
+v 0.028425 0.051405 5.232419
+v 0.129816 0.110325 5.864491
+v 0.129816 0.105145 5.232167
+v 0.218773 0.182812 5.864150
+v 0.218772 0.177630 5.231826
+v 0.291878 0.271261 5.863734
+v 0.291878 0.266078 5.231411
+v 0.346324 0.372271 5.863259
+v 0.346325 0.367089 5.230936
+v 0.380017 0.481962 5.862744
+v 0.380018 0.476780 5.230421
+v 0.391665 0.596120 5.864237
+v 0.391664 0.590937 5.229884
+v 0.048422 -2.358916 5.229884
+v 0.048422 -2.356772 5.864237
+v 0.043605 -2.406133 5.230421
+v 0.043604 -2.403990 5.862744
+v 0.029669 -2.451504 5.230936
+v 0.029668 -2.449360 5.863259
+v 0.007148 -2.493284 5.231411
+v 0.007148 -2.491140 5.863734
+v -0.023090 -2.529867 5.231826
+v -0.023089 -2.527724 5.864150
+v -0.059884 -2.559849 5.232167
+v -0.059884 -2.557706 5.864491
+v -0.101821 -2.582077 5.232419
+v -0.101820 -2.579932 5.864743
+v -0.147287 -2.595695 5.232574
+v -0.147288 -2.593553 5.864897
+v -0.194538 -2.600183 5.232625
+v -0.194538 -2.598039 5.864949
+v -0.241756 -2.595365 5.232570
+v -0.241756 -2.593222 5.864894
+v -0.287127 -2.581429 5.232411
+v -0.287126 -2.579286 5.864735
+v -0.328907 -2.558910 5.232156
+v -0.328907 -2.556767 5.864480
+v -0.365491 -2.528672 5.231813
+v -0.365491 -2.526529 5.864136
+v -0.395473 -2.491878 5.231395
+v -0.395473 -2.489735 5.863719
+v -0.417701 -2.449942 5.230918
+v -0.417701 -2.447799 5.863242
+v -0.431320 -2.404475 5.230402
+v -0.431320 -2.402331 5.862726
+v -0.435807 -2.357225 5.229865
+v -0.435807 -2.355082 5.864219
+v -0.430990 -2.310008 5.229330
+v -0.430991 -2.307864 5.864219
+v -0.417054 -2.264637 5.228814
+v -0.417054 -2.262494 6.389186
+v -0.394534 -2.222857 5.228340
+v -0.394534 -2.220714 6.388711
+v -0.364296 -2.186274 5.227924
+v -0.364296 -2.184130 6.388296
+v -0.327502 -2.156292 5.227584
+v -0.327502 -2.154149 6.387955
+v -0.285565 -2.134065 5.227331
+v -0.285565 -2.131920 6.387703
+v -0.240098 -2.120446 5.227177
+v -0.240099 -2.118302 6.387548
+v -0.192847 -2.115958 5.227125
+v -0.192848 -2.113816 6.387497
+v -0.145629 -2.120776 5.227180
+v -0.145629 -2.118632 6.387552
+v -0.100258 -2.134712 5.227339
+v -0.100258 -2.132568 6.387711
+v -0.058479 -2.157232 5.227594
+v -0.058478 -2.155088 6.387966
+v -0.021894 -2.187469 5.227938
+v -0.021894 -2.185325 6.388309
+v 0.008087 -2.224263 5.228355
+v 0.008087 -2.222121 6.388727
+v 0.030315 -2.266200 5.228831
+v 0.030315 -2.264056 6.389203
+v 0.043935 -2.311666 5.229348
+v 0.043935 -2.309522 5.864238
+v 0.038005 -2.358860 5.595167
+v 0.038077 -2.358598 5.864254
+v 0.033402 -2.404039 5.595614
+v 0.033258 -2.402522 5.862668
+v 0.020062 -2.447478 5.596019
+v 0.020056 -2.445215 5.863057
+v -0.001489 -2.487462 5.596403
+v -0.001496 -2.485197 5.863439
+v -0.030426 -2.522474 5.596738
+v -0.030433 -2.520205 5.863774
+v -0.065637 -2.551166 5.597014
+v -0.065643 -2.548896 5.864049
+v -0.105771 -2.572439 5.597218
+v -0.105775 -2.570166 5.864253
+v -0.149282 -2.585473 5.597343
+v -0.149285 -2.583199 5.864378
+v -0.194502 -2.589767 5.597383
+v -0.194502 -2.587492 5.864418
+v -0.239690 -2.585156 5.597339
+v -0.239687 -2.582883 5.864375
+v -0.283110 -2.571819 5.597212
+v -0.283105 -2.569547 5.864248
+v -0.323093 -2.550268 5.597005
+v -0.323087 -2.547998 5.864041
+v -0.358103 -2.521329 5.596729
+v -0.358097 -2.519062 5.863764
+v -0.386796 -2.486117 5.596392
+v -0.386789 -2.483852 5.863426
+v -0.408066 -2.445985 5.596006
+v -0.408062 -2.443721 5.863041
+v -0.421102 -2.402453 5.595598
+v -0.420964 -2.400939 5.862653
+v -0.425391 -2.357242 5.595152
+v -0.425477 -2.356980 5.864237
+v -0.420781 -2.312076 5.594666
+v -0.420781 -2.309800 5.864267
+v -0.407444 -2.268657 5.594199
+v -0.407445 -2.266382 6.389282
+v -0.385894 -2.228675 5.593767
+v -0.385893 -2.226399 6.388850
+v -0.356957 -2.193665 5.593389
+v -0.356956 -2.191389 6.388473
+v -0.321746 -2.164974 5.593081
+v -0.321745 -2.162699 6.388164
+v -0.281613 -2.143702 5.592852
+v -0.281613 -2.141427 6.387936
+v -0.238102 -2.130670 5.592710
+v -0.238102 -2.128394 6.387794
+v -0.192883 -2.126374 5.592665
+v -0.192884 -2.124101 6.387748
+v -0.147697 -2.130984 5.592714
+v -0.147697 -2.128710 6.387797
+v -0.104278 -2.144320 5.592857
+v -0.104278 -2.142046 6.387940
+v -0.064296 -2.165872 5.593091
+v -0.064296 -2.163597 6.388174
+v -0.029285 -2.194809 5.593402
+v -0.029285 -2.192534 6.388486
+v -0.000594 -2.230020 5.593781
+v -0.000594 -2.227745 6.388865
+v 0.020678 -2.270153 5.594215
+v 0.020678 -2.267877 6.389298
+v 0.033711 -2.313662 5.594683
+v 0.033711 -2.311387 5.864285
+vt 0.442670 0.517822
+vt 0.228883 0.517823
+vt 0.228889 0.488295
+vt 0.797953 0.896311
+vt 0.741726 0.913364
+vt 0.594127 0.834472
+vt 0.577066 0.778233
+vt 0.655948 0.630639
+vt 0.770682 0.619333
+vt 0.859798 0.692466
+vt 0.876858 0.748707
+vt 0.683240 0.907605
+vt 0.655960 0.896305
+vt 0.582826 0.719746
+vt 0.610530 0.667916
+vt 0.683222 0.619339
+vt 0.822512 0.647036
+vt 0.843392 0.667915
+vt 0.871097 0.807194
+vt 0.843391 0.859025
+vt 0.770685 0.907604
+vt 0.712199 0.913364
+vt 0.631409 0.879901
+vt 0.610530 0.859023
+vt 0.582827 0.807192
+vt 0.577066 0.748705
+vt 0.594126 0.692467
+vt 0.631409 0.647037
+vt 0.712194 0.613573
+vt 0.741722 0.613573
+vt 0.797962 0.630631
+vt 0.871098 0.719747
+vt 0.876858 0.778235
+vt 0.859797 0.834474
+vt 0.822501 0.879912
+vt 0.442971 0.724510
+vt 0.443060 0.754024
+vt 0.370472 0.723575
+vt 0.442967 0.311134
+vt 0.370467 0.312068
+vt 0.443056 0.281620
+vt 0.443160 0.252098
+vt 0.371348 0.252525
+vt 0.443163 0.783546
+vt 0.371352 0.783117
+vt 0.006929 0.253167
+vt 0.177644 0.253134
+vt 0.006828 0.284020
+vt 0.007055 0.808541
+vt 0.006941 0.777687
+vt 0.177771 0.808498
+vt 0.006827 0.746834
+vt 0.176852 0.746796
+vt 0.006727 0.314875
+vt 0.176753 0.314837
+vt 0.371492 0.871219
+vt 0.371495 0.841694
+vt 0.443305 0.842613
+vt 0.615392 0.436018
+vt 0.610260 0.440534
+vt 0.592437 0.415287
+vt 0.816609 0.103895
+vt 0.836176 0.125250
+vt 0.831035 0.128981
+vt 0.706468 0.066881
+vt 0.736753 0.067631
+vt 0.735929 0.074111
+vt 0.647824 0.083609
+vt 0.650942 0.089526
+vt 0.626230 0.105756
+vt 0.765981 0.074365
+vt 0.763884 0.080481
+vt 0.579817 0.387126
+vt 0.586362 0.385333
+vt 0.621946 0.100560
+vt 0.605204 0.126737
+vt 0.706979 0.073452
+vt 0.678244 0.078632
+vt 0.792953 0.086670
+vt 0.812355 0.108585
+vt 0.676401 0.072253
+vt 0.789700 0.092175
+vt 0.599888 0.122511
+vt 0.589057 0.151745
+vt 0.582798 0.148853
+vt 0.579868 0.178984
+vt 0.850517 0.149568
+vt 0.844628 0.151667
+vt 0.572791 0.357181
+vt 0.571207 0.326153
+vt 0.578089 0.326242
+vt 0.579568 0.357437
+vt 0.892437 0.349226
+vt 0.885423 0.348954
+vt 0.885704 0.319257
+vt 0.886223 0.285996
+vt 0.879631 0.287703
+vt 0.636826 0.456466
+vt 0.632640 0.461897
+vt 0.874138 0.409852
+vt 0.856681 0.436209
+vt 0.851292 0.431762
+vt 0.661830 0.472412
+vt 0.658752 0.478560
+vt 0.689480 0.483245
+vt 0.687628 0.489885
+vt 0.718747 0.488548
+vt 0.718195 0.495436
+vt 0.779839 0.488518
+vt 0.749318 0.494983
+vt 0.748545 0.488099
+vt 0.808610 0.476251
+vt 0.777761 0.481894
+vt 0.834553 0.458608
+vt 0.805300 0.470133
+vt 0.830124 0.453224
+vt 0.573209 0.178195
+vt 0.886228 0.380492
+vt 0.867982 0.406513
+vt 0.879533 0.378395
+vt 0.370709 0.754396
+vt 0.319322 0.130795
+vt 0.319224 0.161649
+vt 0.007220 0.160604
+vt 0.007387 0.901104
+vt 0.319390 0.899923
+vt 0.319502 0.930776
+vt 0.442778 0.399714
+vt 0.228990 0.399716
+vt 0.229046 0.370189
+vt 0.007499 0.931958
+vt 0.319616 0.961631
+vt 0.006361 0.469146
+vt 0.176387 0.469108
+vt 0.176362 0.499962
+vt 0.442678 0.488295
+vt 0.228910 0.458769
+vt 0.007727 0.006334
+vt 0.319730 0.007378
+vt 0.319626 0.038233
+vt 0.228890 0.547350
+vt 0.007613 0.962812
+vt 0.319730 0.992485
+vt 0.228946 0.606403
+vt 0.228912 0.576877
+vt 0.442700 0.576876
+vt 0.228992 0.635930
+vt 0.442734 0.606402
+vt 0.442833 0.370188
+vt 0.229109 0.340661
+vt 0.229113 0.694984
+vt 0.229049 0.665457
+vt 0.442837 0.665456
+vt 0.442901 0.694984
+vt 0.370705 0.281246
+vt 0.371443 0.812167
+vt 0.443254 0.813086
+vt 0.892437 0.317355
+vt 0.371421 0.900745
+vt 0.443302 0.872141
+vt 0.371269 0.930272
+vt 0.443231 0.901668
+vt 0.007276 0.870250
+vt 0.319278 0.869069
+vt 0.006405 0.438291
+vt 0.176430 0.438254
+vt 0.443090 0.104449
+vt 0.371280 0.105372
+vt 0.371043 0.075847
+vt 0.443237 0.133976
+vt 0.371427 0.134897
+vt 0.443305 0.163503
+vt 0.371495 0.164423
+vt 0.443305 0.193031
+vt 0.371495 0.193949
+vt 0.443252 0.222558
+vt 0.371442 0.223476
+vt 0.177399 0.283987
+vt 0.006543 0.376583
+vt 0.176568 0.376545
+vt 0.176492 0.407399
+vt 0.006467 0.407437
+vt 0.007165 0.839396
+vt 0.319168 0.838215
+vt 0.370694 0.989321
+vt 0.371029 0.959797
+vt 0.442838 0.960722
+vt 0.442780 0.635930
+vt 0.442698 0.458768
+vt 0.228944 0.429242
+vt 0.006337 0.500000
+vt 0.176359 0.530816
+vt 0.319524 0.069087
+vt 0.007520 0.068042
+vt 0.319423 0.099941
+vt 0.007419 0.098896
+vt 0.007319 0.129750
+vt 0.442897 0.340661
+vt 0.319126 0.192504
+vt 0.007122 0.191458
+vt 0.319029 0.223358
+vt 0.007025 0.222313
+vt 0.177512 0.777645
+vt 0.443079 0.931196
+vt 0.176656 0.345691
+vt 0.006631 0.345729
+vt 0.442853 0.074922
+vt 0.370712 0.046322
+vt 0.442732 0.429241
+vt 0.442678 0.547349
+vt 0.176377 0.561671
+vt 0.006351 0.561708
+vt 0.176415 0.592525
+vt 0.006390 0.592562
+vt 0.176474 0.623379
+vt 0.006448 0.623417
+vt 0.176549 0.654233
+vt 0.006524 0.654271
+vt 0.176640 0.685087
+vt 0.006614 0.685125
+vt 0.176742 0.715941
+vt 0.006716 0.715979
+vt 0.598317 0.411848
+vt 0.007623 0.037188
+vt 0.007728 0.993666
+vt 0.442504 0.990249
+vt 0.006334 0.530854
+vt 0.442522 0.045396
+vn 0.094500 -0.995500 0.006900
+vn -0.000000 0.004400 1.000000
+vn 0.000000 0.004500 1.000000
+vn 0.000000 0.004000 1.000000
+vn 0.000300 0.004100 1.000000
+vn -0.000200 0.004400 1.000000
+vn 0.000100 0.004500 1.000000
+vn -0.000000 0.004600 1.000000
+vn -0.000100 0.004400 1.000000
+vn -0.000200 0.003800 1.000000
+vn 0.000100 0.004000 1.000000
+vn -0.000000 0.003900 1.000000
+vn -0.000000 0.004100 1.000000
+vn -0.000100 0.004000 1.000000
+vn -0.001200 0.004200 1.000000
+vn -0.995500 -0.094600 0.001900
+vn 0.994800 -0.101500 0.002100
+vn 0.995500 0.094400 -0.002500
+vn -0.994800 0.101400 -0.002700
+vn 0.995500 0.094500 -0.000800
+vn -0.994800 0.101500 -0.000800
+vn -0.995500 -0.094500 0.000800
+vn 0.994800 -0.101500 0.000800
+vn -0.770700 0.637000 -0.013100
+vn -0.008000 0.009000 0.999900
+vn -0.002400 0.006600 1.000000
+vn -0.000500 0.010000 1.000000
+vn 0.002400 0.009000 1.000000
+vn -0.001400 0.009500 1.000000
+vn -0.002300 0.005400 1.000000
+vn 0.003000 0.008100 1.000000
+vn 0.000600 0.010100 0.999900
+vn -0.002400 0.007600 1.000000
+vn 0.001500 0.009400 1.000000
+vn -0.002100 0.008700 1.000000
+vn 0.003100 0.007000 1.000000
+vn 0.002800 0.005900 1.000000
+vn -0.001800 0.005700 1.000000
+vn 0.001800 0.000200 1.000000
+vn 0.003000 -0.013400 0.999900
+vn 0.004900 -0.014400 0.999900
+vn -0.182400 -0.960000 0.212300
+vn -0.008900 0.012000 0.999900
+vn 0.008900 0.011900 0.999900
+vn -0.008500 0.015200 0.999800
+vn -0.007000 0.017900 0.999800
+vn -0.004600 0.019900 0.999800
+vn 0.001600 0.020800 0.999800
+vn 0.004600 0.019800 0.999800
+vn 0.007100 0.017800 0.999800
+vn 0.008600 0.015000 0.999900
+vn 0.361500 -0.909800 0.204000
+vn 0.007900 0.008900 0.999900
+vn 0.006200 0.005600 1.000000
+vn -0.000100 0.004300 1.000000
+vn -0.995800 -0.092100 0.000900
+vn 0.637100 0.770800 -0.003400
+vn -0.631700 0.775200 -0.003500
+vn 0.770800 -0.637100 0.004400
+vn -0.468300 0.883500 -0.003900
+vn 0.287000 -0.957900 0.007900
+vn 0.286900 -0.957900 0.006600
+vn -0.094500 0.995500 -0.004400
+vn -0.101500 -0.994800 0.006900
+vn -0.286900 0.957900 -0.004300
+vn -0.474500 -0.880200 0.006100
+vn -0.637100 -0.770800 0.005300
+vn 0.880300 -0.474500 0.003300
+vn -0.883600 -0.468300 0.003200
+vn -0.955100 -0.296300 0.002100
+vn 0.995100 -0.099000 0.001000
+vn 0.994800 0.102100 0.000500
+vn -0.880200 0.474500 -0.009800
+vn -0.001500 0.020800 0.999800
+vn -0.955400 0.295400 -0.005300
+vn -0.000800 -0.000200 1.000000
+vn -0.631600 0.775100 -0.016000
+vn -0.468200 0.883400 -0.018200
+vn -0.770800 0.637100 -0.002800
+vn 0.468300 -0.883500 0.007200
+vn 0.293600 0.955700 -0.019700
+vn 0.474400 0.880100 -0.018100
+vn 0.637000 0.770700 -0.015900
+vn 0.775200 0.631600 -0.013000
+vn 0.883500 0.468300 -0.009600
+vn 0.957400 0.288700 -0.005200
+vn -0.994000 0.109100 0.000400
+vn 0.770800 -0.637100 0.005200
+vn 0.631700 -0.775200 0.006400
+vn -0.880300 0.474500 -0.002100
+vn -0.094500 0.995300 -0.020500
+vn -0.775200 -0.631700 0.004400
+vn 0.468300 -0.883500 0.006100
+vn 0.094500 -0.995500 0.008200
+vn 0.101500 0.994800 -0.004400
+vn 0.293600 0.955900 -0.004300
+vn 0.474500 0.880300 -0.003900
+vn 0.952900 -0.303200 0.002100
+vn 0.775200 0.631700 -0.002800
+vn 0.883600 0.468300 -0.002100
+vn 0.956200 0.292700 -0.001300
+vn -0.995500 -0.094600 0.000800
+vn -0.286900 0.957800 -0.019700
+vn 0.955900 -0.293600 0.002400
+vn 0.880300 -0.474500 0.003900
+vn -0.955900 0.293600 -0.002400
+vn 0.101500 0.994600 -0.020500
+vn 0.631700 -0.775200 0.005400
+vn -0.293600 -0.955900 0.006600
+vn -0.101500 -0.994800 0.008200
+vn -0.293600 -0.955900 0.007800
+vn -0.474500 -0.880200 0.007200
+vn -0.637100 -0.770800 0.006300
+vn -0.775200 -0.631700 0.005200
+vn -0.883600 -0.468300 0.003800
+vn -0.958000 -0.286900 0.002400
+vn 0.094600 -0.995500 0.006900
+vn -0.770800 0.637000 -0.013100
+vn -0.005700 0.007000 1.000000
+vn -0.003000 0.007000 1.000000
+vn -0.000500 0.010100 0.999900
+vn 0.002100 0.008600 1.000000
+vn -0.001600 0.009800 1.000000
+vn -0.006300 0.005700 1.000000
+vn 0.002500 0.007600 1.000000
+vn 0.000500 0.010000 1.000000
+vn -0.003000 0.008100 1.000000
+vn 0.001600 0.009700 1.000000
+vn -0.002500 0.009100 1.000000
+vn 0.002300 0.006600 1.000000
+vn 0.001800 0.005700 1.000000
+vn -0.002800 0.006000 1.000000
+vn 0.000700 -0.000200 1.000000
+vn -0.005100 -0.014400 0.999900
+vn -0.003200 -0.013400 0.999900
+vn -0.368000 -0.907200 0.204000
+vn -0.007300 0.010000 0.999900
+vn 0.007200 0.009900 0.999900
+vn -0.007600 0.013300 0.999900
+vn -0.006600 0.016400 0.999800
+vn -0.004500 0.018900 0.999800
+vn 0.001700 0.020400 0.999800
+vn 0.004600 0.018800 0.999800
+vn 0.006600 0.016300 0.999800
+vn 0.007600 0.013100 0.999900
+vn 0.175700 -0.961300 0.212300
+vn 0.005600 0.007000 1.000000
+vn 0.002200 0.005400 1.000000
+vn -0.468300 0.883600 -0.003900
+vn -0.957900 -0.286900 0.005900
+vn -0.880300 0.474400 -0.009800
+vn -0.001600 0.020400 0.999800
+vn -0.955900 0.293500 -0.006100
+vn -0.002000 0.000200 1.000000
+vn -0.468300 0.883400 -0.018200
+vn 0.637100 0.770600 -0.015900
+vn 0.883500 0.468200 -0.009700
+vn 0.958000 0.286800 -0.005900
+vn 0.955900 -0.293600 0.006000
+vn 0.957900 0.286900 -0.002300
+vn -0.286900 0.957700 -0.019700
+vn -0.954100 0.299500 -0.001300
+vn -0.000300 0.003800 1.000000
+vn -0.000100 0.003900 1.000000
+vn -0.000200 0.004000 1.000000
+vn 0.003100 -0.013400 0.999900
+vn 0.008800 0.011900 0.999900
+vn 0.008500 0.015000 0.999900
+vn 0.002400 0.007600 1.000000
+vn 0.006700 0.016300 0.999800
+vn 0.005700 0.007000 1.000000
+vn -0.001900 0.000200 1.000000
+vn 0.000200 0.010900 0.999900
+vn -0.019300 0.021700 0.999600
+vn -0.000000 0.009600 1.000000
+vn -0.000600 0.009200 1.000000
+vn 0.880300 -0.474500 0.001600
+vn 0.000100 0.010700 0.999900
+vn -0.003000 0.010200 0.999900
+vn -0.955400 0.295400 -0.002200
+vn -0.995500 -0.094600 0.000300
+vn -0.880300 0.474500 -0.000900
+vn 0.631700 -0.775200 0.002600
+vn 0.000300 0.009700 1.000000
+vn 0.952900 -0.303200 0.000900
+vn 0.000100 0.009600 1.000000
+vn 0.293600 0.955900 -0.001800
+vn 0.637100 0.770800 -0.001400
+vn 0.020700 0.036300 0.999100
+vn -0.094500 0.995500 -0.001800
+vn -0.775200 -0.631700 0.001800
+vn 0.994800 -0.101500 0.000900
+vn -0.101500 -0.994800 0.002800
+vn -0.000000 0.010800 0.999900
+vn 0.000100 0.009700 1.000000
+vn -0.000100 0.009900 1.000000
+vn -0.994800 0.101400 -0.001100
+vn 0.883600 0.468300 -0.000900
+vn -0.995800 -0.092100 0.000400
+vn 0.005900 0.021700 0.999700
+vn -0.005100 0.021000 0.999800
+vn -0.011000 0.048000 0.998800
+vn 0.000000 0.010900 0.999900
+vn 0.004000 0.050300 0.998700
+vn 0.994800 0.102100 0.000200
+vn -0.637100 -0.770800 0.002200
+vn 0.094500 -0.995500 0.002800
+vn -0.005500 0.013100 0.999900
+vn 0.094500 -0.995500 0.003400
+vn 0.011600 -0.034800 0.999300
+vn -0.001200 0.024100 0.999700
+vn 0.775200 0.631700 -0.005400
+vn 0.995500 0.094500 -0.000300
+vn -0.775200 -0.631700 0.002100
+vn 0.995500 0.094400 -0.001000
+vn -0.468300 0.883500 -0.007500
+vn -0.005600 0.016000 0.999900
+vn 0.007300 0.019500 0.999800
+vn 0.468300 -0.883500 0.002500
+vn 0.631700 -0.775200 0.002200
+vn -0.003700 0.050300 0.998700
+vn 0.021300 0.028700 0.999400
+vn -0.293600 -0.955900 0.002700
+vn -0.101500 -0.994800 0.003400
+vn -0.004400 0.013800 0.999900
+vn -0.287000 0.957900 -0.008200
+vn 0.101500 0.994800 -0.008500
+vn -0.000100 0.010800 0.999900
+vn -0.880300 0.474500 -0.004000
+vn 0.883600 0.468300 -0.004000
+vn -0.003500 0.023000 0.999700
+vn 0.293600 0.955900 -0.008100
+vn 0.017100 0.042900 0.998900
+vn 0.994800 -0.101500 0.000300
+vn -0.286900 0.957900 -0.001800
+vn -0.995500 -0.094500 0.000300
+vn -0.883600 -0.468300 0.001600
+vn -0.020600 0.036600 0.999100
+vn -0.994000 0.109100 0.000200
+vn -0.000300 0.009700 1.000000
+vn -0.000000 0.011200 0.999900
+vn 0.004400 0.000400 1.000000
+vn 0.770800 -0.637100 0.001800
+vn 0.995100 -0.099000 0.000400
+vn -0.474500 -0.880300 0.002500
+vn -0.637100 -0.770800 0.002600
+vn 0.770800 -0.637100 0.002200
+vn -0.883600 -0.468300 0.001300
+vn 0.101500 0.994800 -0.001800
+vn -0.000100 0.009500 1.000000
+vn 0.000800 0.009900 1.000000
+vn -0.631700 0.775200 -0.006600
+vn 0.956200 0.292700 -0.000500
+vn -0.016900 0.043300 0.998900
+vn 0.637100 0.770800 -0.006600
+vn -0.955900 0.293600 -0.001000
+vn -0.000000 0.010700 0.999900
+vn 0.286900 -0.957900 0.002700
+vn 0.011200 0.047900 0.998800
+vn -0.000300 0.010700 0.999900
+vn 0.367900 -0.925900 0.085900
+vn -0.958000 -0.286900 0.001000
+vn -0.021400 0.029100 0.999300
+vn 0.007400 -0.032300 0.999400
+vn -0.631700 0.775200 -0.001400
+vn -0.293600 -0.955900 0.003200
+vn 0.775200 0.631700 -0.001200
+vn 0.955900 -0.293600 0.001000
+vn -0.994800 0.101500 -0.000300
+vn -0.005800 0.018500 0.999800
+vn 0.006900 0.014300 0.999900
+vn -0.770800 0.637100 -0.001200
+vn 0.474500 0.880300 -0.001600
+vn -0.474500 -0.880300 0.003000
+vn 0.015100 0.013600 0.999800
+vn 0.880300 -0.474500 0.001400
+vn -0.094500 0.995500 -0.008500
+vn -0.185900 -0.978500 0.089500
+vn 0.007600 0.016800 0.999800
+vn 0.003500 0.022800 0.999700
+vn 0.474500 0.880200 -0.007500
+vn -0.770800 0.637100 -0.005400
+vn 0.019100 0.021500 0.999600
+vn -0.000300 0.010400 0.999900
+vn 0.468300 -0.883600 0.003000
+vn 0.957400 0.288700 -0.002100
+vn -0.001800 -0.000500 1.000000
+vn -0.955100 -0.296300 0.000800
+vn 0.287000 -0.957900 0.003200
+vn -0.468300 0.883600 -0.001600
+vn 0.001400 0.024400 0.999700
+vn 0.094600 -0.995500 0.002800
+vn -0.770800 0.637000 -0.005400
+vn -0.013700 0.017000 0.999800
+vn -0.007300 0.016900 0.999800
+vn -0.001300 0.024400 0.999700
+vn 0.005100 0.020900 0.999800
+vn -0.003800 0.023600 0.999700
+vn -0.015100 0.013800 0.999800
+vn 0.005900 0.018500 0.999800
+vn 0.001300 0.024100 0.999700
+vn -0.007200 0.019600 0.999800
+vn 0.004000 0.023500 0.999700
+vn -0.006000 0.022000 0.999700
+vn 0.005500 0.015900 0.999900
+vn 0.004400 0.013700 0.999900
+vn -0.006700 0.014400 0.999900
+vn 0.001800 -0.000500 1.000000
+vn -0.012400 -0.034700 0.999300
+vn -0.007700 -0.032300 0.999400
+vn -0.374500 -0.923300 0.085900
+vn -0.017600 0.024100 0.999600
+vn 0.017500 0.023800 0.999600
+vn -0.018300 0.032100 0.999300
+vn -0.016000 0.039700 0.999100
+vn -0.010800 0.045700 0.998900
+vn 0.004100 0.049300 0.998800
+vn 0.011000 0.045400 0.998900
+vn 0.016000 0.039400 0.999100
+vn 0.018400 0.031700 0.999300
+vn 0.179100 -0.979700 0.089500
+vn 0.013700 0.016800 0.999800
+vn 0.005400 0.012900 0.999900
+vn -0.957900 -0.286900 0.002400
+vn -0.880300 0.474400 -0.004000
+vn -0.003800 0.049400 0.998800
+vn -0.955900 0.293500 -0.002500
+vn -0.004700 0.000500 1.000000
+vn 0.637100 0.770700 -0.006600
+vn 0.958000 0.286800 -0.002400
+vn 0.468300 -0.883600 0.002500
+vn 0.955900 -0.293600 0.002500
+vn 0.957900 0.286900 -0.001000
+vn -0.954100 0.299500 -0.000600
+vn 0.094600 -0.995500 0.002900
+vn -0.010800 0.045800 0.998900
+vn -0.012300 -0.034700 0.999300
+vn -0.004500 0.013800 0.999900
+vn -0.287000 0.957900 -0.001800
+vn 0.007300 -0.032300 0.999400
+vn 0.293700 0.955900 -0.008100
+vn 0.000300 0.010900 0.999900
+vn -0.006600 0.014400 0.999900
+vn -0.000000 0.009500 1.000000
+vn -0.019200 0.021700 0.999600
+vn 0.016100 0.039400 0.999100
+vn 0.000200 0.009700 1.000000
+vn 0.001300 0.024000 0.999700
+vn -0.015900 0.039700 0.999100
+vn -0.004600 0.000500 1.000000
+vn -0.000200 0.009700 1.000000
+vn 0.003900 0.023500 0.999700
+vn 0.000700 0.009900 1.000000
+vn 0.006700 0.014300 0.999900
+vn 0.019200 0.021500 0.999600
+vn 0.021400 0.028700 0.999400
+vn 0.004500 0.013700 0.999900
+vn 0.179100 -0.979800 0.089500
+vn 0.020600 0.036300 0.999100
+vn 0.000000 0.009700 1.000000
+vn -0.001300 0.004200 1.000000
+vn -0.001900 0.005700 1.000000
+vn 0.004800 -0.014400 0.999900
+vn 0.000500 0.009900 1.000000
+vn 0.001900 0.005700 1.000000
+vn -0.002700 0.006000 1.000000
+vn -0.000100 0.010700 0.999900
+vn 0.000100 0.010900 0.999900
+vn 0.005900 0.021800 0.999700
+vn -0.000500 0.009200 1.000000
+vn 0.003900 0.050300 0.998700
+vn -0.000100 0.009600 1.000000
+vn -0.000100 0.009700 1.000000
+vn 0.015000 0.013600 0.999800
+g HSLampen_Cube.000_Yellow
+usemtl Yellow
+s off
+f 16/1/1 15/2/1 17/3/1
+f 64/4/2 4/5/2 16/6/2
+f 16/6/3 20/7/3 32/8/3
+f 64/4/4 40/9/4 48/10/4
+f 48/10/4 52/11/4 64/4/4
+f 16/6/3 4/5/3 8/12/3
+f 8/12/3 10/13/3 16/6/3
+f 32/8/3 20/7/3 24/14/3
+f 24/14/2 28/15/2 32/8/2
+f 32/8/5 34/16/5 40/9/5
+f 40/9/4 44/17/4 46/18/4
+f 64/4/4 52/11/4 56/19/4
+f 56/19/4 60/20/4 64/4/4
+f 64/4/6 2/21/6 4/5/6
+f 4/5/7 6/22/7 8/12/7
+f 16/6/3 10/13/3 12/23/3
+f 12/23/3 14/24/3 16/6/3
+f 16/6/8 18/25/8 20/7/8
+f 20/7/2 22/26/2 24/14/2
+f 24/14/9 26/27/9 28/15/9
+f 28/15/7 30/28/7 32/8/7
+f 40/9/10 34/16/10 36/29/10
+f 36/29/11 38/30/11 40/9/11
+f 40/9/12 42/31/12 44/17/12
+f 40/9/4 46/18/4 48/10/4
+f 48/10/13 50/32/13 52/11/13
+f 52/11/4 54/33/4 56/19/4
+f 56/19/14 58/34/14 60/20/14
+f 60/20/15 62/35/15 64/4/15
+f 2/36/16 64/37/16 1/38/16
+f 30/39/17 29/40/17 32/41/17
+f 34/42/18 32/41/18 33/43/18
+f 62/44/19 61/45/19 64/37/19
+f 66/46/20 65/47/20 128/48/20
+f 94/49/21 96/50/21 93/51/21
+f 98/52/22 97/53/22 96/50/22
+f 126/54/23 128/48/23 125/55/23
+f 55/56/24 57/57/24 58/58/24
+f 57/59/25 121/60/25 123/61/25
+f 87/62/26 89/63/26 25/64/26
+f 79/65/27 81/66/27 17/67/27
+f 75/68/28 11/69/28 9/70/28
+f 81/66/29 83/71/29 19/72/29
+f 123/61/30 125/73/30 61/74/30
+f 73/75/31 9/70/31 7/76/31
+f 79/65/32 15/77/32 13/78/32
+f 85/79/33 87/62/33 23/80/33
+f 11/69/34 75/68/34 77/81/34
+f 83/71/35 85/79/35 21/82/35
+f 71/83/36 7/76/36 5/84/36
+f 69/85/37 5/84/37 3/86/37
+f 89/63/38 91/87/38 27/88/38
+f 127/89/39 65/90/39 1/91/39
+f 125/73/40 127/89/40 63/92/40
+f 97/93/41 33/94/41 31/95/41
+f 91/87/42 93/96/42 29/97/42
+f 55/98/43 119/99/43 121/60/43
+f 101/100/44 103/101/44 39/102/44
+f 53/103/45 117/104/45 119/99/45
+f 51/105/46 115/106/46 117/104/46
+f 49/107/47 113/108/47 115/106/47
+f 109/109/48 111/110/48 47/111/48
+f 107/112/49 109/109/49 45/113/49
+f 105/114/50 107/112/50 43/115/50
+f 103/101/51 105/114/51 41/116/51
+f 67/117/52 3/86/52 1/91/52
+f 99/118/53 101/100/53 37/119/53
+f 99/118/54 35/120/54 33/94/54
+f 40/9/55 64/4/55 16/6/55
+f 64/37/56 63/121/56 1/38/56
+f 73/122/57 71/123/57 72/124/57
+f 88/125/58 87/126/58 85/127/58
+f 24/128/59 23/129/59 25/130/59
+f 86/131/60 85/127/60 83/132/60
+f 116/133/61 115/134/61 113/135/61
+f 18/136/62 17/3/62 19/137/62
+f 82/138/63 81/139/63 79/140/63
+f 13/141/64 15/2/64 16/1/64
+f 84/142/65 83/132/65 81/143/65
+f 9/144/66 11/145/66 12/146/66
+f 7/147/67 9/144/67 10/148/67
+f 26/149/68 25/130/68 27/150/68
+f 3/151/69 5/152/69 6/153/69
+f 1/38/70 3/151/70 4/154/70
+f 29/40/71 31/155/71 32/41/71
+f 32/41/72 31/155/72 33/43/72
+f 57/57/73 59/156/73 60/157/73
+f 47/111/74 111/110/74 113/108/74
+f 59/156/75 61/45/75 62/44/75
+f 95/158/76 31/95/76 29/97/76
+f 53/159/77 55/56/77 56/160/77
+f 51/161/78 53/159/78 54/162/78
+f 90/163/79 89/164/79 87/126/79
+f 118/165/80 117/166/80 115/134/80
+f 44/167/81 43/168/81 45/169/81
+f 42/170/82 41/171/82 43/168/82
+f 40/172/83 39/173/83 41/171/83
+f 38/174/84 37/175/84 39/173/84
+f 36/176/85 35/177/85 37/175/85
+f 34/42/86 33/43/86 35/177/86
+f 61/45/87 63/121/87 64/37/87
+f 65/47/20 127/178/20 128/48/20
+f 122/179/88 121/180/88 119/181/88
+f 120/182/89 119/181/89 117/166/89
+f 92/183/90 91/184/90 89/164/90
+f 47/185/91 49/186/91 50/187/91
+f 5/152/92 7/147/92 8/188/92
+f 20/189/93 19/137/93 21/190/93
+f 114/191/94 113/135/94 111/192/94
+f 79/140/95 77/193/95 78/194/95
+f 77/193/96 75/195/96 76/196/96
+f 75/195/97 73/122/97 74/197/97
+f 28/198/98 27/150/98 29/40/98
+f 71/123/99 69/199/99 70/200/99
+f 69/199/100 67/201/100 68/202/100
+f 68/202/101 67/201/101 65/47/101
+f 96/50/21 95/203/21 93/51/21
+f 97/53/102 95/203/102 96/50/102
+f 49/186/103 51/161/103 52/204/103
+f 126/54/104 125/55/104 123/205/104
+f 124/206/105 123/205/105 121/180/105
+f 92/183/106 94/49/106 93/51/106
+f 46/207/107 45/169/107 47/208/107
+f 22/209/108 21/190/108 23/129/108
+f 11/145/109 13/141/109 14/210/109
+f 111/192/110 109/211/110 110/212/110
+f 109/211/111 107/213/111 108/214/111
+f 107/213/112 105/215/112 106/216/112
+f 105/215/113 103/217/113 104/218/113
+f 103/217/114 101/219/114 102/220/114
+f 101/219/115 99/221/115 100/222/115
+f 99/221/116 97/53/116 98/52/116
+f 128/48/23 127/178/23 125/55/23
+f 18/136/117 16/1/117 17/3/117
+f 56/160/118 55/56/118 58/58/118
+f 59/223/119 57/59/119 123/61/119
+f 23/80/120 87/62/120 25/64/120
+f 15/77/121 79/65/121 17/67/121
+f 73/75/122 75/68/122 9/70/122
+f 17/67/123 81/66/123 19/72/123
+f 59/223/124 123/61/124 61/74/124
+f 71/83/125 73/75/125 7/76/125
+f 77/81/126 79/65/126 13/78/126
+f 21/82/127 85/79/127 23/80/127
+f 13/78/128 11/69/128 77/81/128
+f 19/72/129 83/71/129 21/82/129
+f 69/85/130 71/83/130 5/84/130
+f 67/117/131 69/85/131 3/86/131
+f 25/64/132 89/63/132 27/88/132
+f 63/92/133 127/89/133 1/91/133
+f 61/74/134 125/73/134 63/92/134
+f 95/158/135 97/93/135 31/95/135
+f 27/88/136 91/87/136 29/97/136
+f 57/59/137 55/98/137 121/60/137
+f 37/119/138 101/100/138 39/102/138
+f 55/98/139 53/103/139 119/99/139
+f 53/103/140 51/105/140 117/104/140
+f 51/105/141 49/107/141 115/106/141
+f 45/113/142 109/109/142 47/111/142
+f 43/115/143 107/112/143 45/113/143
+f 41/116/144 105/114/144 43/115/144
+f 39/102/145 103/101/145 41/116/145
+f 65/90/146 67/117/146 1/91/146
+f 35/120/147 99/118/147 37/119/147
+f 97/93/148 99/118/148 33/94/148
+f 32/8/5 40/9/5 16/6/5
+f 74/197/57 73/122/57 72/124/57
+f 86/131/58 88/125/58 85/127/58
+f 26/149/59 24/128/59 25/130/59
+f 84/142/149 86/131/149 83/132/149
+f 114/191/61 116/133/61 113/135/61
+f 20/189/62 18/136/62 19/137/62
+f 80/224/63 82/138/63 79/140/63
+f 14/210/64 13/141/64 16/1/64
+f 82/225/65 84/142/65 81/143/65
+f 10/148/66 9/144/66 12/146/66
+f 8/188/67 7/147/67 10/148/67
+f 28/198/68 26/149/68 27/150/68
+f 4/154/69 3/151/69 6/153/69
+f 2/36/150 1/38/150 4/154/150
+f 58/58/151 57/57/151 60/157/151
+f 49/107/152 47/111/152 113/108/152
+f 60/157/153 59/156/153 62/44/153
+f 93/96/154 95/158/154 29/97/154
+f 54/162/77 53/159/77 56/160/77
+f 52/204/155 51/161/155 54/162/155
+f 88/125/79 90/163/79 87/126/79
+f 116/133/80 118/165/80 115/134/80
+f 46/207/81 44/167/81 45/169/81
+f 44/167/82 42/170/82 43/168/82
+f 42/170/156 40/172/156 41/171/156
+f 40/172/84 38/174/84 39/173/84
+f 38/174/157 36/176/157 37/175/157
+f 36/176/158 34/42/158 35/177/158
+f 120/182/88 122/179/88 119/181/88
+f 118/165/89 120/182/89 117/166/89
+f 90/163/90 92/183/90 89/164/90
+f 48/226/91 47/185/91 50/187/91
+f 6/153/92 5/152/92 8/188/92
+f 22/209/93 20/189/93 21/190/93
+f 112/227/94 114/191/94 111/192/94
+f 80/224/95 79/140/95 78/194/95
+f 78/194/96 77/193/96 76/196/96
+f 76/196/97 75/195/97 74/197/97
+f 30/39/159 28/198/159 29/40/159
+f 72/124/99 71/123/99 70/200/99
+f 70/200/100 69/199/100 68/202/100
+f 66/46/160 68/202/160 65/47/160
+f 50/187/161 49/186/161 52/204/161
+f 124/206/104 126/54/104 123/205/104
+f 122/179/105 124/206/105 121/180/105
+f 91/184/162 92/183/162 93/51/162
+f 48/228/107 46/207/107 47/208/107
+f 24/128/108 22/209/108 23/129/108
+f 12/146/109 11/145/109 14/210/109
+f 112/227/110 111/192/110 110/212/110
+f 110/212/111 109/211/111 108/214/111
+f 108/214/112 107/213/112 106/216/112
+f 106/216/113 105/215/113 104/218/113
+f 104/218/114 103/217/114 102/220/114
+f 102/220/115 101/219/115 100/222/115
+f 100/222/116 99/221/116 98/52/116
+g HSLampen_Cube.000_Green
+usemtl Green
+f 272/1/1 271/2/1 273/3/1
+f 320/4/2 260/5/2 272/6/2
+f 272/6/3 276/7/3 288/8/3
+f 320/4/4 296/9/4 304/10/4
+f 304/10/4 308/11/4 320/4/4
+f 272/6/3 260/5/3 264/12/3
+f 264/12/3 266/13/3 272/6/3
+f 288/8/3 276/7/3 280/14/3
+f 280/14/2 284/15/2 288/8/2
+f 288/8/5 290/16/5 296/9/5
+f 296/9/4 300/17/4 302/18/4
+f 320/4/4 308/11/4 312/19/4
+f 312/19/4 316/20/4 320/4/4
+f 320/4/6 258/21/6 260/5/6
+f 260/5/7 262/22/7 264/12/7
+f 272/6/3 266/13/3 268/23/3
+f 268/23/3 270/24/3 272/6/3
+f 272/6/8 274/25/8 276/7/8
+f 276/7/2 278/26/2 280/14/2
+f 280/14/9 282/27/9 284/15/9
+f 284/15/3 286/28/3 288/8/3
+f 296/9/163 290/16/163 292/29/163
+f 292/29/11 294/30/11 296/9/11
+f 296/9/164 298/31/164 300/17/164
+f 296/9/4 302/18/4 304/10/4
+f 304/10/13 306/32/13 308/11/13
+f 308/11/4 310/33/4 312/19/4
+f 312/19/165 314/34/165 316/20/165
+f 316/20/15 318/35/15 320/4/15
+f 258/36/16 320/37/16 257/38/16
+f 286/39/17 285/40/17 288/41/17
+f 290/42/18 288/41/18 289/43/18
+f 318/44/19 317/45/19 320/37/19
+f 322/46/20 321/47/20 384/48/20
+f 350/49/21 352/50/21 349/51/21
+f 354/52/22 353/53/22 352/50/22
+f 382/54/23 384/48/23 381/55/23
+f 311/56/24 313/57/24 314/58/24
+f 313/59/25 377/60/25 379/61/25
+f 343/62/26 345/63/26 281/64/26
+f 335/65/27 337/66/27 273/67/27
+f 331/68/28 267/69/28 265/70/28
+f 337/66/29 339/71/29 275/72/29
+f 379/61/30 381/73/30 317/74/30
+f 329/75/31 265/70/31 263/76/31
+f 335/65/32 271/77/32 269/78/32
+f 341/79/33 343/62/33 279/80/33
+f 267/69/34 331/68/34 333/81/34
+f 339/71/35 341/79/35 277/82/35
+f 327/83/36 263/76/36 261/84/36
+f 325/85/37 261/84/37 259/86/37
+f 345/63/38 347/87/38 283/88/38
+f 383/89/39 321/90/39 257/91/39
+f 381/73/166 383/89/166 319/92/166
+f 353/93/41 289/94/41 287/95/41
+f 347/87/42 349/96/42 285/97/42
+f 311/98/43 375/99/43 377/60/43
+f 357/100/167 359/101/167 295/102/167
+f 309/103/45 373/104/45 375/99/45
+f 307/105/46 371/106/46 373/104/46
+f 305/107/47 369/108/47 371/106/47
+f 365/109/48 367/110/48 303/111/48
+f 363/112/49 365/109/49 301/113/49
+f 361/114/50 363/112/50 299/115/50
+f 359/101/168 361/114/168 297/116/168
+f 323/117/52 259/86/52 257/91/52
+f 355/118/53 357/100/53 293/119/53
+f 355/118/54 291/120/54 289/94/54
+f 296/9/55 320/4/55 272/6/55
+f 320/37/56 319/121/56 257/38/56
+f 329/122/57 327/123/57 328/124/57
+f 344/125/58 343/126/58 341/127/58
+f 280/128/59 279/129/59 281/130/59
+f 342/131/60 341/127/60 339/132/60
+f 372/133/61 371/134/61 369/135/61
+f 274/136/62 273/3/62 275/137/62
+f 338/138/63 337/139/63 335/140/63
+f 269/141/64 271/2/64 272/1/64
+f 340/142/65 339/132/65 337/143/65
+f 265/144/66 267/145/66 268/146/66
+f 263/147/67 265/144/67 266/148/67
+f 282/149/68 281/130/68 283/150/68
+f 259/151/69 261/152/69 262/153/69
+f 257/38/70 259/151/70 260/154/70
+f 285/40/71 287/155/71 288/41/71
+f 288/41/72 287/155/72 289/43/72
+f 313/57/73 315/156/73 316/157/73
+f 303/111/74 367/110/74 369/108/74
+f 315/156/75 317/45/75 318/44/75
+f 351/158/76 287/95/76 285/97/76
+f 309/159/77 311/56/77 312/160/77
+f 307/161/78 309/159/78 310/162/78
+f 346/163/79 345/164/79 343/126/79
+f 374/165/80 373/166/80 371/134/80
+f 300/167/81 299/168/81 301/169/81
+f 298/170/82 297/171/82 299/168/82
+f 296/172/83 295/173/83 297/171/83
+f 294/174/84 293/175/84 295/173/84
+f 292/176/85 291/177/85 293/175/85
+f 290/42/86 289/43/86 291/177/86
+f 317/45/87 319/121/87 320/37/87
+f 321/47/20 383/178/20 384/48/20
+f 378/179/88 377/180/88 375/181/88
+f 376/182/89 375/181/89 373/166/89
+f 348/183/90 347/184/90 345/164/90
+f 303/185/91 305/186/91 306/187/91
+f 261/152/92 263/147/92 264/188/92
+f 276/189/93 275/137/93 277/190/93
+f 370/191/94 369/135/94 367/192/94
+f 335/140/95 333/193/95 334/194/95
+f 333/193/96 331/195/96 332/196/96
+f 331/195/97 329/122/97 330/197/97
+f 284/198/98 283/150/98 285/40/98
+f 327/123/99 325/199/99 326/200/99
+f 325/199/100 323/201/100 324/202/100
+f 324/202/101 323/201/101 321/47/101
+f 352/50/21 351/203/21 349/51/21
+f 353/53/102 351/203/102 352/50/102
+f 305/186/103 307/161/103 308/204/103
+f 382/54/104 381/55/104 379/205/104
+f 380/206/105 379/205/105 377/180/105
+f 348/183/106 350/49/106 349/51/106
+f 302/207/107 301/169/107 303/208/107
+f 278/209/108 277/190/108 279/129/108
+f 267/145/109 269/141/109 270/210/109
+f 367/192/110 365/211/110 366/212/110
+f 365/211/111 363/213/111 364/214/111
+f 363/213/112 361/215/112 362/216/112
+f 361/215/113 359/217/113 360/218/113
+f 359/217/114 357/219/114 358/220/114
+f 357/219/115 355/221/115 356/222/115
+f 355/221/116 353/53/116 354/52/116
+f 384/48/23 383/178/23 381/55/23
+f 274/136/117 272/1/117 273/3/117
+f 312/160/118 311/56/118 314/58/118
+f 315/223/119 313/59/119 379/61/119
+f 279/80/120 343/62/120 281/64/120
+f 271/77/121 335/65/121 273/67/121
+f 329/75/122 331/68/122 265/70/122
+f 273/67/123 337/66/123 275/72/123
+f 315/223/124 379/61/124 317/74/124
+f 327/83/169 329/75/169 263/76/169
+f 333/81/126 335/65/126 269/78/126
+f 277/82/127 341/79/127 279/80/127
+f 269/78/128 267/69/128 333/81/128
+f 275/72/129 339/71/129 277/82/129
+f 325/85/130 327/83/130 261/84/130
+f 323/117/131 325/85/131 259/86/131
+f 281/64/132 345/63/132 283/88/132
+f 319/92/133 383/89/133 257/91/133
+f 317/74/134 381/73/134 319/92/134
+f 351/158/135 353/93/135 287/95/135
+f 283/88/136 347/87/136 285/97/136
+f 313/59/137 311/98/137 377/60/137
+f 293/119/138 357/100/138 295/102/138
+f 311/98/139 309/103/139 375/99/139
+f 309/103/140 307/105/140 373/104/140
+f 307/105/141 305/107/141 371/106/141
+f 301/113/142 365/109/142 303/111/142
+f 299/115/143 363/112/143 301/113/143
+f 297/116/170 361/114/170 299/115/170
+f 295/102/145 359/101/145 297/116/145
+f 321/90/146 323/117/146 257/91/146
+f 291/120/171 355/118/171 293/119/171
+f 353/93/148 355/118/148 289/94/148
+f 288/8/5 296/9/5 272/6/5
+f 330/197/57 329/122/57 328/124/57
+f 342/131/58 344/125/58 341/127/58
+f 282/149/59 280/128/59 281/130/59
+f 340/142/149 342/131/149 339/132/149
+f 370/191/61 372/133/61 369/135/61
+f 276/189/62 274/136/62 275/137/62
+f 336/224/63 338/138/63 335/140/63
+f 270/210/64 269/141/64 272/1/64
+f 338/225/65 340/142/65 337/143/65
+f 266/148/66 265/144/66 268/146/66
+f 264/188/67 263/147/67 266/148/67
+f 284/198/68 282/149/68 283/150/68
+f 260/154/69 259/151/69 262/153/69
+f 258/36/150 257/38/150 260/154/150
+f 314/58/151 313/57/151 316/157/151
+f 305/107/152 303/111/152 369/108/152
+f 316/157/153 315/156/153 318/44/153
+f 349/96/172 351/158/172 285/97/172
+f 310/162/77 309/159/77 312/160/77
+f 308/204/155 307/161/155 310/162/155
+f 344/125/79 346/163/79 343/126/79
+f 372/133/80 374/165/80 371/134/80
+f 302/207/81 300/167/81 301/169/81
+f 300/167/82 298/170/82 299/168/82
+f 298/170/156 296/172/156 297/171/156
+f 296/172/84 294/174/84 295/173/84
+f 294/174/157 292/176/157 293/175/157
+f 292/176/158 290/42/158 291/177/158
+f 376/182/88 378/179/88 375/181/88
+f 374/165/89 376/182/89 373/166/89
+f 346/163/90 348/183/90 345/164/90
+f 304/226/91 303/185/91 306/187/91
+f 262/153/92 261/152/92 264/188/92
+f 278/209/93 276/189/93 277/190/93
+f 368/227/94 370/191/94 367/192/94
+f 336/224/95 335/140/95 334/194/95
+f 334/194/96 333/193/96 332/196/96
+f 332/196/97 331/195/97 330/197/97
+f 286/39/159 284/198/159 285/40/159
+f 328/124/99 327/123/99 326/200/99
+f 326/200/100 325/199/100 324/202/100
+f 322/46/160 324/202/160 321/47/160
+f 306/187/161 305/186/161 308/204/161
+f 380/206/104 382/54/104 379/205/104
+f 378/179/105 380/206/105 377/180/105
+f 347/184/162 348/183/162 349/51/162
+f 304/228/107 302/207/107 303/208/107
+f 280/128/108 278/209/108 279/129/108
+f 268/146/109 267/145/109 270/210/109
+f 368/227/110 367/192/110 366/212/110
+f 366/212/111 365/211/111 364/214/111
+f 364/214/112 363/213/112 362/216/112
+f 362/216/113 361/215/113 360/218/113
+f 360/218/114 359/217/114 358/220/114
+f 358/220/115 357/219/115 356/222/115
+f 356/222/116 355/221/116 354/52/116
+g HSLampen_Cube.000_Distance
+usemtl Distance
+f 132/5/173 134/22/173 136/12/173
+f 185/59/174 249/60/174 251/61/174
+f 176/10/175 180/11/175 192/4/175
+f 168/9/176 162/16/176 164/29/176
+f 252/206/177 251/205/177 249/180/177
+f 152/14/178 156/15/178 160/8/178
+f 188/20/179 190/35/179 192/4/179
+f 187/156/180 189/45/180 190/44/180
+f 225/53/181 223/203/181 224/50/181
+f 220/183/182 219/184/182 217/164/182
+f 248/182/183 247/181/183 245/166/183
+f 164/29/184 166/30/184 168/9/184
+f 156/198/185 155/150/185 157/40/185
+f 168/9/186 172/17/186 174/18/186
+f 205/193/187 203/195/187 204/196/187
+f 201/122/188 199/123/188 200/124/188
+f 231/101/189 233/114/189 169/116/189
+f 210/138/190 209/139/190 207/140/190
+f 133/152/191 135/147/191 136/188/191
+f 158/39/192 157/40/192 160/41/192
+f 141/141/193 143/2/193 144/1/193
+f 160/8/194 148/7/194 152/14/194
+f 180/11/195 182/33/195 184/19/195
+f 176/10/196 178/32/196 180/11/196
+f 190/44/197 189/45/197 192/37/197
+f 197/199/198 195/201/198 196/202/198
+f 192/37/199 191/121/199 129/38/199
+f 203/68/200 139/69/200 137/70/200
+f 211/71/201 213/79/201 149/82/201
+f 192/4/175 168/9/175 176/10/175
+f 177/107/202 241/108/202 243/106/202
+f 144/6/203 138/13/203 140/23/203
+f 237/109/204 239/110/204 175/111/204
+f 160/41/205 159/155/205 161/43/205
+f 135/147/206 137/144/206 138/148/206
+f 140/23/194 142/24/194 144/6/194
+f 144/1/207 143/2/207 145/3/207
+f 251/61/208 253/73/208 189/74/208
+f 242/191/209 241/135/209 239/192/209
+f 225/93/210 161/94/210 159/95/210
+f 207/65/211 209/66/211 145/67/211
+f 166/174/212 165/175/212 167/173/212
+f 193/47/213 255/178/213 256/48/213
+f 136/12/194 138/13/194 144/6/194
+f 231/217/214 229/219/214 230/220/214
+f 162/42/215 160/41/215 161/43/215
+f 179/161/216 181/159/216 182/162/216
+f 215/62/217 217/63/217 153/64/217
+f 201/75/218 137/70/218 135/76/218
+f 148/189/219 147/137/219 149/190/219
+f 150/209/220 149/190/220 151/129/220
+f 175/111/221 239/110/221 241/108/221
+f 229/100/222 231/101/222 167/102/222
+f 139/145/223 141/141/223 142/210/223
+f 239/192/224 237/211/224 238/212/224
+f 217/63/225 219/87/225 155/88/225
+f 177/186/226 179/161/226 180/204/226
+f 174/207/227 173/169/227 175/208/227
+f 152/14/228 154/27/228 156/15/228
+f 185/57/229 187/156/229 188/157/229
+f 164/176/230 163/177/230 165/175/230
+f 209/66/231 211/71/231 147/72/231
+f 172/167/232 171/168/232 173/169/232
+f 233/114/233 235/112/233 171/115/233
+f 254/54/234 256/48/234 253/55/234
+f 212/142/235 211/132/235 209/143/235
+f 226/52/236 225/53/236 224/50/236
+f 229/219/237 227/221/237 228/222/237
+f 181/103/238 245/104/238 247/99/238
+f 189/45/239 191/121/239 192/37/239
+f 184/19/240 186/34/240 188/20/240
+f 130/36/102 192/37/102 129/38/102
+f 194/46/213 193/47/213 256/48/213
+f 144/6/241 146/25/241 148/7/241
+f 144/6/194 148/7/194 160/8/194
+f 144/6/194 132/5/194 136/12/194
+f 156/15/194 158/28/194 160/8/194
+f 255/89/242 193/90/242 129/91/242
+f 152/128/243 151/129/243 153/130/243
+f 184/19/175 188/20/175 192/4/175
+f 192/4/175 180/11/175 184/19/175
+f 157/40/244 159/155/244 160/41/244
+f 137/144/245 139/145/245 140/146/245
+f 256/48/234 255/178/234 253/55/234
+f 233/215/246 231/217/246 232/218/246
+f 250/179/247 249/180/247 247/181/247
+f 131/151/248 133/152/248 134/153/248
+f 207/140/249 205/193/249 206/194/249
+f 168/9/250 170/31/250 172/17/250
+f 160/8/251 162/16/251 168/9/251
+f 181/159/252 183/56/252 184/160/252
+f 168/9/175 174/18/175 176/10/175
+f 196/202/253 195/201/253 193/47/253
+f 179/105/254 243/106/254 245/104/254
+f 168/172/255 167/173/255 169/171/255
+f 220/183/256 222/49/256 221/51/256
+f 192/4/257 132/5/257 144/6/257
+f 146/136/258 145/3/258 147/137/258
+f 235/112/259 237/109/259 173/113/259
+f 192/4/260 130/21/260 132/5/260
+f 195/117/261 131/86/261 129/91/261
+f 227/221/262 225/53/262 226/52/262
+f 183/98/263 247/99/263 249/60/263
+f 253/73/264 255/89/264 191/92/264
+f 216/125/265 215/126/265 213/127/265
+f 237/211/266 235/213/266 236/214/266
+f 199/123/267 197/199/267 198/200/267
+f 254/54/268 253/55/268 251/205/268
+f 224/50/269 223/203/269 221/51/269
+f 213/79/270 215/62/270 151/80/270
+f 148/7/257 150/26/257 152/14/257
+f 222/49/269 224/50/269 221/51/269
+f 197/85/271 133/84/271 131/86/271
+f 218/163/272 217/164/272 215/126/272
+f 203/195/273 201/122/273 202/197/273
+f 235/213/274 233/215/274 234/216/274
+f 227/118/275 163/120/275 161/94/275
+f 154/149/276 153/130/276 155/150/276
+f 175/185/277 177/186/277 178/187/277
+f 219/87/278 221/96/278 157/97/278
+f 199/83/279 135/76/279 133/84/279
+f 139/69/280 203/68/280 205/81/280
+f 170/170/281 169/171/281 171/168/281
+f 183/56/282 185/57/282 186/58/282
+f 227/118/283 229/100/283 165/119/283
+f 168/9/284 192/4/284 144/6/284
+f 246/165/285 245/166/285 243/134/285
+f 162/42/286 161/43/286 163/177/286
+f 223/158/287 159/95/287 157/97/287
+f 129/38/288 131/151/288 132/154/288
+f 244/133/289 243/134/289 241/135/289
+f 214/131/290 213/127/290 211/132/290
+f 207/65/291 143/77/291 141/78/291
+f 146/136/292 144/1/292 145/3/292
+f 184/160/293 183/56/293 186/58/293
+f 187/223/294 185/59/294 251/61/294
+f 151/80/295 215/62/295 153/64/295
+f 143/77/296 207/65/296 145/67/296
+f 201/75/297 203/68/297 137/70/297
+f 145/67/298 209/66/298 147/72/298
+f 187/223/299 251/61/299 189/74/299
+f 199/83/300 201/75/300 135/76/300
+f 205/81/301 207/65/301 141/78/301
+f 149/82/302 213/79/302 151/80/302
+f 141/78/303 139/69/303 205/81/303
+f 147/72/304 211/71/304 149/82/304
+f 197/85/305 199/83/305 133/84/305
+f 195/117/306 197/85/306 131/86/306
+f 153/64/307 217/63/307 155/88/307
+f 191/92/308 255/89/308 129/91/308
+f 189/74/309 253/73/309 191/92/309
+f 223/158/310 225/93/310 159/95/310
+f 155/88/311 219/87/311 157/97/311
+f 185/59/312 183/98/312 249/60/312
+f 165/119/313 229/100/313 167/102/313
+f 183/98/314 181/103/314 247/99/314
+f 181/103/315 179/105/315 245/104/315
+f 179/105/316 177/107/316 243/106/316
+f 173/113/317 237/109/317 175/111/317
+f 171/115/318 235/112/318 173/113/318
+f 169/116/319 233/114/319 171/115/319
+f 167/102/320 231/101/320 169/116/320
+f 193/90/321 195/117/321 129/91/321
+f 163/120/322 227/118/322 165/119/322
+f 225/93/323 227/118/323 161/94/323
+f 160/8/251 168/9/251 144/6/251
+f 202/197/188 201/122/188 200/124/188
+f 214/131/265 216/125/265 213/127/265
+f 154/149/243 152/128/243 153/130/243
+f 212/142/290 214/131/290 211/132/290
+f 242/191/289 244/133/289 241/135/289
+f 148/189/258 146/136/258 147/137/258
+f 208/224/190 210/138/190 207/140/190
+f 142/210/193 141/141/193 144/1/193
+f 210/225/235 212/142/235 209/143/235
+f 138/148/245 137/144/245 140/146/245
+f 136/188/206 135/147/206 138/148/206
+f 156/198/276 154/149/276 155/150/276
+f 132/154/248 131/151/248 134/153/248
+f 130/36/324 129/38/324 132/154/324
+f 186/58/325 185/57/325 188/157/325
+f 177/107/326 175/111/326 241/108/326
+f 188/157/327 187/156/327 190/44/327
+f 221/96/328 223/158/328 157/97/328
+f 182/162/252 181/159/252 184/160/252
+f 180/204/216 179/161/216 182/162/216
+f 216/125/272 218/163/272 215/126/272
+f 244/133/285 246/165/285 243/134/285
+f 174/207/232 172/167/232 173/169/232
+f 172/167/281 170/170/281 171/168/281
+f 170/170/329 168/172/329 169/171/329
+f 168/172/212 166/174/212 167/173/212
+f 166/174/230 164/176/230 165/175/230
+f 164/176/330 162/42/330 163/177/330
+f 248/182/247 250/179/247 247/181/247
+f 246/165/183 248/182/183 245/166/183
+f 218/163/182 220/183/182 217/164/182
+f 176/226/277 175/185/277 178/187/277
+f 134/153/191 133/152/191 136/188/191
+f 150/209/331 148/189/331 149/190/331
+f 240/227/209 242/191/209 239/192/209
+f 208/224/249 207/140/249 206/194/249
+f 206/194/187 205/193/187 204/196/187
+f 204/196/273 203/195/273 202/197/273
+f 158/39/332 156/198/332 157/40/332
+f 200/124/267 199/123/267 198/200/267
+f 198/200/198 197/199/198 196/202/198
+f 194/46/333 196/202/333 193/47/333
+f 178/187/226 177/186/226 180/204/226
+f 252/206/268 254/54/268 251/205/268
+f 250/179/177 252/206/177 249/180/177
+f 219/184/334 220/183/334 221/51/334
+f 176/228/227 174/207/227 175/208/227
+f 152/128/220 150/209/220 151/129/220
+f 140/146/223 139/145/223 142/210/223
+f 240/227/224 239/192/224 238/212/224
+f 238/212/266 237/211/266 236/214/266
+f 236/214/274 235/213/274 234/216/274
+f 234/216/246 233/215/246 232/218/246
+f 232/218/214 231/217/214 230/220/214
+f 230/220/237 229/219/237 228/222/237
+f 228/222/262 227/221/262 226/52/262
+g HSLampen_Cube.000_Repeater
+usemtl Repeater
+f 447/46/333 445/202/333 448/47/333
+f 485/15/194 483/28/194 481/8/194
+f 495/136/258 496/3/258 494/137/258
+f 495/136/335 497/1/335 496/3/335
+f 389/206/177 390/205/177 392/180/177
+f 462/105/336 464/107/336 398/106/336
+f 452/45/239 450/121/239 449/37/239
+f 511/36/102 449/37/102 512/38/102
+f 497/6/203 503/13/203 501/23/203
+f 497/1/207 498/2/207 496/3/207
+f 416/53/181 418/203/181 417/50/181
+f 418/158/287 482/95/287 484/97/287
+f 452/74/337 388/73/337 450/92/337
+f 466/185/277 464/186/277 463/187/277
+f 466/111/221 402/110/221 400/108/221
+f 424/63/338 422/87/338 486/88/338
+f 438/195/273 440/122/273 439/197/273
+f 462/161/216 460/159/216 459/162/216
+f 437/196/273 438/195/273 439/197/273
+f 494/72/304 430/71/304 492/82/304
+f 459/162/252 460/159/252 457/160/252
+f 487/149/243 489/128/243 488/130/243
+f 434/140/249 436/193/249 435/194/249
+f 406/112/259 404/109/259 468/113/259
+f 412/219/237 414/221/237 413/222/237
+f 456/57/229 454/156/229 453/157/229
+f 411/220/237 412/219/237 413/222/237
+f 393/182/247 391/179/247 394/181/247
+f 416/93/323 414/118/323 480/94/323
+f 442/83/279 506/76/279 508/84/279
+f 449/4/257 509/5/257 497/6/257
+f 475/174/230 477/176/230 476/175/230
+f 458/98/263 394/99/263 392/60/263
+f 496/67/298 432/66/298 494/72/298
+f 507/153/191 508/152/191 505/188/191
+f 397/133/285 395/165/285 398/134/285
+f 464/107/326 466/111/326 400/108/326
+f 429/142/339 430/132/339 432/143/339
+f 436/193/187 438/195/187 437/196/187
+f 454/223/299 390/61/299 452/74/299
+f 499/210/193 500/141/193 497/1/193
+f 479/42/286 480/43/286 478/177/286
+f 432/66/231 430/71/231 494/72/231
+f 465/226/277 466/185/277 463/187/277
+f 444/199/198 446/201/198 445/202/198
+f 440/75/297 438/68/297 504/70/297
+f 460/159/252 458/56/252 457/160/252
+f 456/59/312 458/98/312 392/60/312
+f 491/209/331 493/189/331 492/190/331
+f 429/142/290 427/131/290 430/132/290
+f 478/120/322 414/118/322 476/119/322
+f 388/73/340 386/89/340 450/92/340
+f 471/170/281 472/171/281 470/168/281
+f 467/207/341 469/167/341 468/169/341
+f 431/138/190 432/139/190 434/140/190
+f 450/92/308 386/89/308 512/91/308
+f 473/172/255 474/173/255 472/171/255
+f 387/54/268 388/55/268 390/205/268
+f 468/113/317 404/109/317 466/111/317
+f 497/6/241 495/25/241 493/7/241
+f 427/131/290 428/127/290 430/132/290
+f 399/191/289 397/133/289 400/135/289
+f 428/79/270 426/62/270 490/80/270
+f 493/7/257 491/26/257 489/14/257
+f 509/5/342 507/22/342 505/12/342
+f 410/217/214 412/219/214 411/220/214
+f 464/107/202 400/108/202 398/106/202
+f 438/68/200 502/69/200 504/70/200
+f 385/48/234 386/178/234 388/55/234
+f 460/103/238 396/104/238 394/99/238
+f 386/89/242 448/90/242 512/91/242
+f 409/218/214 410/217/214 411/220/214
+f 469/167/232 470/168/232 468/169/232
+f 509/154/248 510/151/248 507/153/248
+f 511/36/324 512/38/324 509/154/324
+f 443/200/198 444/199/198 445/202/198
+f 488/64/343 424/63/343 486/88/343
+f 405/214/274 406/213/274 407/216/274
+f 470/115/318 406/112/318 468/113/318
+f 489/128/243 490/129/243 488/130/243
+f 473/9/344 471/31/344 469/17/344
+f 444/85/305 442/83/305 508/84/305
+f 395/165/183 393/182/183 396/166/183
+f 456/59/345 392/60/345 390/61/345
+f 465/10/196 463/32/196 461/11/196
+f 453/20/179 451/35/179 449/4/179
+f 389/206/268 387/54/268 390/205/268
+f 493/189/219 494/137/219 492/190/219
+f 422/87/278 420/96/278 484/97/278
+f 498/77/296 434/65/296 496/67/296
+f 408/215/246 410/217/246 409/218/246
+f 487/149/276 488/130/276 486/150/276
+f 408/114/233 406/112/233 470/115/233
+f 445/202/253 446/201/253 448/47/253
+f 502/69/280 438/68/280 436/81/280
+f 506/147/206 504/144/206 503/148/206
+f 463/187/226 464/186/226 461/204/226
+f 503/148/245 504/144/245 501/146/245
+f 404/211/266 406/213/266 405/214/266
+f 489/128/220 491/209/220 490/129/220
+f 501/146/223 502/145/223 499/210/223
+f 449/4/175 473/9/175 465/10/175
+f 504/144/245 502/145/245 501/146/245
+f 387/54/234 385/48/234 388/55/234
+f 434/65/211 432/66/211 496/67/211
+f 483/39/192 484/40/192 481/41/192
+f 467/207/227 468/169/227 466/208/227
+f 473/9/176 479/16/176 477/29/176
+f 404/109/204 402/110/204 466/111/204
+f 472/116/346 408/114/346 470/115/346
+f 439/197/188 440/122/188 441/124/188
+f 481/41/205 482/155/205 480/43/205
+f 391/179/177 389/206/177 392/180/177
+f 399/191/209 400/135/209 402/192/209
+f 502/145/223 500/141/223 499/210/223
+f 481/8/194 493/7/194 489/14/194
+f 457/160/293 458/56/293 455/58/293
+f 476/119/313 412/100/313 474/102/313
+f 449/4/260 511/21/260 509/5/260
+f 492/82/302 428/79/302 490/80/302
+f 483/39/332 485/198/332 484/40/332
+f 473/172/212 475/174/212 474/173/212
+f 423/163/272 424/164/272 426/126/272
+f 457/19/175 453/20/175 449/4/175
+f 473/9/284 449/4/284 497/6/284
+f 505/188/206 506/147/206 503/148/206
+f 477/29/347 475/30/347 473/9/347
+f 454/223/294 456/59/294 390/61/294
+f 436/81/348 434/65/348 500/78/348
+f 401/227/224 402/192/224 403/212/224
+f 473/9/186 469/17/186 467/18/186
+f 448/47/213 386/178/213 385/48/213
+f 461/204/216 462/161/216 459/162/216
+f 485/198/276 487/149/276 486/150/276
+f 484/40/244 482/155/244 481/41/244
+f 497/6/194 493/7/194 481/8/194
+f 474/102/320 410/101/320 472/116/320
+f 453/157/327 454/156/327 451/44/327
+f 477/176/330 479/42/330 478/177/330
+f 431/225/235 429/142/235 432/143/235
+f 447/46/213 448/47/213 385/48/213
+f 433/224/249 434/140/249 435/194/249
+f 425/125/272 423/163/272 426/126/272
+f 481/8/251 473/9/251 497/6/251
+f 469/167/281 471/170/281 470/168/281
+f 493/189/258 495/136/258 494/137/258
+f 441/124/267 442/123/267 443/200/267
+f 465/228/227 467/207/227 466/208/227
+f 458/98/314 460/103/314 394/99/314
+f 413/222/262 414/221/262 415/52/262
+f 415/52/236 416/53/236 417/50/236
+f 442/83/300 440/75/300 506/76/300
+f 418/158/310 416/93/310 482/95/310
+f 449/4/175 461/11/175 457/19/175
+f 395/165/285 396/166/285 398/134/285
+f 391/179/247 392/180/247 394/181/247
+f 473/9/175 467/18/175 465/10/175
+f 421/183/256 419/49/256 420/51/256
+f 442/123/267 444/199/267 443/200/267
+f 419/49/269 417/50/269 420/51/269
+f 406/213/274 408/215/274 407/216/274
+f 458/56/282 456/57/282 455/58/282
+f 407/216/246 408/215/246 409/218/246
+f 397/133/289 398/134/289 400/135/289
+f 490/80/295 426/62/295 488/64/295
+f 455/58/325 456/57/325 453/157/325
+f 402/192/224 404/211/224 403/212/224
+f 475/174/212 476/175/212 474/173/212
+f 460/103/349 462/105/349 396/104/349
+f 505/12/194 503/13/194 497/6/194
+f 485/198/185 486/150/185 484/40/185
+f 433/224/190 431/138/190 434/140/190
+f 426/62/217 424/63/217 488/64/217
+f 420/96/350 418/158/350 484/97/350
+f 479/42/215 481/41/215 480/43/215
+f 423/163/182 421/183/182 424/164/182
+f 401/227/209 399/191/209 402/192/209
+f 486/88/311 422/87/311 484/97/311
+f 422/184/334 421/183/334 420/51/334
+f 403/212/266 404/211/266 405/214/266
+f 497/6/194 509/5/194 505/12/194
+f 446/117/261 510/86/261 512/91/261
+f 457/19/351 455/34/351 453/20/351
+f 500/78/352 502/69/352 436/81/352
+f 510/151/248 508/152/248 507/153/248
+f 481/8/353 479/16/353 473/9/353
+f 416/93/210 480/94/210 482/95/210
+f 393/182/183 394/181/183 396/166/183
+f 417/50/269 418/203/269 420/51/269
+f 444/85/354 508/84/354 510/86/354
+f 414/118/275 478/120/275 480/94/275
+f 414/118/355 412/100/355 476/119/355
+f 512/38/288 510/151/288 509/154/288
+f 434/65/291 498/77/291 500/78/291
+f 454/156/180 452/45/180 451/44/180
+f 421/183/182 422/184/182 424/164/182
+f 390/61/208 388/73/208 452/74/208
+f 414/221/262 416/53/262 415/52/262
+f 435/194/187 436/193/187 437/196/187
+f 427/131/265 425/125/265 428/127/265
+f 440/122/188 442/123/188 441/124/188
+f 440/75/218 504/70/218 506/76/218
+f 491/209/220 492/190/220 490/129/220
+f 412/100/356 410/101/356 474/102/356
+f 446/117/357 444/85/357 510/86/357
+f 464/186/226 462/161/226 461/204/226
+f 489/14/228 487/27/228 485/15/228
+f 477/176/230 478/177/230 476/175/230
+f 462/105/254 398/106/254 396/104/254
+f 465/10/175 461/11/175 449/4/175
+f 489/14/178 485/15/178 481/8/178
+f 501/23/194 499/24/194 497/6/194
+f 425/125/265 426/126/265 428/127/265
+f 448/90/358 446/117/358 512/91/358
+f 471/170/329 473/172/329 472/171/329
+f 410/101/359 408/114/359 472/116/359
+f 508/152/191 506/147/191 505/188/191
+f 500/141/193 498/2/193 497/1/193
+f 461/11/360 459/33/360 457/19/360
+f 451/44/197 452/45/197 449/37/197
+f 449/37/199 450/121/199 512/38/199
+f 430/71/201 428/79/201 492/82/201
+g HSLampen_Cube.000_Red
+usemtl Red
+f 528/1/1 527/2/1 529/3/1
+f 576/4/2 516/5/2 528/6/2
+f 528/6/3 532/7/3 544/8/3
+f 576/4/4 552/9/4 560/10/4
+f 560/10/4 564/11/4 576/4/4
+f 528/6/3 516/5/3 520/12/3
+f 520/12/3 522/13/3 528/6/3
+f 544/8/3 532/7/3 536/14/3
+f 536/14/2 540/15/2 544/8/2
+f 544/8/5 546/16/5 552/9/5
+f 552/9/4 556/17/4 558/18/4
+f 576/4/4 564/11/4 568/19/4
+f 568/19/4 572/20/4 576/4/4
+f 576/4/6 514/21/6 516/5/6
+f 516/5/7 518/22/7 520/12/7
+f 528/6/3 522/13/3 524/23/3
+f 524/23/3 526/24/3 528/6/3
+f 528/6/8 530/25/8 532/7/8
+f 532/7/2 534/26/2 536/14/2
+f 536/14/2 538/27/2 540/15/2
+f 540/15/3 542/28/3 544/8/3
+f 552/9/163 546/16/163 548/29/163
+f 548/29/11 550/30/11 552/9/11
+f 552/9/164 554/31/164 556/17/164
+f 552/9/4 558/18/4 560/10/4
+f 560/10/13 562/32/13 564/11/13
+f 564/11/4 566/33/4 568/19/4
+f 568/19/165 570/34/165 572/20/165
+f 572/20/361 574/35/361 576/4/361
+f 514/36/16 576/37/16 513/38/16
+f 542/39/17 541/40/17 544/41/17
+f 546/42/18 544/41/18 545/43/18
+f 574/44/19 573/45/19 576/37/19
+f 578/46/20 577/47/20 640/48/20
+f 606/49/21 608/50/21 605/51/21
+f 610/52/22 609/53/22 608/50/22
+f 638/54/23 640/48/23 637/55/23
+f 567/56/24 569/57/24 570/58/24
+f 569/59/25 633/60/25 635/61/25
+f 599/62/26 601/63/26 537/64/26
+f 591/65/27 593/66/27 529/67/27
+f 587/68/28 523/69/28 521/70/28
+f 593/66/29 595/71/29 531/72/29
+f 635/61/30 637/73/30 573/74/30
+f 585/75/31 521/70/31 519/76/31
+f 591/65/32 527/77/32 525/78/32
+f 597/79/33 599/62/33 535/80/33
+f 523/69/34 587/68/34 589/81/34
+f 595/71/35 597/79/35 533/82/35
+f 583/83/36 519/76/36 517/84/36
+f 581/85/37 517/84/37 515/86/37
+f 601/63/362 603/87/362 539/88/362
+f 639/89/39 577/90/39 513/91/39
+f 637/73/166 639/89/166 575/92/166
+f 609/93/363 545/94/363 543/95/363
+f 603/87/42 605/96/42 541/97/42
+f 567/98/43 631/99/43 633/60/43
+f 613/100/167 615/101/167 551/102/167
+f 565/103/45 629/104/45 631/99/45
+f 563/105/46 627/106/46 629/104/46
+f 561/107/47 625/108/47 627/106/47
+f 621/109/48 623/110/48 559/111/48
+f 619/112/49 621/109/49 557/113/49
+f 617/114/50 619/112/50 555/115/50
+f 615/101/168 617/114/168 553/116/168
+f 579/117/52 515/86/52 513/91/52
+f 611/118/53 613/100/53 549/119/53
+f 611/118/54 547/120/54 545/94/54
+f 552/9/55 576/4/55 528/6/55
+f 576/37/56 575/121/56 513/38/56
+f 585/122/57 583/123/57 584/124/57
+f 600/125/58 599/126/58 597/127/58
+f 536/128/59 535/129/59 537/130/59
+f 598/131/60 597/127/60 595/132/60
+f 628/133/61 627/134/61 625/135/61
+f 530/136/62 529/3/62 531/137/62
+f 594/138/63 593/139/63 591/140/63
+f 525/141/64 527/2/64 528/1/64
+f 596/142/65 595/132/65 593/143/65
+f 521/144/66 523/145/66 524/146/66
+f 519/147/67 521/144/67 522/148/67
+f 538/149/68 537/130/68 539/150/68
+f 515/151/69 517/152/69 518/153/69
+f 513/38/70 515/151/70 516/154/70
+f 541/40/71 543/155/71 544/41/71
+f 544/41/72 543/155/72 545/43/72
+f 569/57/73 571/156/73 572/157/73
+f 559/111/74 623/110/74 625/108/74
+f 571/156/75 573/45/75 574/44/75
+f 607/158/76 543/95/76 541/97/76
+f 565/159/77 567/56/77 568/160/77
+f 563/161/78 565/159/78 566/162/78
+f 602/163/79 601/164/79 599/126/79
+f 630/165/80 629/166/80 627/134/80
+f 556/167/81 555/168/81 557/169/81
+f 554/170/82 553/171/82 555/168/82
+f 552/172/83 551/173/83 553/171/83
+f 550/174/84 549/175/84 551/173/84
+f 548/176/85 547/177/85 549/175/85
+f 546/42/86 545/43/86 547/177/86
+f 573/45/87 575/121/87 576/37/87
+f 577/47/20 639/178/20 640/48/20
+f 634/179/88 633/180/88 631/181/88
+f 632/182/89 631/181/89 629/166/89
+f 604/183/90 603/184/90 601/164/90
+f 559/185/91 561/186/91 562/187/91
+f 517/152/92 519/147/92 520/188/92
+f 532/189/93 531/137/93 533/190/93
+f 626/191/94 625/135/94 623/192/94
+f 591/140/95 589/193/95 590/194/95
+f 589/193/96 587/195/96 588/196/96
+f 587/195/97 585/122/97 586/197/97
+f 540/198/98 539/150/98 541/40/98
+f 583/123/99 581/199/99 582/200/99
+f 581/199/100 579/201/100 580/202/100
+f 580/202/101 579/201/101 577/47/101
+f 608/50/21 607/203/21 605/51/21
+f 609/53/102 607/203/102 608/50/102
+f 561/186/103 563/161/103 564/204/103
+f 638/54/104 637/55/104 635/205/104
+f 636/206/105 635/205/105 633/180/105
+f 604/183/106 606/49/106 605/51/106
+f 558/207/107 557/169/107 559/208/107
+f 534/209/108 533/190/108 535/129/108
+f 523/145/109 525/141/109 526/210/109
+f 623/192/110 621/211/110 622/212/110
+f 621/211/111 619/213/111 620/214/111
+f 619/213/112 617/215/112 618/216/112
+f 617/215/113 615/217/113 616/218/113
+f 615/217/114 613/219/114 614/220/114
+f 613/219/115 611/221/115 612/222/115
+f 611/221/116 609/53/116 610/52/116
+f 640/48/23 639/178/23 637/55/23
+f 530/136/117 528/1/117 529/3/117
+f 568/160/118 567/56/118 570/58/118
+f 571/223/119 569/59/119 635/61/119
+f 535/80/120 599/62/120 537/64/120
+f 527/77/121 591/65/121 529/67/121
+f 585/75/122 587/68/122 521/70/122
+f 529/67/123 593/66/123 531/72/123
+f 571/223/124 635/61/124 573/74/124
+f 583/83/125 585/75/125 519/76/125
+f 589/81/364 591/65/364 525/78/364
+f 533/82/127 597/79/127 535/80/127
+f 525/78/128 523/69/128 589/81/128
+f 531/72/129 595/71/129 533/82/129
+f 581/85/130 583/83/130 517/84/130
+f 579/117/365 581/85/365 515/86/365
+f 537/64/366 601/63/366 539/88/366
+f 575/92/133 639/89/133 513/91/133
+f 573/74/134 637/73/134 575/92/134
+f 607/158/135 609/93/135 543/95/135
+f 539/88/136 603/87/136 541/97/136
+f 569/59/137 567/98/137 633/60/137
+f 549/119/138 613/100/138 551/102/138
+f 567/98/139 565/103/139 631/99/139
+f 565/103/140 563/105/140 629/104/140
+f 563/105/141 561/107/141 627/106/141
+f 557/113/142 621/109/142 559/111/142
+f 555/115/143 619/112/143 557/113/143
+f 553/116/170 617/114/170 555/115/170
+f 551/102/145 615/101/145 553/116/145
+f 577/90/146 579/117/146 513/91/146
+f 547/120/171 611/118/171 549/119/171
+f 609/93/148 611/118/148 545/94/148
+f 544/8/5 552/9/5 528/6/5
+f 586/197/57 585/122/57 584/124/57
+f 598/131/58 600/125/58 597/127/58
+f 538/149/59 536/128/59 537/130/59
+f 596/142/149 598/131/149 595/132/149
+f 626/191/61 628/133/61 625/135/61
+f 532/189/62 530/136/62 531/137/62
+f 592/224/63 594/138/63 591/140/63
+f 526/210/64 525/141/64 528/1/64
+f 594/225/65 596/142/65 593/143/65
+f 522/148/66 521/144/66 524/146/66
+f 520/188/67 519/147/67 522/148/67
+f 540/198/68 538/149/68 539/150/68
+f 516/154/69 515/151/69 518/153/69
+f 514/36/150 513/38/150 516/154/150
+f 570/58/151 569/57/151 572/157/151
+f 561/107/152 559/111/152 625/108/152
+f 572/157/153 571/156/153 574/44/153
+f 605/96/172 607/158/172 541/97/172
+f 566/162/77 565/159/77 568/160/77
+f 564/204/155 563/161/155 566/162/155
+f 600/125/79 602/163/79 599/126/79
+f 628/133/80 630/165/80 627/134/80
+f 558/207/81 556/167/81 557/169/81
+f 556/167/82 554/170/82 555/168/82
+f 554/170/156 552/172/156 553/171/156
+f 552/172/84 550/174/84 551/173/84
+f 550/174/157 548/176/157 549/175/157
+f 548/176/158 546/42/158 547/177/158
+f 632/182/88 634/179/88 631/181/88
+f 630/165/89 632/182/89 629/166/89
+f 602/163/90 604/183/90 601/164/90
+f 560/226/91 559/185/91 562/187/91
+f 518/153/92 517/152/92 520/188/92
+f 534/209/93 532/189/93 533/190/93
+f 624/227/94 626/191/94 623/192/94
+f 592/224/95 591/140/95 590/194/95
+f 590/194/96 589/193/96 588/196/96
+f 588/196/97 587/195/97 586/197/97
+f 542/39/159 540/198/159 541/40/159
+f 584/124/99 583/123/99 582/200/99
+f 582/200/100 581/199/100 580/202/100
+f 578/46/160 580/202/160 577/47/160
+f 562/187/161 561/186/161 564/204/161
+f 636/206/104 638/54/104 635/205/104
+f 634/179/105 636/206/105 633/180/105
+f 603/184/162 604/183/162 605/51/162
+f 560/228/107 558/207/107 559/208/107
+f 536/128/108 534/209/108 535/129/108
+f 524/146/109 523/145/109 526/210/109
+f 624/227/110 623/192/110 622/212/110
+f 622/212/111 621/211/111 620/214/111
+f 620/214/112 619/213/112 618/216/112
+f 618/216/113 617/215/113 616/218/113
+f 616/218/114 615/217/114 614/220/114
+f 614/220/115 613/219/115 612/222/115
+f 612/222/116 611/221/116 610/52/116
+g HSLampen_Cube.000_Shunt
+usemtl Shunt
+f 703/46/333 701/202/333 704/47/333
+f 741/15/194 739/28/194 737/8/194
+f 751/136/258 752/3/258 750/137/258
+f 751/136/335 753/1/335 752/3/335
+f 645/206/177 646/205/177 648/180/177
+f 718/105/316 720/107/316 654/106/316
+f 708/45/239 706/121/239 705/37/239
+f 767/36/102 705/37/102 768/38/102
+f 753/6/203 759/13/203 757/23/203
+f 753/1/207 754/2/207 752/3/207
+f 672/53/181 674/203/181 673/50/181
+f 674/158/287 738/95/287 740/97/287
+f 708/74/309 644/73/309 706/92/309
+f 722/185/277 720/186/277 719/187/277
+f 722/111/221 658/110/221 656/108/221
+f 680/63/338 678/87/338 742/88/338
+f 694/195/273 696/122/273 695/197/273
+f 718/161/216 716/159/216 715/162/216
+f 693/196/273 694/195/273 695/197/273
+f 750/72/304 686/71/304 748/82/304
+f 715/162/252 716/159/252 713/160/252
+f 743/149/243 745/128/243 744/130/243
+f 690/140/249 692/193/249 691/194/249
+f 662/112/259 660/109/259 724/113/259
+f 668/219/237 670/221/237 669/222/237
+f 712/57/229 710/156/229 709/157/229
+f 667/220/237 668/219/237 669/222/237
+f 649/182/247 647/179/247 650/181/247
+f 672/93/323 670/118/323 736/94/323
+f 698/83/279 762/76/279 764/84/279
+f 705/4/257 765/5/257 753/6/257
+f 731/174/230 733/176/230 732/175/230
+f 714/98/263 650/99/263 648/60/263
+f 752/67/298 688/66/298 750/72/298
+f 763/153/191 764/152/191 761/188/191
+f 653/133/285 651/165/285 654/134/285
+f 720/107/326 722/111/326 656/108/326
+f 685/142/235 686/132/235 688/143/235
+f 692/193/187 694/195/187 693/196/187
+f 710/223/299 646/61/299 708/74/299
+f 755/210/193 756/141/193 753/1/193
+f 735/42/286 736/43/286 734/177/286
+f 688/66/231 686/71/231 750/72/231
+f 721/226/277 722/185/277 719/187/277
+f 700/199/198 702/201/198 701/202/198
+f 696/75/297 694/68/297 760/70/297
+f 716/159/252 714/56/252 713/160/252
+f 712/59/312 714/98/312 648/60/312
+f 747/209/331 749/189/331 748/190/331
+f 685/142/290 683/131/290 686/132/290
+f 734/120/322 670/118/322 732/119/322
+f 644/73/264 642/89/264 706/92/264
+f 727/170/281 728/171/281 726/168/281
+f 723/207/232 725/167/232 724/169/232
+f 687/138/190 688/139/190 690/140/190
+f 706/92/308 642/89/308 768/91/308
+f 729/172/255 730/173/255 728/171/255
+f 643/54/268 644/55/268 646/205/268
+f 724/113/317 660/109/317 722/111/317
+f 753/6/241 751/25/241 749/7/241
+f 683/131/290 684/127/290 686/132/290
+f 655/191/289 653/133/289 656/135/289
+f 684/79/270 682/62/270 746/80/270
+f 749/7/367 747/26/367 745/14/367
+f 765/5/368 763/22/368 761/12/368
+f 666/217/214 668/219/214 667/220/214
+f 720/107/202 656/108/202 654/106/202
+f 694/68/369 758/69/369 760/70/369
+f 641/48/234 642/178/234 644/55/234
+f 716/103/238 652/104/238 650/99/238
+f 642/89/242 704/90/242 768/91/242
+f 665/218/214 666/217/214 667/220/214
+f 725/167/232 726/168/232 724/169/232
+f 765/154/248 766/151/248 763/153/248
+f 767/36/324 768/38/324 765/154/324
+f 699/200/198 700/199/198 701/202/198
+f 744/64/343 680/63/343 742/88/343
+f 661/214/274 662/213/274 663/216/274
+f 726/115/318 662/112/318 724/113/318
+f 745/128/243 746/129/243 744/130/243
+f 729/9/250 727/31/250 725/17/250
+f 700/85/305 698/83/305 764/84/305
+f 651/165/183 649/182/183 652/166/183
+f 712/59/345 648/60/345 646/61/345
+f 721/10/196 719/32/196 717/11/196
+f 709/20/179 707/35/179 705/4/179
+f 645/206/268 643/54/268 646/205/268
+f 749/189/219 750/137/219 748/190/219
+f 678/87/278 676/96/278 740/97/278
+f 754/77/296 690/65/296 752/67/296
+f 664/215/246 666/217/246 665/218/246
+f 743/149/276 744/130/276 742/150/276
+f 664/114/233 662/112/233 726/115/233
+f 701/202/253 702/201/253 704/47/253
+f 758/69/280 694/68/280 692/81/280
+f 762/147/206 760/144/206 759/148/206
+f 719/187/226 720/186/226 717/204/226
+f 759/148/245 760/144/245 757/146/245
+f 660/211/266 662/213/266 661/214/266
+f 745/128/220 747/209/220 746/129/220
+f 757/146/223 758/145/223 755/210/223
+f 705/4/175 729/9/175 721/10/175
+f 760/144/245 758/145/245 757/146/245
+f 643/54/234 641/48/234 644/55/234
+f 690/65/211 688/66/211 752/67/211
+f 739/39/192 740/40/192 737/41/192
+f 723/207/227 724/169/227 722/208/227
+f 729/9/370 735/16/370 733/29/370
+f 660/109/371 658/110/371 722/111/371
+f 728/116/346 664/114/346 726/115/346
+f 695/197/188 696/122/188 697/124/188
+f 737/41/205 738/155/205 736/43/205
+f 647/179/177 645/206/177 648/180/177
+f 655/191/209 656/135/209 658/192/209
+f 758/145/223 756/141/223 755/210/223
+f 737/8/194 749/7/194 745/14/194
+f 713/160/293 714/56/293 711/58/293
+f 732/119/313 668/100/313 730/102/313
+f 705/4/260 767/21/260 765/5/260
+f 748/82/302 684/79/302 746/80/302
+f 739/39/332 741/198/332 740/40/332
+f 729/172/212 731/174/212 730/173/212
+f 679/163/272 680/164/272 682/126/272
+f 713/19/372 709/20/372 705/4/372
+f 729/9/284 705/4/284 753/6/284
+f 761/188/206 762/147/206 759/148/206
+f 733/29/195 731/30/195 729/9/195
+f 710/223/294 712/59/294 646/61/294
+f 692/81/301 690/65/301 756/78/301
+f 657/227/224 658/192/224 659/212/224
+f 729/9/186 725/17/186 723/18/186
+f 704/47/213 642/178/213 641/48/213
+f 717/204/216 718/161/216 715/162/216
+f 741/198/276 743/149/276 742/150/276
+f 740/40/244 738/155/244 737/41/244
+f 753/6/194 749/7/194 737/8/194
+f 730/102/320 666/101/320 728/116/320
+f 709/157/327 710/156/327 707/44/327
+f 733/176/330 735/42/330 734/177/330
+f 687/225/235 685/142/235 688/143/235
+f 703/46/213 704/47/213 641/48/213
+f 689/224/249 690/140/249 691/194/249
+f 681/125/272 679/163/272 682/126/272
+f 737/8/251 729/9/251 753/6/251
+f 725/167/281 727/170/281 726/168/281
+f 749/189/258 751/136/258 750/137/258
+f 697/124/267 698/123/267 699/200/267
+f 721/228/227 723/207/227 722/208/227
+f 714/98/314 716/103/314 650/99/314
+f 669/222/262 670/221/262 671/52/262
+f 671/52/236 672/53/236 673/50/236
+f 698/83/300 696/75/300 762/76/300
+f 674/158/310 672/93/310 738/95/310
+f 705/4/175 717/11/175 713/19/175
+f 651/165/285 652/166/285 654/134/285
+f 647/179/247 648/180/247 650/181/247
+f 729/9/175 723/18/175 721/10/175
+f 677/183/256 675/49/256 676/51/256
+f 698/123/267 700/199/267 699/200/267
+f 675/49/269 673/50/269 676/51/269
+f 662/213/274 664/215/274 663/216/274
+f 714/56/282 712/57/282 711/58/282
+f 663/216/246 664/215/246 665/218/246
+f 653/133/289 654/134/289 656/135/289
+f 746/80/295 682/62/295 744/64/295
+f 711/58/325 712/57/325 709/157/325
+f 658/192/224 660/211/224 659/212/224
+f 731/174/212 732/175/212 730/173/212
+f 716/103/349 718/105/349 652/104/349
+f 761/12/194 759/13/194 753/6/194
+f 741/198/185 742/150/185 740/40/185
+f 689/224/190 687/138/190 690/140/190
+f 682/62/217 680/63/217 744/64/217
+f 676/96/328 674/158/328 740/97/328
+f 735/42/215 737/41/215 736/43/215
+f 679/163/182 677/183/182 680/164/182
+f 657/227/209 655/191/209 658/192/209
+f 742/88/311 678/87/311 740/97/311
+f 678/184/334 677/183/334 676/51/334
+f 659/212/266 660/211/266 661/214/266
+f 753/6/194 765/5/194 761/12/194
+f 702/117/261 766/86/261 768/91/261
+f 713/19/373 711/34/373 709/20/373
+f 756/78/303 758/69/303 692/81/303
+f 766/151/248 764/152/248 763/153/248
+f 737/8/251 735/16/251 729/9/251
+f 672/93/210 736/94/210 738/95/210
+f 649/182/183 650/181/183 652/166/183
+f 673/50/269 674/203/269 676/51/269
+f 700/85/271 764/84/271 766/86/271
+f 670/118/374 734/120/374 736/94/374
+f 670/118/283 668/100/283 732/119/283
+f 768/38/288 766/151/288 765/154/288
+f 690/65/291 754/77/291 756/78/291
+f 710/156/180 708/45/180 707/44/180
+f 677/183/182 678/184/182 680/164/182
+f 646/61/208 644/73/208 708/74/208
+f 670/221/262 672/53/262 671/52/262
+f 691/194/187 692/193/187 693/196/187
+f 683/131/265 681/125/265 684/127/265
+f 696/122/188 698/123/188 697/124/188
+f 696/75/218 760/70/218 762/76/218
+f 747/209/220 748/190/220 746/129/220
+f 668/100/222 666/101/222 730/102/222
+f 702/117/306 700/85/306 766/86/306
+f 720/186/226 718/161/226 717/204/226
+f 745/14/228 743/27/228 741/15/228
+f 733/176/230 734/177/230 732/175/230
+f 718/105/254 654/106/254 652/104/254
+f 721/10/175 717/11/175 705/4/175
+f 745/14/178 741/15/178 737/8/178
+f 757/23/194 755/24/194 753/6/194
+f 681/125/265 682/126/265 684/127/265
+f 704/90/321 702/117/321 768/91/321
+f 727/170/329 729/172/329 728/171/329
+f 666/101/359 664/114/359 728/116/359
+f 764/152/191 762/147/191 761/188/191
+f 756/141/193 754/2/193 753/1/193
+f 717/11/195 715/33/195 713/19/195
+f 707/44/197 708/45/197 705/37/197
+f 705/37/199 706/121/199 768/38/199
+f 686/71/201 684/79/201 748/82/201
diff --git a/advtrains_signals_ks/models/advtrains_signals_ks_main_smr0.obj b/advtrains_signals_ks/models/advtrains_signals_ks_main_smr0.obj
new file mode 100644
index 0000000..eb6d468
--- /dev/null
+++ b/advtrains_signals_ks/models/advtrains_signals_ks_main_smr0.obj
@@ -0,0 +1,4280 @@
+# Blender v2.79 (sub 0) OBJ File: 'signal_ks_singlemesh_final.blend'
+# www.blender.org
+mtllib advtrains_signals_ks_main_smr0.mtl
+o KsHauptsignal_Plane.006
+v 0.028888 -0.503378 -0.439591
+v -0.028274 -0.503378 -0.439631
+v -0.028235 -0.503378 -0.496794
+v 0.028928 -0.503378 -0.496754
+v 0.028809 -0.503378 -0.325555
+v 0.028769 -0.503378 -0.268392
+v -0.028394 -0.503378 -0.268432
+v -0.028354 -0.503378 -0.325595
+v 0.028888 -0.299114 -0.439591
+v 0.028888 -0.172218 -0.439591
+v -0.028274 -0.172218 -0.439631
+v -0.028274 -0.299114 -0.439631
+v -0.028235 -0.299114 -0.496794
+v -0.028235 -0.172218 -0.496794
+v 0.028928 -0.172218 -0.496754
+v 0.028928 -0.299114 -0.496754
+v 0.028809 -0.299114 -0.325555
+v 0.028809 -0.172218 -0.325555
+v 0.028769 -0.172218 -0.268392
+v 0.028769 -0.299114 -0.268392
+v -0.028394 -0.299114 -0.268432
+v -0.028394 -0.172218 -0.268432
+v -0.028354 -0.172218 -0.325595
+v -0.028354 -0.299114 -0.325595
+v -0.135201 0.243339 -0.586523
+v -0.135219 0.242789 -0.559563
+v -0.132013 0.253858 -0.639022
+v -0.132068 0.253308 -0.559514
+v -0.126870 0.263561 -0.638975
+v -0.126926 0.263011 -0.559467
+v -0.119933 0.272074 -0.638933
+v -0.119989 0.271524 -0.559424
+v -0.111469 0.279070 -0.638896
+v -0.111524 0.278520 -0.559387
+v -0.101802 0.284280 -0.638865
+v -0.101858 0.283730 -0.559357
+v -0.091305 0.287504 -0.638844
+v -0.091361 0.286954 -0.559335
+v -0.080380 0.288619 -0.638831
+v -0.080436 0.288069 -0.559323
+v -0.069448 0.287581 -0.638828
+v -0.069504 0.287030 -0.559320
+v -0.058929 0.284430 -0.638835
+v -0.058984 0.283880 -0.559327
+v -0.049226 0.279287 -0.638851
+v -0.049281 0.278737 -0.559343
+v -0.040713 0.272350 -0.638876
+v -0.040768 0.271800 -0.559368
+v -0.033717 0.263886 -0.638909
+v -0.033772 0.263336 -0.559401
+v -0.028506 0.254219 -0.638948
+v -0.028562 0.253669 -0.559440
+v -0.025318 0.243722 -0.586445
+v -0.025337 0.243172 -0.559485
+v -0.024183 0.232316 -0.586441
+v -0.024223 0.232252 -0.559532
+v -0.025274 0.221688 -0.586283
+v -0.025260 0.221322 -0.559578
+v -0.028394 0.211344 -0.586324
+v -0.028411 0.210797 -0.559621
+v -0.033537 0.201642 -0.586366
+v -0.033554 0.201094 -0.559663
+v -0.040474 0.193129 -0.586405
+v -0.040491 0.192581 -0.559701
+v -0.048938 0.186134 -0.586439
+v -0.048955 0.185585 -0.559735
+v -0.058604 0.180924 -0.586466
+v -0.058622 0.180374 -0.559762
+v -0.069101 0.177700 -0.586486
+v -0.069119 0.177150 -0.559782
+v -0.080026 0.176585 -0.586498
+v -0.080044 0.176035 -0.559794
+v -0.090958 0.177623 -0.586502
+v -0.090977 0.177073 -0.559798
+v -0.101477 0.180774 -0.586496
+v -0.101497 0.180224 -0.559793
+v -0.111180 0.185916 -0.586483
+v -0.111200 0.185367 -0.559779
+v -0.119693 0.192853 -0.586461
+v -0.119713 0.192304 -0.559758
+v -0.126689 0.201317 -0.586433
+v -0.126709 0.200769 -0.559729
+v -0.131899 0.210983 -0.586398
+v -0.131919 0.210436 -0.559694
+v -0.135091 0.221305 -0.586361
+v -0.135144 0.220938 -0.559656
+v -0.136256 0.231924 -0.586521
+v -0.136258 0.231861 -0.559612
+v -0.137672 0.243790 -0.586520
+v -0.137717 0.243271 -0.523031
+v -0.134343 0.254782 -0.639014
+v -0.134424 0.254264 -0.522977
+v -0.128969 0.264921 -0.638963
+v -0.129050 0.264402 -0.522926
+v -0.121720 0.273816 -0.638916
+v -0.121801 0.273298 -0.522879
+v -0.112875 0.281127 -0.638876
+v -0.112956 0.280609 -0.522839
+v -0.102774 0.286572 -0.638843
+v -0.102855 0.286053 -0.522806
+v -0.091805 0.289941 -0.638820
+v -0.091886 0.289423 -0.522782
+v -0.080389 0.291105 -0.638806
+v -0.080470 0.290587 -0.522769
+v -0.068965 0.290021 -0.638803
+v -0.069047 0.289502 -0.522766
+v -0.057973 0.286728 -0.638811
+v -0.058054 0.286210 -0.522774
+v -0.047834 0.281354 -0.638829
+v -0.047915 0.280836 -0.522792
+v -0.038938 0.274105 -0.638857
+v -0.039019 0.273587 -0.522820
+v -0.031628 0.265261 -0.638893
+v -0.031709 0.264742 -0.522856
+v -0.026183 0.255160 -0.638937
+v -0.026264 0.254641 -0.522900
+v -0.022850 0.244190 -0.586438
+v -0.022895 0.243672 -0.522949
+v -0.021686 0.232775 -0.586437
+v -0.021730 0.232256 -0.523002
+v -0.022771 0.221351 -0.586289
+v -0.022815 0.220833 -0.523056
+v -0.026063 0.210359 -0.586343
+v -0.026107 0.209840 -0.523110
+v -0.031437 0.200220 -0.586394
+v -0.031481 0.199702 -0.523162
+v -0.038686 0.191324 -0.586441
+v -0.038730 0.190806 -0.523209
+v -0.047531 0.184013 -0.586481
+v -0.047575 0.183495 -0.523249
+v -0.057632 0.178569 -0.586514
+v -0.057676 0.178051 -0.523282
+v -0.068601 0.175200 -0.586538
+v -0.068645 0.174682 -0.523305
+v -0.080017 0.174035 -0.586551
+v -0.080061 0.173517 -0.523319
+v -0.091441 0.175120 -0.586554
+v -0.091485 0.174602 -0.523322
+v -0.102433 0.178413 -0.586546
+v -0.102477 0.177894 -0.523314
+v -0.112572 0.183787 -0.586528
+v -0.112616 0.183268 -0.523296
+v -0.121468 0.191035 -0.586500
+v -0.121512 0.190517 -0.523268
+v -0.128778 0.199880 -0.586464
+v -0.128823 0.199362 -0.523231
+v -0.134223 0.209981 -0.586420
+v -0.134267 0.209463 -0.523188
+v -0.137592 0.220950 -0.586371
+v -0.137637 0.220432 -0.523138
+v -0.138757 0.232366 -0.586521
+v -0.138801 0.231848 -0.523086
+v 0.097038 0.506097 -0.586361
+v 0.097019 0.505869 -0.559401
+v 0.098378 0.510448 -0.638861
+v 0.098323 0.510220 -0.559353
+v 0.100505 0.514461 -0.638817
+v 0.100450 0.514233 -0.559308
+v 0.103374 0.517982 -0.638777
+v 0.103319 0.517755 -0.559268
+v 0.106875 0.520876 -0.638743
+v 0.106820 0.520648 -0.559235
+v 0.110874 0.523031 -0.638717
+v 0.110818 0.522803 -0.559209
+v 0.115216 0.524364 -0.638700
+v 0.115160 0.524137 -0.559191
+v 0.119734 0.524825 -0.638692
+v 0.119679 0.524598 -0.559183
+v 0.124256 0.524396 -0.638693
+v 0.124201 0.524168 -0.559185
+v 0.128607 0.523093 -0.638704
+v 0.128552 0.522865 -0.559196
+v 0.132620 0.520966 -0.638724
+v 0.132565 0.520738 -0.559216
+v 0.136141 0.518097 -0.638753
+v 0.136086 0.517869 -0.559244
+v 0.139035 0.514596 -0.638788
+v 0.138980 0.514368 -0.559280
+v 0.141190 0.510597 -0.638830
+v 0.141135 0.510370 -0.559322
+v 0.142487 0.506255 -0.586328
+v 0.142469 0.506028 -0.559367
+v 0.142957 0.501537 -0.586324
+v 0.142930 0.501511 -0.559416
+v 0.142506 0.497142 -0.586166
+v 0.142501 0.496990 -0.559461
+v 0.141215 0.492863 -0.586206
+v 0.141197 0.492637 -0.559502
+v 0.139088 0.488850 -0.586246
+v 0.139070 0.488624 -0.559542
+v 0.136219 0.485329 -0.586282
+v 0.136201 0.485102 -0.559578
+v 0.132718 0.482436 -0.586312
+v 0.132700 0.482209 -0.559608
+v 0.128720 0.480281 -0.586335
+v 0.128702 0.480054 -0.559632
+v 0.124378 0.478947 -0.586351
+v 0.124360 0.478720 -0.559647
+v 0.119860 0.478486 -0.586358
+v 0.119841 0.478259 -0.559655
+v 0.115338 0.478915 -0.586358
+v 0.115319 0.478688 -0.559654
+v 0.110987 0.480219 -0.586348
+v 0.110968 0.479992 -0.559645
+v 0.106974 0.482346 -0.586331
+v 0.106954 0.482119 -0.559627
+v 0.103453 0.485215 -0.586305
+v 0.103433 0.484988 -0.559602
+v 0.100559 0.488716 -0.586274
+v 0.100539 0.488489 -0.559570
+v 0.098404 0.492714 -0.586237
+v 0.098384 0.492488 -0.559533
+v 0.097083 0.496983 -0.586199
+v 0.097050 0.496832 -0.559494
+v 0.096602 0.501376 -0.586358
+v 0.096590 0.501349 -0.559449
+v 0.096016 0.506283 -0.586357
+v 0.095972 0.506069 -0.522868
+v 0.097414 0.510830 -0.638853
+v 0.097333 0.510615 -0.522815
+v 0.099637 0.515023 -0.638803
+v 0.099556 0.514809 -0.522766
+v 0.102635 0.518703 -0.638760
+v 0.102554 0.518489 -0.522722
+v 0.106294 0.521727 -0.638723
+v 0.106213 0.521512 -0.522686
+v 0.110472 0.523979 -0.638694
+v 0.110391 0.523764 -0.522657
+v 0.115009 0.525372 -0.638675
+v 0.114928 0.525158 -0.522638
+v 0.119731 0.525854 -0.638666
+v 0.119650 0.525640 -0.522629
+v 0.124456 0.525405 -0.638668
+v 0.124375 0.525191 -0.522631
+v 0.129002 0.524043 -0.638681
+v 0.128921 0.523829 -0.522643
+v 0.133196 0.521821 -0.638703
+v 0.133115 0.521606 -0.522666
+v 0.136876 0.518822 -0.638734
+v 0.136795 0.518608 -0.522697
+v 0.139899 0.515164 -0.638774
+v 0.139818 0.514950 -0.522737
+v 0.142151 0.510986 -0.638820
+v 0.142070 0.510772 -0.522783
+v 0.143508 0.506449 -0.586322
+v 0.143464 0.506235 -0.522833
+v 0.143990 0.501727 -0.586322
+v 0.143946 0.501513 -0.522886
+v 0.143541 0.497002 -0.586173
+v 0.143497 0.496788 -0.522940
+v 0.142179 0.492455 -0.586225
+v 0.142135 0.492241 -0.522993
+v 0.139957 0.488262 -0.586274
+v 0.139912 0.488048 -0.523042
+v 0.136958 0.484583 -0.586318
+v 0.136914 0.484368 -0.523086
+v 0.133300 0.481559 -0.586355
+v 0.133256 0.481344 -0.523123
+v 0.129122 0.479307 -0.586384
+v 0.129078 0.479092 -0.523151
+v 0.124585 0.477913 -0.586403
+v 0.124541 0.477699 -0.523170
+v 0.119863 0.477432 -0.586412
+v 0.119819 0.477217 -0.523179
+v 0.115138 0.477880 -0.586410
+v 0.115094 0.477666 -0.523177
+v 0.110591 0.479242 -0.586397
+v 0.110547 0.479028 -0.523165
+v 0.106398 0.481465 -0.586375
+v 0.106354 0.481251 -0.523143
+v 0.102718 0.484463 -0.586344
+v 0.102674 0.484249 -0.523111
+v 0.099694 0.488121 -0.586304
+v 0.099650 0.487907 -0.523072
+v 0.097442 0.492299 -0.586258
+v 0.097398 0.492085 -0.523026
+v 0.096049 0.496836 -0.586208
+v 0.096005 0.496622 -0.522975
+v 0.095567 0.501558 -0.586357
+v 0.095523 0.501344 -0.522922
+v 0.064799 0.243339 -0.586384
+v 0.064781 0.242789 -0.559423
+v 0.067987 0.253858 -0.638883
+v 0.067932 0.253308 -0.559374
+v 0.073130 0.263561 -0.638836
+v 0.073074 0.263011 -0.559327
+v 0.080067 0.272074 -0.638793
+v 0.080011 0.271524 -0.559285
+v 0.088531 0.279070 -0.638756
+v 0.088476 0.278520 -0.559248
+v 0.098198 0.284280 -0.638726
+v 0.098142 0.283730 -0.559217
+v 0.108695 0.287504 -0.638704
+v 0.108639 0.286954 -0.559196
+v 0.119620 0.288619 -0.638692
+v 0.119564 0.288069 -0.559183
+v 0.130552 0.287581 -0.638689
+v 0.130496 0.287030 -0.559180
+v 0.141071 0.284430 -0.638695
+v 0.141016 0.283880 -0.559187
+v 0.150774 0.279287 -0.638712
+v 0.150719 0.278737 -0.559203
+v 0.159287 0.272350 -0.638736
+v 0.159232 0.271800 -0.559228
+v 0.166283 0.263886 -0.638769
+v 0.166228 0.263336 -0.559261
+v 0.171494 0.254219 -0.638809
+v 0.171438 0.253669 -0.559300
+v 0.174681 0.243722 -0.586305
+v 0.174663 0.243172 -0.559345
+v 0.175817 0.232316 -0.586301
+v 0.175777 0.232252 -0.559393
+v 0.174726 0.221688 -0.586144
+v 0.174740 0.221322 -0.559438
+v 0.171606 0.211344 -0.586185
+v 0.171589 0.210797 -0.559481
+v 0.166463 0.201642 -0.586227
+v 0.166446 0.201094 -0.559523
+v 0.159526 0.193129 -0.586265
+v 0.159509 0.192581 -0.559562
+v 0.151062 0.186134 -0.586299
+v 0.151045 0.185585 -0.559595
+v 0.141396 0.180924 -0.586326
+v 0.141378 0.180374 -0.559623
+v 0.130899 0.177700 -0.586346
+v 0.130881 0.177150 -0.559643
+v 0.119974 0.176585 -0.586358
+v 0.119956 0.176035 -0.559655
+v 0.109042 0.177623 -0.586362
+v 0.109023 0.177073 -0.559659
+v 0.098523 0.180774 -0.586357
+v 0.098503 0.180224 -0.559653
+v 0.088820 0.185916 -0.586343
+v 0.088800 0.185368 -0.559640
+v 0.080307 0.192853 -0.586322
+v 0.080287 0.192304 -0.559618
+v 0.073311 0.201317 -0.586293
+v 0.073291 0.200769 -0.559589
+v 0.068101 0.210983 -0.586258
+v 0.068081 0.210436 -0.559555
+v 0.064909 0.221305 -0.586222
+v 0.064856 0.220938 -0.559516
+v 0.063744 0.231924 -0.586381
+v 0.063742 0.231861 -0.559472
+v 0.062328 0.243790 -0.586381
+v 0.062283 0.243271 -0.522892
+v 0.065657 0.254782 -0.638875
+v 0.065576 0.254264 -0.522838
+v 0.071031 0.264921 -0.638823
+v 0.070950 0.264402 -0.522786
+v 0.078280 0.273816 -0.638777
+v 0.078199 0.273298 -0.522739
+v 0.087125 0.281127 -0.638736
+v 0.087044 0.280609 -0.522699
+v 0.097226 0.286572 -0.638704
+v 0.097145 0.286053 -0.522666
+v 0.108195 0.289941 -0.638680
+v 0.108114 0.289423 -0.522643
+v 0.119611 0.291105 -0.638667
+v 0.119530 0.290587 -0.522629
+v 0.131035 0.290021 -0.638664
+v 0.130953 0.289502 -0.522627
+v 0.142027 0.286728 -0.638672
+v 0.141946 0.286210 -0.522634
+v 0.152166 0.281354 -0.638690
+v 0.152085 0.280836 -0.522652
+v 0.161062 0.274105 -0.638717
+v 0.160981 0.273587 -0.522680
+v 0.168372 0.265261 -0.638754
+v 0.168291 0.264742 -0.522717
+v 0.173817 0.255160 -0.638798
+v 0.173736 0.254641 -0.522760
+v 0.177150 0.244190 -0.586299
+v 0.177105 0.243672 -0.522810
+v 0.178314 0.232775 -0.586298
+v 0.178270 0.232256 -0.522862
+v 0.177229 0.221351 -0.586149
+v 0.177185 0.220833 -0.522917
+v 0.173937 0.210359 -0.586203
+v 0.173893 0.209840 -0.522971
+v 0.168563 0.200220 -0.586254
+v 0.168519 0.199702 -0.523022
+v 0.161314 0.191324 -0.586301
+v 0.161270 0.190806 -0.523069
+v 0.152469 0.184014 -0.586342
+v 0.152425 0.183495 -0.523109
+v 0.142368 0.178569 -0.586374
+v 0.142324 0.178051 -0.523142
+v 0.131399 0.175200 -0.586398
+v 0.131355 0.174682 -0.523166
+v 0.119983 0.174035 -0.586411
+v 0.119939 0.173517 -0.523179
+v 0.108559 0.175120 -0.586414
+v 0.108515 0.174602 -0.523182
+v 0.097567 0.178413 -0.586406
+v 0.097523 0.177894 -0.523174
+v 0.087428 0.183787 -0.586388
+v 0.087384 0.183268 -0.523156
+v 0.078532 0.191035 -0.586360
+v 0.078488 0.190517 -0.523128
+v 0.071222 0.199880 -0.586324
+v 0.071177 0.199362 -0.523092
+v 0.065777 0.209981 -0.586280
+v 0.065733 0.209463 -0.523048
+v 0.062408 0.220950 -0.586231
+v 0.062363 0.220432 -0.522999
+v 0.061243 0.232366 -0.586381
+v 0.061199 0.231848 -0.522946
+v 0.095523 -0.005892 -0.522922
+v 0.095567 -0.005677 -0.586357
+v 0.096005 -0.010613 -0.522975
+v 0.096049 -0.010399 -0.586208
+v 0.097398 -0.015150 -0.523026
+v 0.097442 -0.014936 -0.586258
+v 0.099650 -0.019328 -0.523072
+v 0.099694 -0.019114 -0.586304
+v 0.102674 -0.022987 -0.523111
+v 0.102718 -0.022772 -0.586344
+v 0.106354 -0.025985 -0.523143
+v 0.106398 -0.025771 -0.586375
+v 0.110547 -0.028208 -0.523165
+v 0.110591 -0.027993 -0.586397
+v 0.115094 -0.029570 -0.523177
+v 0.115138 -0.029355 -0.586410
+v 0.119819 -0.030018 -0.523179
+v 0.119863 -0.029804 -0.586412
+v 0.124541 -0.029537 -0.523170
+v 0.124585 -0.029322 -0.586403
+v 0.129078 -0.028143 -0.523151
+v 0.129122 -0.027929 -0.586384
+v 0.133256 -0.025891 -0.523123
+v 0.133300 -0.025677 -0.586355
+v 0.136914 -0.022867 -0.523086
+v 0.136958 -0.022653 -0.586318
+v 0.139912 -0.019188 -0.523042
+v 0.139957 -0.018973 -0.586274
+v 0.142135 -0.014994 -0.522993
+v 0.142179 -0.014780 -0.586225
+v 0.143497 -0.010448 -0.522940
+v 0.143541 -0.010233 -0.586173
+v 0.143946 -0.005723 -0.522886
+v 0.143990 -0.005508 -0.586322
+v 0.143464 -0.001001 -0.522833
+v 0.143508 -0.000787 -0.586322
+v 0.142070 0.003536 -0.522783
+v 0.142151 0.003751 -0.638820
+v 0.139818 0.007714 -0.522737
+v 0.139899 0.007929 -0.638774
+v 0.136795 0.011373 -0.522697
+v 0.136876 0.011587 -0.638734
+v 0.133115 0.014371 -0.522666
+v 0.133196 0.014585 -0.638703
+v 0.128921 0.016593 -0.522643
+v 0.129002 0.016808 -0.638681
+v 0.124375 0.017955 -0.522631
+v 0.124456 0.018170 -0.638668
+v 0.119650 0.018404 -0.522629
+v 0.119731 0.018618 -0.638666
+v 0.114928 0.017922 -0.522638
+v 0.115009 0.018137 -0.638675
+v 0.110391 0.016529 -0.522657
+v 0.110472 0.016743 -0.638694
+v 0.106213 0.014277 -0.522686
+v 0.106294 0.014491 -0.638723
+v 0.102554 0.011253 -0.522722
+v 0.102635 0.011467 -0.638760
+v 0.099556 0.007574 -0.522766
+v 0.099637 0.007788 -0.638803
+v 0.097333 0.003380 -0.522815
+v 0.097414 0.003594 -0.638853
+v 0.095972 -0.001167 -0.522868
+v 0.096016 -0.000952 -0.586357
+v 0.096590 -0.005886 -0.559449
+v 0.096602 -0.005860 -0.586358
+v 0.097050 -0.010404 -0.559494
+v 0.097083 -0.010252 -0.586199
+v 0.098384 -0.014748 -0.559533
+v 0.098404 -0.014522 -0.586237
+v 0.100539 -0.018746 -0.559570
+v 0.100559 -0.018520 -0.586274
+v 0.103433 -0.022247 -0.559602
+v 0.103453 -0.022021 -0.586305
+v 0.106954 -0.025117 -0.559627
+v 0.106974 -0.024890 -0.586331
+v 0.110968 -0.027244 -0.559645
+v 0.110987 -0.027017 -0.586348
+v 0.115319 -0.028547 -0.559654
+v 0.115338 -0.028320 -0.586358
+v 0.119841 -0.028977 -0.559655
+v 0.119860 -0.028749 -0.586358
+v 0.124360 -0.028516 -0.559647
+v 0.124378 -0.028288 -0.586351
+v 0.128702 -0.027182 -0.559632
+v 0.128720 -0.026955 -0.586335
+v 0.132700 -0.025027 -0.559608
+v 0.132718 -0.024800 -0.586312
+v 0.136201 -0.022133 -0.559578
+v 0.136219 -0.021906 -0.586282
+v 0.139070 -0.018612 -0.559542
+v 0.139088 -0.018385 -0.586246
+v 0.141197 -0.014598 -0.559502
+v 0.141215 -0.014372 -0.586206
+v 0.142501 -0.010245 -0.559461
+v 0.142506 -0.010094 -0.586166
+v 0.142930 -0.005724 -0.559416
+v 0.142957 -0.005698 -0.586324
+v 0.142469 -0.001208 -0.559367
+v 0.142487 -0.000980 -0.586328
+v 0.141135 0.003134 -0.559322
+v 0.141190 0.003362 -0.638830
+v 0.138980 0.007132 -0.559280
+v 0.139035 0.007360 -0.638788
+v 0.136086 0.010633 -0.559244
+v 0.136141 0.010861 -0.638753
+v 0.132565 0.013503 -0.559216
+v 0.132620 0.013730 -0.638724
+v 0.128552 0.015630 -0.559196
+v 0.128607 0.015857 -0.638704
+v 0.124201 0.016933 -0.559185
+v 0.124256 0.017161 -0.638693
+v 0.119679 0.017362 -0.559183
+v 0.119734 0.017590 -0.638692
+v 0.115160 0.016901 -0.559191
+v 0.115216 0.017129 -0.638700
+v 0.110818 0.015568 -0.559209
+v 0.110874 0.015795 -0.638717
+v 0.106820 0.013413 -0.559235
+v 0.106875 0.013640 -0.638743
+v 0.103319 0.010519 -0.559268
+v 0.103374 0.010747 -0.638777
+v 0.100450 0.006998 -0.559308
+v 0.100505 0.007226 -0.638817
+v 0.098323 0.002985 -0.559353
+v 0.098378 0.003212 -0.638861
+v 0.097019 -0.001366 -0.559401
+v 0.097038 -0.001139 -0.586361
+v -0.035201 0.400585 -0.586453
+v -0.035219 0.400035 -0.559493
+v -0.032013 0.411104 -0.638952
+v -0.032068 0.410554 -0.559444
+v -0.026870 0.420807 -0.638906
+v -0.026926 0.420257 -0.559397
+v -0.019933 0.429320 -0.638863
+v -0.019989 0.428770 -0.559354
+v -0.011469 0.436316 -0.638826
+v -0.011524 0.435766 -0.559317
+v -0.001802 0.441526 -0.638796
+v -0.001858 0.440976 -0.559287
+v 0.008695 0.444750 -0.638774
+v 0.008639 0.444201 -0.559266
+v 0.019620 0.445865 -0.638761
+v 0.019564 0.445315 -0.559253
+v 0.030552 0.444827 -0.638758
+v 0.030496 0.444276 -0.559250
+v 0.041071 0.441676 -0.638765
+v 0.041016 0.441126 -0.559257
+v 0.050774 0.436533 -0.638781
+v 0.050719 0.435983 -0.559273
+v 0.059287 0.429597 -0.638806
+v 0.059232 0.429046 -0.559298
+v 0.066283 0.421132 -0.638839
+v 0.066228 0.420582 -0.559331
+v 0.071494 0.411466 -0.638879
+v 0.071438 0.410915 -0.559370
+v 0.074682 0.400968 -0.586375
+v 0.074663 0.400418 -0.559415
+v 0.075817 0.389562 -0.586371
+v 0.075777 0.389498 -0.559463
+v 0.074726 0.378934 -0.586213
+v 0.074740 0.378568 -0.559508
+v 0.071606 0.368590 -0.586254
+v 0.071589 0.368043 -0.559551
+v 0.066463 0.358888 -0.586296
+v 0.066446 0.358340 -0.559593
+v 0.059526 0.350376 -0.586335
+v 0.059509 0.349827 -0.559632
+v 0.051062 0.343380 -0.586369
+v 0.051045 0.342831 -0.559665
+v 0.041396 0.338170 -0.586396
+v 0.041378 0.337620 -0.559693
+v 0.030899 0.334946 -0.586416
+v 0.030881 0.334396 -0.559713
+v 0.019974 0.333831 -0.586428
+v 0.019956 0.333281 -0.559725
+v 0.009042 0.334869 -0.586432
+v 0.009023 0.334319 -0.559728
+v -0.001477 0.338020 -0.586427
+v -0.001497 0.337471 -0.559723
+v -0.011180 0.343162 -0.586413
+v -0.011200 0.342614 -0.559709
+v -0.019693 0.350099 -0.586391
+v -0.019713 0.349550 -0.559688
+v -0.026689 0.358563 -0.586363
+v -0.026709 0.358015 -0.559659
+v -0.031899 0.368229 -0.586328
+v -0.031919 0.367682 -0.559624
+v -0.035091 0.378551 -0.586292
+v -0.035144 0.378184 -0.559586
+v -0.036256 0.389171 -0.586451
+v -0.036258 0.389107 -0.559542
+v -0.037672 0.401036 -0.586450
+v -0.037717 0.400517 -0.522961
+v -0.034343 0.412028 -0.638945
+v -0.034424 0.411510 -0.522907
+v -0.028969 0.422167 -0.638893
+v -0.029050 0.421649 -0.522856
+v -0.021720 0.431063 -0.638846
+v -0.021801 0.430544 -0.522809
+v -0.012875 0.438373 -0.638806
+v -0.012956 0.437855 -0.522769
+v -0.002774 0.443818 -0.638773
+v -0.002855 0.443299 -0.522736
+v 0.008195 0.447187 -0.638750
+v 0.008114 0.446669 -0.522713
+v 0.019611 0.448351 -0.638736
+v 0.019530 0.447833 -0.522699
+v 0.031035 0.447267 -0.638734
+v 0.030953 0.446748 -0.522696
+v 0.042027 0.443974 -0.638741
+v 0.041946 0.443456 -0.522704
+v 0.052166 0.438600 -0.638759
+v 0.052085 0.438082 -0.522722
+v 0.061062 0.431351 -0.638787
+v 0.060981 0.430833 -0.522750
+v 0.068372 0.422507 -0.638824
+v 0.068291 0.421988 -0.522787
+v 0.073817 0.412406 -0.638867
+v 0.073736 0.411887 -0.522830
+v 0.077150 0.401436 -0.586368
+v 0.077105 0.400918 -0.522879
+v 0.078314 0.390021 -0.586367
+v 0.078270 0.389503 -0.522932
+v 0.077229 0.378597 -0.586219
+v 0.077185 0.378079 -0.522987
+v 0.073937 0.367605 -0.586273
+v 0.073893 0.367086 -0.523041
+v 0.068563 0.357466 -0.586324
+v 0.068519 0.356948 -0.523092
+v 0.061314 0.348570 -0.586371
+v 0.061270 0.348052 -0.523139
+v 0.052469 0.341260 -0.586412
+v 0.052425 0.340741 -0.523179
+v 0.042368 0.335815 -0.586444
+v 0.042324 0.335297 -0.523212
+v 0.031399 0.332446 -0.586468
+v 0.031355 0.331928 -0.523235
+v 0.019983 0.331281 -0.586481
+v 0.019939 0.330763 -0.523249
+v 0.008559 0.332366 -0.586484
+v 0.008515 0.331848 -0.523252
+v -0.002433 0.335659 -0.586476
+v -0.002477 0.335141 -0.523244
+v -0.012572 0.341033 -0.586458
+v -0.012616 0.340515 -0.523226
+v -0.021468 0.348281 -0.586430
+v -0.021512 0.347763 -0.523198
+v -0.028778 0.357126 -0.586394
+v -0.028823 0.356608 -0.523161
+v -0.034223 0.367227 -0.586350
+v -0.034267 0.366709 -0.523118
+v -0.037592 0.378196 -0.586301
+v -0.037637 0.377678 -0.523069
+v -0.038757 0.389612 -0.586451
+v -0.038801 0.389094 -0.523016
+v -0.004477 0.094108 -0.522992
+v -0.004433 0.094323 -0.586427
+v -0.003995 0.089387 -0.523045
+v -0.003951 0.089601 -0.586277
+v -0.002602 0.084850 -0.523096
+v -0.002558 0.085064 -0.586328
+v -0.000350 0.080672 -0.523142
+v -0.000306 0.080886 -0.586374
+v 0.002674 0.077013 -0.523181
+v 0.002718 0.077228 -0.586413
+v 0.006354 0.074015 -0.523213
+v 0.006398 0.074229 -0.586445
+v 0.010547 0.071792 -0.523235
+v 0.010591 0.072007 -0.586467
+v 0.015094 0.070430 -0.523247
+v 0.015138 0.070645 -0.586479
+v 0.019819 0.069982 -0.523249
+v 0.019863 0.070196 -0.586481
+v 0.024541 0.070463 -0.523240
+v 0.024585 0.070678 -0.586473
+v 0.029078 0.071857 -0.523221
+v 0.029122 0.072071 -0.586453
+v 0.033256 0.074109 -0.523193
+v 0.033300 0.074323 -0.586425
+v 0.036914 0.077133 -0.523156
+v 0.036958 0.077347 -0.586388
+v 0.039912 0.080812 -0.523112
+v 0.039957 0.081027 -0.586344
+v 0.042135 0.085006 -0.523063
+v 0.042179 0.085220 -0.586295
+v 0.043497 0.089552 -0.523010
+v 0.043541 0.089767 -0.586242
+v 0.043946 0.094278 -0.522956
+v 0.043990 0.094492 -0.586391
+v 0.043464 0.098999 -0.522903
+v 0.043508 0.099214 -0.586392
+v 0.042070 0.103536 -0.522852
+v 0.042151 0.103751 -0.638889
+v 0.039818 0.107714 -0.522806
+v 0.039899 0.107929 -0.638844
+v 0.036795 0.111373 -0.522767
+v 0.036876 0.111587 -0.638804
+v 0.033115 0.114371 -0.522735
+v 0.033196 0.114585 -0.638773
+v 0.028921 0.116594 -0.522713
+v 0.029002 0.116808 -0.638750
+v 0.024375 0.117955 -0.522701
+v 0.024456 0.118170 -0.638738
+v 0.019650 0.118404 -0.522699
+v 0.019731 0.118618 -0.638736
+v 0.014928 0.117922 -0.522708
+v 0.015009 0.118137 -0.638745
+v 0.010391 0.116529 -0.522727
+v 0.010472 0.116743 -0.638764
+v 0.006213 0.114277 -0.522755
+v 0.006294 0.114491 -0.638793
+v 0.002554 0.111253 -0.522792
+v 0.002635 0.111467 -0.638829
+v -0.000444 0.107574 -0.522836
+v -0.000363 0.107788 -0.638873
+v -0.002667 0.103380 -0.522885
+v -0.002586 0.103594 -0.638922
+v -0.004028 0.098833 -0.522938
+v -0.003984 0.099048 -0.586427
+v -0.003410 0.094114 -0.559519
+v -0.003398 0.094140 -0.586428
+v -0.002950 0.089596 -0.559564
+v -0.002917 0.089748 -0.586269
+v -0.001616 0.085252 -0.559603
+v -0.001596 0.085478 -0.586307
+v 0.000540 0.081254 -0.559640
+v 0.000559 0.081480 -0.586344
+v 0.003433 0.077753 -0.559672
+v 0.003453 0.077979 -0.586375
+v 0.006954 0.074883 -0.559697
+v 0.006974 0.075110 -0.586400
+v 0.010968 0.072756 -0.559714
+v 0.010987 0.072983 -0.586418
+v 0.015319 0.071453 -0.559724
+v 0.015338 0.071680 -0.586427
+v 0.019841 0.071023 -0.559725
+v 0.019860 0.071251 -0.586428
+v 0.024360 0.071484 -0.559717
+v 0.024378 0.071712 -0.586421
+v 0.028702 0.072818 -0.559701
+v 0.028720 0.073045 -0.586405
+v 0.032700 0.074973 -0.559678
+v 0.032718 0.075200 -0.586382
+v 0.036201 0.077867 -0.559648
+v 0.036219 0.078094 -0.586351
+v 0.039070 0.081388 -0.559612
+v 0.039088 0.081615 -0.586316
+v 0.041197 0.085402 -0.559572
+v 0.041215 0.085628 -0.586276
+v 0.042501 0.089755 -0.559530
+v 0.042506 0.089906 -0.586236
+v 0.042930 0.094276 -0.559486
+v 0.042957 0.094302 -0.586394
+v 0.042469 0.098792 -0.559437
+v 0.042487 0.099020 -0.586397
+v 0.041135 0.103134 -0.559391
+v 0.041190 0.103362 -0.638900
+v 0.038980 0.107132 -0.559350
+v 0.039035 0.107360 -0.638858
+v 0.036086 0.110633 -0.559314
+v 0.036141 0.110861 -0.638822
+v 0.032565 0.113503 -0.559286
+v 0.032620 0.113730 -0.638794
+v 0.028552 0.115630 -0.559266
+v 0.028607 0.115857 -0.638774
+v 0.024201 0.116933 -0.559254
+v 0.024256 0.117161 -0.638763
+v 0.019679 0.117363 -0.559253
+v 0.019734 0.117590 -0.638761
+v 0.015160 0.116902 -0.559261
+v 0.015216 0.117129 -0.638769
+v 0.010818 0.115568 -0.559278
+v 0.010874 0.115795 -0.638787
+v 0.006820 0.113413 -0.559305
+v 0.006875 0.113640 -0.638813
+v 0.003319 0.110519 -0.559338
+v 0.003374 0.110747 -0.638847
+v 0.000450 0.106998 -0.559378
+v 0.000505 0.107226 -0.638886
+v -0.001677 0.102985 -0.559423
+v -0.001622 0.103212 -0.638931
+v -0.002981 0.098634 -0.559471
+v -0.002962 0.098861 -0.586431
+v 0.028888 -0.168051 -0.439591
+v -0.028274 -0.168051 -0.439631
+v -0.028235 -0.168051 -0.496794
+v 0.028928 -0.168051 -0.496754
+v 0.028809 -0.168051 -0.325555
+v 0.028769 -0.168051 -0.268392
+v -0.028394 -0.168051 -0.268432
+v -0.028354 -0.168051 -0.325595
+v -0.028394 -0.168051 -0.268432
+v 0.028769 -0.168051 -0.268392
+v 0.028928 -0.168051 -0.496754
+v -0.028235 -0.168051 -0.496794
+v -0.028354 -0.168051 -0.325595
+v -0.028274 -0.168051 -0.439631
+v 0.028809 -0.168051 -0.325555
+v 0.028888 -0.168051 -0.439591
+v 0.028888 0.037812 -0.439591
+v 0.028888 0.165702 -0.439591
+v -0.028274 0.165702 -0.439631
+v -0.028274 0.037812 -0.439631
+v -0.028235 0.037812 -0.496794
+v -0.028235 0.165702 -0.496794
+v 0.028928 0.165702 -0.496754
+v 0.028928 0.037812 -0.496754
+v 0.028809 0.037812 -0.325555
+v 0.028809 0.165702 -0.325555
+v 0.028769 0.165702 -0.268392
+v 0.028769 0.037812 -0.268392
+v -0.028394 0.037812 -0.268432
+v -0.028394 0.165702 -0.268432
+v -0.028354 0.165702 -0.325595
+v -0.028354 0.037812 -0.325595
+v 0.028888 0.166622 -0.439591
+v -0.028274 0.166622 -0.439631
+v -0.028235 0.166622 -0.496794
+v 0.028928 0.166622 -0.496754
+v 0.028809 0.166622 -0.325555
+v 0.028769 0.166622 -0.268392
+v -0.028394 0.166622 -0.268432
+v -0.028354 0.166622 -0.325595
+v -0.028394 0.166622 -0.268432
+v 0.028769 0.166622 -0.268392
+v 0.028928 0.166622 -0.496754
+v -0.028235 0.166622 -0.496794
+v -0.028354 0.166622 -0.325595
+v -0.028274 0.166622 -0.439631
+v 0.028809 0.166622 -0.325555
+v 0.028888 0.166622 -0.439591
+v 0.028888 0.370885 -0.439591
+v 0.028888 0.497782 -0.439591
+v -0.028274 0.497782 -0.439631
+v -0.028274 0.370885 -0.439631
+v -0.028235 0.370885 -0.496794
+v -0.028235 0.497782 -0.496794
+v 0.028928 0.497782 -0.496754
+v 0.028928 0.370885 -0.496754
+v 0.028809 0.370885 -0.325555
+v 0.028809 0.497782 -0.325555
+v 0.028769 0.497782 -0.268392
+v 0.028769 0.370885 -0.268392
+v -0.028394 0.370885 -0.268432
+v -0.028394 0.497782 -0.268432
+v -0.028354 0.497782 -0.325595
+v -0.028354 0.370885 -0.325595
+v 0.269752 -0.145166 -0.548298
+v -0.230248 -0.145166 -0.548647
+v 0.269752 0.603749 -0.548297
+v -0.230248 0.603749 -0.548646
+v 0.269719 0.603749 -0.500879
+v 0.269719 -0.145166 -0.500879
+v -0.230281 -0.145166 -0.501228
+v -0.230281 0.603749 -0.501228
+vt 0.285559 0.095797
+vt 0.214079 0.095797
+vt 0.214079 0.000000
+vt 0.285559 0.000000
+vt 0.357039 0.095797
+vt 0.285559 0.095797
+vt 0.285559 0.000000
+vt 0.357039 0.000000
+vt 0.642599 0.095797
+vt 0.571118 0.095797
+vt 0.571118 0.000000
+vt 0.642598 0.000000
+vt 0.714079 0.904203
+vt 0.785559 0.904203
+vt 0.785559 1.000000
+vt 0.714079 1.000000
+vt 0.642599 0.904203
+vt 0.714079 0.904203
+vt 0.714079 1.000000
+vt 0.642599 1.000000
+vt 0.071480 0.095797
+vt 0.000000 0.095797
+vt 0.000000 0.000000
+vt 0.071480 0.000000
+vt 0.571118 0.095797
+vt 0.499638 0.095797
+vt 0.499638 0.000000
+vt 0.571118 0.000000
+vt 0.857040 0.394495
+vt 0.928520 0.394495
+vt 0.928520 0.480583
+vt 0.857040 0.480583
+vt 0.857040 0.129242
+vt 0.785559 0.129242
+vt 0.785559 0.043154
+vt 0.857040 0.043154
+vt 0.785559 0.172396
+vt 0.857040 0.172396
+vt 0.785559 0.000000
+vt 0.857040 0.000000
+vt 0.285559 0.250000
+vt 0.214079 0.250000
+vt 0.357039 0.250000
+vt 0.285559 0.250000
+vt 0.642599 0.250000
+vt 0.571118 0.250000
+vt 0.714079 0.750000
+vt 0.785559 0.750000
+vt 0.642599 0.750000
+vt 0.714079 0.750000
+vt 0.928520 0.154203
+vt 0.857040 0.154203
+vt 0.857040 0.000000
+vt 0.928520 0.000000
+vt 0.071480 0.250000
+vt 0.000000 0.250000
+vt 0.571118 0.250000
+vt 0.499638 0.250000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.615085 0.501950
+vt 0.538102 0.501950
+vt 0.538102 0.192341
+vt 0.615085 0.192341
+vt 0.538102 0.000000
+vt 0.615085 0.000000
+vt 0.076983 0.501950
+vt 0.000000 0.501950
+vt 0.000000 0.192341
+vt 0.076983 0.192341
+vt 0.000000 0.000000
+vt 0.076983 0.000000
+vt 0.692068 0.501950
+vt 0.692068 0.192341
+vt 0.692068 0.000000
+vt 0.769051 0.501950
+vt 0.692068 0.501950
+vt 0.692068 0.192341
+vt 0.769051 0.192341
+vt 0.692068 0.000000
+vt 0.769051 0.000000
+vt 0.846034 0.501950
+vt 0.769051 0.501950
+vt 0.769051 0.192341
+vt 0.846034 0.192341
+vt 0.769051 0.000000
+vt 0.846034 0.000000
+vt 0.923017 0.996502
+vt 0.846034 0.996502
+vt 0.846034 0.686893
+vt 0.923017 0.686893
+vt 0.384526 0.501950
+vt 0.307543 0.501950
+vt 0.307542 0.192341
+vt 0.384525 0.192341
+vt 0.307542 0.000000
+vt 0.384525 0.000000
+vt 0.307542 0.501950
+vt 0.230559 0.501950
+vt 0.230559 0.192341
+vt 0.307542 0.192341
+vt 0.230559 0.000000
+vt 0.307542 0.000000
+vt 0.923017 0.478709
+vt 1.000000 0.478709
+vt 1.000000 0.650215
+vt 0.923017 0.650215
+vt 0.923017 0.600923
+vt 0.846034 0.600923
+vt 0.846034 0.429417
+vt 0.923017 0.429417
+vt 0.846034 0.686893
+vt 0.923017 0.686893
+vt 0.846034 0.343447
+vt 0.923017 0.343447
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.000000 0.000000
+vt 0.615085 1.000000
+vt 0.538102 1.000000
+vt 0.538102 0.692796
+vt 0.615085 0.692796
+vt 0.538102 0.501950
+vt 0.615085 0.501950
+vt 0.076983 1.000000
+vt 0.000000 1.000000
+vt 0.000000 0.692797
+vt 0.076983 0.692797
+vt 0.000000 0.501950
+vt 0.076983 0.501950
+vt 0.769051 1.000000
+vt 0.692068 1.000000
+vt 0.692068 0.692796
+vt 0.769051 0.692796
+vt 0.692068 0.501950
+vt 0.769051 0.501950
+vt 0.615085 0.501950
+vt 0.692068 0.501950
+vt 0.692068 0.809153
+vt 0.615085 0.809153
+vt 0.692068 1.000000
+vt 0.615085 1.000000
+vt 0.846034 1.000000
+vt 0.769051 1.000000
+vt 0.769051 0.692796
+vt 0.846034 0.692796
+vt 0.769051 0.501950
+vt 0.846034 0.501950
+vt 1.000000 0.307203
+vt 0.923017 0.307203
+vt 0.923017 0.000000
+vt 1.000000 0.000000
+vt 0.384526 1.000000
+vt 0.307543 1.000000
+vt 0.307542 0.692797
+vt 0.384525 0.692797
+vt 0.307542 0.501950
+vt 0.384525 0.501950
+vt 0.307542 1.000000
+vt 0.230559 1.000000
+vt 0.230559 0.692797
+vt 0.307542 0.692796
+vt 0.230559 0.501950
+vt 0.307542 0.501950
+vt 0.923017 0.307203
+vt 1.000000 0.307203
+vt 1.000000 0.478709
+vt 0.923017 0.478709
+vt 0.923017 0.257476
+vt 0.846034 0.257476
+vt 0.846034 0.085970
+vt 0.923017 0.085970
+vt 0.846034 0.343447
+vt 0.923017 0.343447
+vt 0.846034 0.000000
+vt 0.923017 0.000000
+vt 0.875462 0.748915
+vt 0.437731 0.748915
+vt 0.437731 0.000000
+vt 0.875462 0.000000
+vt 0.437731 0.000000
+vt 0.437731 0.748915
+vt 0.000000 0.748915
+vt 0.000000 0.000000
+vt 0.958487 0.748915
+vt 0.916975 0.748915
+vt 0.916975 0.000000
+vt 0.958487 0.000000
+vt 0.958487 0.000000
+vt 1.000000 0.000000
+vt 1.000000 0.500000
+vt 0.958487 0.500000
+vt 1.000000 1.000000
+vt 0.958487 1.000000
+vt 0.958487 0.500000
+vt 1.000000 0.500000
+vt 0.875462 0.000000
+vt 0.916974 0.000000
+vt 0.916975 0.748915
+vt 0.875462 0.748915
+vt 0.442670 0.517822
+vt 0.228883 0.517823
+vt 0.228889 0.488295
+vt 0.442971 0.724510
+vt 0.443060 0.754024
+vt 0.370472 0.723575
+vt 0.442967 0.311134
+vt 0.370467 0.312068
+vt 0.443056 0.281620
+vt 0.443160 0.252098
+vt 0.371348 0.252525
+vt 0.443163 0.783546
+vt 0.371352 0.783117
+vt 0.006929 0.253167
+vt 0.177644 0.253134
+vt 0.006828 0.284020
+vt 0.007055 0.808541
+vt 0.006941 0.777687
+vt 0.177771 0.808498
+vt 0.006827 0.746834
+vt 0.176852 0.746796
+vt 0.006727 0.314875
+vt 0.176753 0.314837
+vt 0.371492 0.871219
+vt 0.371495 0.841694
+vt 0.443305 0.842613
+vt 0.615392 0.436018
+vt 0.610260 0.440534
+vt 0.592437 0.415287
+vt 0.816609 0.103895
+vt 0.836176 0.125250
+vt 0.831035 0.128981
+vt 0.706468 0.066881
+vt 0.736753 0.067631
+vt 0.735929 0.074111
+vt 0.647824 0.083609
+vt 0.650942 0.089526
+vt 0.626230 0.105756
+vt 0.765981 0.074365
+vt 0.763884 0.080481
+vt 0.579817 0.387126
+vt 0.586362 0.385333
+vt 0.621946 0.100560
+vt 0.605204 0.126737
+vt 0.706979 0.073452
+vt 0.678244 0.078632
+vt 0.792953 0.086670
+vt 0.812355 0.108585
+vt 0.676401 0.072253
+vt 0.789700 0.092175
+vt 0.599888 0.122511
+vt 0.589057 0.151745
+vt 0.582798 0.148853
+vt 0.579868 0.178984
+vt 0.850517 0.149568
+vt 0.844628 0.151667
+vt 0.572791 0.357181
+vt 0.571207 0.326153
+vt 0.578089 0.326242
+vt 0.579568 0.357437
+vt 0.892437 0.349226
+vt 0.885423 0.348954
+vt 0.885704 0.319257
+vt 0.886223 0.285996
+vt 0.879631 0.287703
+vt 0.636826 0.456466
+vt 0.632640 0.461897
+vt 0.874138 0.409852
+vt 0.856681 0.436209
+vt 0.851292 0.431762
+vt 0.661830 0.472412
+vt 0.658752 0.478560
+vt 0.689480 0.483245
+vt 0.687628 0.489885
+vt 0.718747 0.488548
+vt 0.718195 0.495436
+vt 0.779839 0.488518
+vt 0.749318 0.494983
+vt 0.748545 0.488099
+vt 0.808610 0.476251
+vt 0.777761 0.481894
+vt 0.834553 0.458608
+vt 0.805300 0.470133
+vt 0.830124 0.453224
+vt 0.573209 0.178195
+vt 0.886228 0.380492
+vt 0.867982 0.406513
+vt 0.879533 0.378395
+vt 0.370709 0.754396
+vt 0.319322 0.130795
+vt 0.319224 0.161649
+vt 0.007220 0.160604
+vt 0.007387 0.901104
+vt 0.319390 0.899923
+vt 0.319502 0.930776
+vt 0.442778 0.399714
+vt 0.228990 0.399716
+vt 0.229046 0.370189
+vt 0.007499 0.931958
+vt 0.319616 0.961631
+vt 0.006361 0.469146
+vt 0.176387 0.469108
+vt 0.176362 0.499962
+vt 0.442678 0.488295
+vt 0.228910 0.458769
+vt 0.007727 0.006334
+vt 0.319730 0.007378
+vt 0.319626 0.038233
+vt 0.228890 0.547350
+vt 0.007613 0.962812
+vt 0.319730 0.992485
+vt 0.228946 0.606403
+vt 0.228912 0.576877
+vt 0.442700 0.576876
+vt 0.228992 0.635930
+vt 0.442734 0.606402
+vt 0.442833 0.370188
+vt 0.229109 0.340661
+vt 0.229113 0.694984
+vt 0.229049 0.665457
+vt 0.442837 0.665456
+vt 0.442901 0.694984
+vt 0.370705 0.281246
+vt 0.371443 0.812167
+vt 0.443254 0.813086
+vt 0.892437 0.317355
+vt 0.371421 0.900745
+vt 0.443302 0.872141
+vt 0.371269 0.930272
+vt 0.443231 0.901668
+vt 0.007276 0.870250
+vt 0.319278 0.869069
+vt 0.006405 0.438291
+vt 0.176430 0.438254
+vt 0.443090 0.104449
+vt 0.371280 0.105372
+vt 0.371043 0.075847
+vt 0.443237 0.133976
+vt 0.371427 0.134897
+vt 0.443305 0.163503
+vt 0.371495 0.164423
+vt 0.443305 0.193031
+vt 0.371495 0.193949
+vt 0.443252 0.222558
+vt 0.371442 0.223476
+vt 0.177399 0.283987
+vt 0.006543 0.376583
+vt 0.176568 0.376545
+vt 0.176492 0.407399
+vt 0.006467 0.407437
+vt 0.007165 0.839396
+vt 0.319168 0.838215
+vt 0.370694 0.989321
+vt 0.371029 0.959797
+vt 0.442838 0.960722
+vt 0.442780 0.635930
+vt 0.442698 0.458768
+vt 0.228944 0.429242
+vt 0.006337 0.500000
+vt 0.176359 0.530816
+vt 0.319524 0.069087
+vt 0.007520 0.068042
+vt 0.319423 0.099941
+vt 0.007419 0.098896
+vt 0.007319 0.129750
+vt 0.442897 0.340661
+vt 0.319126 0.192504
+vt 0.007122 0.191458
+vt 0.319029 0.223358
+vt 0.007025 0.222313
+vt 0.177512 0.777645
+vt 0.443079 0.931196
+vt 0.176656 0.345691
+vt 0.006631 0.345729
+vt 0.442853 0.074922
+vt 0.370712 0.046322
+vt 0.442732 0.429241
+vt 0.442678 0.547349
+vt 0.176377 0.561671
+vt 0.006351 0.561708
+vt 0.176415 0.592525
+vt 0.006390 0.592562
+vt 0.176474 0.623379
+vt 0.006448 0.623417
+vt 0.176549 0.654233
+vt 0.006524 0.654271
+vt 0.176640 0.685087
+vt 0.006614 0.685125
+vt 0.176742 0.715941
+vt 0.006716 0.715979
+vt 0.615392 0.436018
+vt 0.610260 0.440534
+vt 0.592437 0.415287
+vt 0.006631 0.345729
+vt 0.176656 0.345691
+vt 0.176568 0.376545
+vt 0.371443 0.812167
+vt 0.371352 0.783117
+vt 0.443163 0.783546
+vt 0.176852 0.746796
+vt 0.177512 0.777645
+vt 0.006941 0.777687
+vt 0.007165 0.839396
+vt 0.319168 0.838215
+vt 0.319278 0.869069
+vt 0.006467 0.407437
+vt 0.176492 0.407399
+vt 0.176430 0.438254
+vt 0.442897 0.340661
+vt 0.229109 0.340661
+vt 0.370467 0.312068
+vt 0.319524 0.069087
+vt 0.319423 0.099941
+vt 0.007419 0.098896
+vt 0.319322 0.130795
+vt 0.319224 0.161649
+vt 0.007220 0.160604
+vt 0.856681 0.436209
+vt 0.834553 0.458608
+vt 0.830124 0.453224
+vt 0.007727 0.006334
+vt 0.319730 0.007378
+vt 0.319626 0.038233
+vt 0.229049 0.665457
+vt 0.228992 0.635930
+vt 0.442780 0.635930
+vt 0.442967 0.311134
+vt 0.443056 0.281620
+vt 0.228890 0.547350
+vt 0.228883 0.517823
+vt 0.442670 0.517822
+vt 0.443060 0.754024
+vt 0.319126 0.192504
+vt 0.319029 0.223358
+vt 0.007025 0.222313
+vt 0.370709 0.754396
+vt 0.370472 0.723575
+vt 0.647824 0.083609
+vt 0.650942 0.089526
+vt 0.626230 0.105756
+vt 0.765981 0.074365
+vt 0.792953 0.086670
+vt 0.789700 0.092175
+vt 0.718747 0.488548
+vt 0.718195 0.495436
+vt 0.687628 0.489885
+vt 0.779839 0.488518
+vt 0.749318 0.494983
+vt 0.748545 0.488099
+vt 0.370705 0.281246
+vt 0.371348 0.252525
+vt 0.228946 0.606403
+vt 0.442734 0.606402
+vt 0.228889 0.488295
+vt 0.579817 0.387126
+vt 0.586362 0.385333
+vt 0.006337 0.500000
+vt 0.176362 0.499962
+vt 0.176359 0.530816
+vt 0.892437 0.349226
+vt 0.885423 0.348954
+vt 0.885704 0.319257
+vt 0.706468 0.066881
+vt 0.736753 0.067631
+vt 0.735929 0.074111
+vt 0.443305 0.193031
+vt 0.371495 0.193949
+vt 0.371495 0.164423
+vt 0.177644 0.253134
+vt 0.177399 0.283987
+vt 0.006828 0.284020
+vt 0.176549 0.654233
+vt 0.176640 0.685087
+vt 0.006614 0.685125
+vt 0.443160 0.252098
+vt 0.371269 0.930272
+vt 0.371421 0.900745
+vt 0.443231 0.901668
+vt 0.816609 0.103895
+vt 0.836176 0.125250
+vt 0.831035 0.128981
+vt 0.621946 0.100560
+vt 0.605204 0.126737
+vt 0.442698 0.458768
+vt 0.228910 0.458769
+vt 0.228944 0.429242
+vt 0.442732 0.429241
+vt 0.228990 0.399716
+vt 0.874138 0.409852
+vt 0.851292 0.431762
+vt 0.228912 0.576877
+vt 0.442678 0.547349
+vt 0.176377 0.561671
+vt 0.006351 0.561708
+vt 0.850517 0.149568
+vt 0.844628 0.151667
+vt 0.371029 0.959797
+vt 0.443079 0.931196
+vt 0.442853 0.074922
+vt 0.371043 0.075847
+vt 0.370712 0.046322
+vt 0.371495 0.841694
+vt 0.443254 0.813086
+vt 0.443252 0.222558
+vt 0.371442 0.223476
+vt 0.763884 0.080481
+vt 0.443090 0.104449
+vt 0.371280 0.105372
+vt 0.808610 0.476251
+vt 0.805300 0.470133
+vt 0.006727 0.314875
+vt 0.176753 0.314837
+vt 0.007613 0.962812
+vt 0.319616 0.961631
+vt 0.319730 0.992485
+vt 0.006827 0.746834
+vt 0.176742 0.715941
+vt 0.006716 0.715979
+vt 0.661830 0.472412
+vt 0.658752 0.478560
+vt 0.632640 0.461897
+vt 0.442971 0.724510
+vt 0.006929 0.253167
+vt 0.572791 0.357181
+vt 0.571207 0.326153
+vt 0.578089 0.326242
+vt 0.442778 0.399714
+vt 0.229046 0.370189
+vt 0.442700 0.576876
+vt 0.176474 0.623379
+vt 0.006524 0.654271
+vt 0.006543 0.376583
+vt 0.229113 0.694984
+vt 0.442837 0.665456
+vt 0.007520 0.068042
+vt 0.371492 0.871219
+vt 0.443302 0.872141
+vt 0.689480 0.483245
+vt 0.443305 0.163503
+vt 0.371427 0.134897
+vt 0.007055 0.808541
+vt 0.177771 0.808498
+vt 0.442678 0.488295
+vt 0.777761 0.481894
+vt 0.573209 0.178195
+vt 0.579868 0.178984
+vt 0.636826 0.456466
+vt 0.579568 0.357437
+vt 0.007387 0.901104
+vt 0.319390 0.899923
+vt 0.319502 0.930776
+vt 0.176415 0.592525
+vt 0.006390 0.592562
+vt 0.007122 0.191458
+vt 0.812355 0.108585
+vt 0.582798 0.148853
+vt 0.589057 0.151745
+vt 0.007276 0.870250
+vt 0.007319 0.129750
+vt 0.006448 0.623417
+vt 0.886228 0.380492
+vt 0.879533 0.378395
+vt 0.442833 0.370188
+vt 0.370694 0.989321
+vt 0.442838 0.960722
+vt 0.886223 0.285996
+vt 0.879631 0.287703
+vt 0.599888 0.122511
+vt 0.676401 0.072253
+vt 0.443237 0.133976
+vt 0.443305 0.842613
+vt 0.867982 0.406513
+vt 0.006405 0.438291
+vt 0.176387 0.469108
+vt 0.892437 0.317355
+vt 0.442901 0.694984
+vt 0.006361 0.469146
+vt 0.007499 0.931958
+vt 0.706979 0.073452
+vt 0.678244 0.078632
+vt 0.598317 0.411848
+vt 0.007623 0.037188
+vt 0.007728 0.993666
+vt 0.442504 0.990249
+vt 0.006334 0.530854
+vt 0.442522 0.045396
+vt 0.442670 0.517822
+vt 0.228883 0.517823
+vt 0.228889 0.488295
+vt 0.442971 0.724510
+vt 0.443060 0.754024
+vt 0.370472 0.723575
+vt 0.442967 0.311134
+vt 0.370467 0.312068
+vt 0.443056 0.281620
+vt 0.443160 0.252098
+vt 0.371348 0.252525
+vt 0.443163 0.783546
+vt 0.371352 0.783117
+vt 0.006929 0.253167
+vt 0.177644 0.253134
+vt 0.006828 0.284020
+vt 0.007055 0.808541
+vt 0.006941 0.777687
+vt 0.177771 0.808498
+vt 0.006827 0.746834
+vt 0.176852 0.746796
+vt 0.006727 0.314875
+vt 0.176753 0.314837
+vt 0.371492 0.871219
+vt 0.371495 0.841694
+vt 0.443305 0.842613
+vt 0.615392 0.436018
+vt 0.610260 0.440534
+vt 0.592437 0.415287
+vt 0.816609 0.103895
+vt 0.836176 0.125250
+vt 0.831035 0.128981
+vt 0.706468 0.066881
+vt 0.736753 0.067631
+vt 0.735929 0.074111
+vt 0.647824 0.083609
+vt 0.650942 0.089526
+vt 0.626230 0.105756
+vt 0.765981 0.074365
+vt 0.763884 0.080481
+vt 0.579817 0.387126
+vt 0.586362 0.385333
+vt 0.621946 0.100560
+vt 0.605204 0.126737
+vt 0.706979 0.073452
+vt 0.678244 0.078632
+vt 0.792953 0.086670
+vt 0.812355 0.108585
+vt 0.676401 0.072253
+vt 0.789700 0.092175
+vt 0.599888 0.122511
+vt 0.589057 0.151745
+vt 0.582798 0.148853
+vt 0.579868 0.178984
+vt 0.850517 0.149568
+vt 0.844628 0.151667
+vt 0.572791 0.357181
+vt 0.571207 0.326153
+vt 0.578089 0.326242
+vt 0.579568 0.357437
+vt 0.892437 0.349226
+vt 0.885423 0.348954
+vt 0.885704 0.319257
+vt 0.886223 0.285996
+vt 0.879631 0.287703
+vt 0.636826 0.456466
+vt 0.632640 0.461897
+vt 0.874138 0.409852
+vt 0.856681 0.436209
+vt 0.851292 0.431762
+vt 0.661830 0.472412
+vt 0.658752 0.478560
+vt 0.689480 0.483245
+vt 0.687628 0.489885
+vt 0.718747 0.488548
+vt 0.718195 0.495436
+vt 0.779839 0.488518
+vt 0.749318 0.494983
+vt 0.748545 0.488099
+vt 0.808610 0.476251
+vt 0.777761 0.481894
+vt 0.834553 0.458608
+vt 0.805300 0.470133
+vt 0.830124 0.453224
+vt 0.573209 0.178195
+vt 0.886228 0.380492
+vt 0.867982 0.406513
+vt 0.879533 0.378395
+vt 0.370709 0.754396
+vt 0.319322 0.130795
+vt 0.319224 0.161649
+vt 0.007220 0.160604
+vt 0.007387 0.901104
+vt 0.319390 0.899923
+vt 0.319502 0.930776
+vt 0.442778 0.399714
+vt 0.228990 0.399716
+vt 0.229046 0.370189
+vt 0.007499 0.931958
+vt 0.319616 0.961631
+vt 0.006361 0.469146
+vt 0.176387 0.469108
+vt 0.176362 0.499962
+vt 0.442678 0.488295
+vt 0.228910 0.458769
+vt 0.007727 0.006334
+vt 0.319730 0.007378
+vt 0.319626 0.038233
+vt 0.228890 0.547350
+vt 0.007613 0.962812
+vt 0.319730 0.992485
+vt 0.228946 0.606403
+vt 0.228912 0.576877
+vt 0.442700 0.576876
+vt 0.228992 0.635930
+vt 0.442734 0.606402
+vt 0.442833 0.370188
+vt 0.229109 0.340661
+vt 0.229113 0.694984
+vt 0.229049 0.665457
+vt 0.442837 0.665456
+vt 0.442901 0.694984
+vt 0.370705 0.281246
+vt 0.371443 0.812167
+vt 0.443254 0.813086
+vt 0.892437 0.317355
+vt 0.371421 0.900745
+vt 0.443302 0.872141
+vt 0.371269 0.930272
+vt 0.443231 0.901668
+vt 0.007276 0.870250
+vt 0.319278 0.869069
+vt 0.006405 0.438291
+vt 0.176430 0.438254
+vt 0.443090 0.104449
+vt 0.371280 0.105372
+vt 0.371043 0.075847
+vt 0.443237 0.133976
+vt 0.371427 0.134897
+vt 0.443305 0.163503
+vt 0.371495 0.164423
+vt 0.443305 0.193031
+vt 0.371495 0.193949
+vt 0.443252 0.222558
+vt 0.371442 0.223476
+vt 0.177399 0.283987
+vt 0.006543 0.376583
+vt 0.176568 0.376545
+vt 0.176492 0.407399
+vt 0.006467 0.407437
+vt 0.007165 0.839396
+vt 0.319168 0.838215
+vt 0.370694 0.989321
+vt 0.371029 0.959797
+vt 0.442838 0.960722
+vt 0.442780 0.635930
+vt 0.442698 0.458768
+vt 0.228944 0.429242
+vt 0.006337 0.500000
+vt 0.176359 0.530816
+vt 0.319524 0.069087
+vt 0.007520 0.068042
+vt 0.319423 0.099941
+vt 0.007419 0.098896