From 39de44275d224e3385cf52f3da28e30f575f16b1 Mon Sep 17 00:00:00 2001 From: Joachim Stolberg Date: Fri, 14 May 2021 18:50:16 +0200 Subject: [PATCH] built on 14/05/2021 18:50:16 --- autobahn/init.lua | 16 +- basic_materials/.gitlab-ci.yml | 8 + compost/init.lua | 2 +- hyperloop/README.md | 21 +- hyperloop/booking_node.lua | 110 +- hyperloop/init.lua | 6 +- hyperloop/network.lua | 17 +- hyperloop/settingtypes.txt | 5 + minecart/README.md | 64 +- minecart/api.lua | 52 + minecart/baselib.lua | 360 ++++ minecart/buffer.lua | 54 +- minecart/cart_lib1.lua | 397 ---- minecart/cart_lib2e.lua | 150 -- minecart/cart_lib2n.lua | 198 -- minecart/cart_lib3.lua | 90 - minecart/doc.lua | 43 +- minecart/entitylib.lua | 318 ++++ minecart/hopper.lua | 3 +- minecart/hopperlib.lua | 141 ++ minecart/i18n.py | 502 ++++- minecart/init.lua | 37 +- minecart/lib.lua | 326 ---- minecart/locale/minecart.de.tr | 65 +- minecart/locale/template.txt | 62 +- minecart/minecart.lua | 137 +- minecart/mods_support.lua | 2 +- minecart/monitoring.lua | 418 ++-- minecart/nodelib.lua | 139 ++ minecart/protection.lua | 7 +- minecart/pusher.lua | 67 + minecart/rails.lua | 548 ++++++ minecart/recording.lua | 206 +- minecart/signs.lua | 107 ++ minecart/storage.lua | 131 +- minecart/terminal.lua | 90 + minecart/textures/minecart_appl_cart_top.png | Bin 0 -> 108 bytes minecart/textures/minecart_appl_hopper.png | Bin 159 -> 138 bytes .../textures/minecart_appl_hopper_right.png | Bin 169 -> 148 bytes .../textures/minecart_appl_hopper_top.png | Bin 203 -> 182 bytes minecart/textures/minecart_ballast.png | Bin 3184 -> 572 bytes minecart/textures/minecart_buffer.png | Bin 457 -> 222 bytes minecart/textures/minecart_cart.png | Bin 285 -> 264 bytes minecart/textures/minecart_doc_image.png | Bin 40385 -> 10813 bytes minecart/textures/minecart_logo.png | Bin 263 -> 201 bytes minecart/textures/minecart_marker_cube.png | Bin 0 -> 195 bytes minecart/textures/minecart_protect.png | Bin 266 -> 128 bytes minecart/textures/minecart_pusher.png | Bin 0 -> 111 bytes minecart/textures/minecart_pusher_top.png | Bin 0 -> 126 bytes minecart/textures/minecart_sign1.png | Bin 0 -> 141 bytes minecart/textures/minecart_sign2.png | Bin 0 -> 154 bytes minecart/textures/minecart_sign4.png | Bin 0 -> 151 bytes minecart/textures/minecart_sign8.png | Bin 0 -> 174 bytes minecart/textures/minecart_terminal_back.png | Bin 0 -> 123 bytes minecart/textures/minecart_terminal_front.png | Bin 0 -> 263 bytes minecart/textures/minecart_terminal_side.png | Bin 0 -> 166 bytes minecart/textures/minecart_terminal_top.png | Bin 0 -> 126 bytes minecart/textures/minecart_tool.png | Bin 0 -> 202 bytes minecart/textures/minecart_waypoint.png | Bin 0 -> 111 bytes minecart/textures/shrink.py | 16 - minecart/textures/shrink.sh | 2 + minecart/tool.lua | 101 + signs_bot/README.md | 4 + signs_bot/basis.lua | 90 +- signs_bot/bot_flap.lua | 24 +- signs_bot/bot_sensor.lua | 31 +- signs_bot/cart_sensor.lua | 41 +- signs_bot/changer.lua | 34 +- signs_bot/chest.lua | 31 +- signs_bot/cmd_farming.lua | 35 +- signs_bot/cmd_flowers.lua | 38 +- signs_bot/cmd_item.lua | 37 +- signs_bot/cmd_move.lua | 32 +- signs_bot/cmd_pattern.lua | 38 +- signs_bot/cmd_place.lua | 78 +- signs_bot/cmd_sign.lua | 73 +- signs_bot/commands.lua | 63 +- signs_bot/compost.lua | 101 + signs_bot/crop_sensor.lua | 28 +- signs_bot/delayer.lua | 36 +- signs_bot/doc.lua | 178 +- signs_bot/duplicator.lua | 51 +- signs_bot/extender.lua | 30 +- signs_bot/i18n.py | 458 +++++ signs_bot/init.lua | 15 +- signs_bot/interpreter.lua | 36 +- signs_bot/intllib.lua | 45 - signs_bot/intllib.sh | 2 +- signs_bot/legacy.lua | 22 +- signs_bot/lib.lua | 12 +- signs_bot/locale/de.mo | Bin 37075 -> 0 bytes signs_bot/locale/de.po | 1674 ----------------- signs_bot/locale/de.po~ | 1671 ---------------- signs_bot/locale/signs_bot.de.tr | 413 ++++ signs_bot/locale/template.pot | 1356 ------------- signs_bot/locale/template.txt | 410 ++++ signs_bot/logic_and.lua | 34 +- signs_bot/node_sensor.lua | 44 +- signs_bot/nodes.lua | 4 +- signs_bot/random.lua | 2 +- signs_bot/robot.lua | 18 +- signs_bot/signal.lua | 12 +- signs_bot/signs.lua | 63 +- signs_bot/techage.lua | 78 +- signs_bot/timer.lua | 45 +- signs_bot/tool.lua | 28 +- techage/README.md | 12 +- techage/basic_machines/autocrafter.lua | 11 + techage/basic_machines/chest.lua | 8 + techage/basic_machines/concentrator.lua | 125 ++ techage/basic_machines/consumer.lua | 46 +- techage/basic_machines/distributor.lua | 82 +- techage/basic_machines/electronic_fab.lua | 11 + techage/basic_machines/forceload.lua | 21 +- techage/basic_machines/gravelrinser.lua | 34 +- techage/basic_machines/gravelsieve.lua | 3 + techage/basic_machines/grinder.lua | 43 +- techage/basic_machines/legacy_nodes.lua | 62 + techage/basic_machines/mods_support.lua | 16 + techage/basic_machines/quarry.lua | 11 +- techage/basic_machines/recycler.lua | 3 + techage/basic_machines/ta4_chest.lua | 7 +- techage/basic_machines/ta4_injector.lua | 136 +- techage/basis/command.lua | 14 +- techage/basis/fuel_lib.lua | 2 +- techage/basis/laser_lib.lua | 137 ++ techage/basis/lib.lua | 2 +- techage/basis/liquid_lib.lua | 2 +- techage/basis/mark2.lua | 2 +- techage/basis/networks.lua | 6 +- techage/basis/node_states.lua | 20 +- techage/basis/nodedata_meta.lua | 2 +- techage/basis/recipe_lib.lua | 1 - techage/basis/tubes.lua | 7 +- techage/basis/tubes_ta4.lua | 7 +- techage/basis/windturbine_lib.lua | 23 +- techage/carts/chest_cart.lua | 49 +- techage/carts/tank_cart.lua | 65 +- techage/chemistry/ta4_doser.lua | 42 +- techage/chemistry/ta4_liquid_filter.lua | 2 + techage/chemistry/ta4_reactor.lua | 2 + techage/chemistry/ta4_stand.lua | 1 + techage/doc/guide.lua | 11 +- techage/doc/items.lua | 11 +- techage/doc/manual_DE.lua | 141 +- techage/doc/manual_EN.lua | 126 +- techage/energy_storage/generator.lua | 3 + techage/energy_storage/heatexchanger1.lua | 8 +- techage/energy_storage/inlet.lua | 10 +- techage/energy_storage/turbine.lua | 7 + techage/furnace/firebox.lua | 4 +- techage/furnace/furnace_top.lua | 12 +- techage/furnace/heater.lua | 6 +- techage/hydrogen/electrolyzer.lua | 4 +- techage/hydrogen/fuelcell.lua | 182 +- techage/i18n.py | 43 +- techage/icta_controller/battery.lua | 4 +- techage/icta_controller/commands.lua | 35 +- techage/icta_controller/controller.lua | 1 + techage/icta_controller/display.lua | 3 + techage/icta_controller/signaltower.lua | 2 + techage/init.lua | 21 +- techage/iron_age/coalburner.lua | 2 +- techage/iron_age/gravelsieve.lua | 1 + techage/iron_age/hammer.lua | 2 +- techage/iron_age/recipes.lua | 2 +- techage/items/basalt.lua | 12 +- techage/items/cement.lua | 13 +- techage/items/ceramic.lua | 55 + techage/items/powder.lua | 23 + techage/lamps/lib.lua | 2 + techage/liquids/liquid_pipe.lua | 4 +- techage/liquids/node_api.lua | 35 +- techage/liquids/tank.lua | 2 +- techage/liquids/valve.lua | 2 + techage/liquids/waterinlet.lua | 120 ++ techage/liquids/waterpump.lua | 18 +- techage/locale/techage.de.tr | 110 +- techage/locale/template.txt | 100 +- techage/logic/button.lua | 10 +- techage/logic/cart_detector.lua | 18 +- techage/logic/doorblock.lua | 5 +- techage/logic/doorcontroller2.lua | 313 +-- techage/logic/gateblock.lua | 5 +- techage/logic/lib.lua | 1 + techage/logic/logic_block.lua | 372 ++++ techage/logic/lua_logic.lua | 20 +- techage/logic/sequencer.lua | 13 +- techage/logic/terminal.lua | 1 + techage/lua_controller/commands.lua | 12 + techage/lua_controller/controller.lua | 14 + techage/lua_controller/server.lua | 110 +- techage/lua_controller/terminal.lua | 1 + techage/manuals/QSG.md | 4 +- techage/manuals/manual_ta2_DE.md | 12 + techage/manuals/manual_ta2_EN.md | 11 + techage/manuals/manual_ta3_DE.md | 81 +- techage/manuals/manual_ta3_EN.md | 69 +- techage/manuals/manual_ta4_DE.md | 56 +- techage/manuals/manual_ta4_EN.md | 52 +- techage/manuals/ta4_lua_controller_EN.md | 1 + techage/manuals/ta4_lua_controller_EN.pdf | Bin 354267 -> 354649 bytes techage/manuals/toc_DE.md | 9 +- techage/manuals/toc_EN.md | 9 +- techage/mod.conf | 2 +- techage/oil/explore.lua | 2 +- techage/oil/gasflare.lua | 2 +- techage/oil/pumpjack.lua | 12 +- techage/oil/tower.lua | 7 + techage/power/drive_axle.lua | 2 + techage/power/electric_cable.lua | 2 + techage/power/junction.lua | 30 +- techage/power/laser.lua | 146 ++ techage/power/power_line.lua | 7 + techage/power/power_terminal.lua | 1 + techage/power/power_terminal2.lua | 286 ++- techage/power/powerswitch.lua | 4 + techage/power/powerswitchbox.lua | 1 + techage/power/steam_pipe.lua | 2 + techage/power/ta4_cable.lua | 17 +- techage/power/ta4_cable_wall_entry.lua | 57 + techage/solar/inverter.lua | 2 + techage/solar/minicell.lua | 1 + techage/solar/solarcell.lua | 4 + techage/ta3_power/akkubox.lua | 2 +- techage/textures/shrink.py | 2 +- techage/textures/shrink.sh | 2 + techage/textures/techage_aluminum_inv.png | Bin 1227 -> 1226 bytes techage/textures/techage_appl_arrow2.png | Bin 0 -> 116 bytes .../textures/techage_appl_chest_front_ta4.png | Bin 280 -> 259 bytes techage/textures/techage_appl_cooler4.png | Bin 688 -> 624 bytes techage/textures/techage_appl_filler.png | Bin 432 -> 385 bytes .../textures/techage_appl_hole_electric.png | Bin 199 -> 178 bytes techage/textures/techage_appl_laser.png | Bin 0 -> 567 bytes techage/textures/techage_appl_laser_hole.png | Bin 0 -> 134 bytes techage/textures/techage_appl_pumpjack.png | Bin 167 -> 164 bytes techage/textures/techage_appl_recycler.png | Bin 2013 -> 565 bytes techage/textures/techage_appl_rinser_top.png | Bin 262 -> 261 bytes techage/textures/techage_appl_sieve4_top.png | Bin 963 -> 942 bytes techage/textures/techage_appl_switch_inv.png | Bin 223 -> 216 bytes techage/textures/techage_ash_side.png | Bin 177 -> 152 bytes techage/textures/techage_axle4.png | Bin 367 -> 364 bytes techage/textures/techage_basalt_glass2.png | Bin 427 -> 405 bytes techage/textures/techage_ceilinglamp.png | Bin 335 -> 213 bytes .../textures/techage_ceilinglamp_bottom.png | Bin 322 -> 226 bytes techage/textures/techage_ceramic_material.png | Bin 0 -> 1068 bytes techage/textures/techage_charcoal_burn.png | Bin 1104 -> 940 bytes techage/textures/techage_concrete.png | Bin 276 -> 191 bytes techage/textures/techage_concrete4.png | Bin 425 -> 364 bytes .../textures/techage_electric_junction.png | Bin 328 -> 306 bytes techage/textures/techage_electric_trowel.png | Bin 297 -> 277 bytes techage/textures/techage_end_wrench.png | Bin 819 -> 751 bytes techage/textures/techage_filling4_ta4.png | Bin 254 -> 233 bytes techage/textures/techage_filling8_ta4.png | Bin 257 -> 219 bytes techage/textures/techage_filling_metal.png | Bin 296 -> 275 bytes techage/textures/techage_firebox.png | Bin 704 -> 609 bytes techage/textures/techage_flame_animated.png | Bin 670 -> 644 bytes techage/textures/techage_form_tank.png | Bin 487 -> 406 bytes techage/textures/techage_form_temp_fg.png | Bin 263 -> 262 bytes techage/textures/techage_frame14_ta2.png | Bin 432 -> 431 bytes techage/textures/techage_frame4_ta2.png | Bin 428 -> 407 bytes techage/textures/techage_frame4_ta2_top.png | Bin 428 -> 407 bytes techage/textures/techage_frame8_ta3.png | Bin 432 -> 411 bytes techage/textures/techage_frame_ta3.png | Bin 398 -> 377 bytes techage/textures/techage_frame_ta3_top.png | Bin 398 -> 377 bytes techage/textures/techage_furnace_ceramic.png | Bin 0 -> 643 bytes techage/textures/techage_gasflare.png | Bin 401 -> 333 bytes techage/textures/techage_gate.png | Bin 508 -> 425 bytes techage/textures/techage_inv_button_error.png | Bin 381 -> 340 bytes .../textures/techage_inv_button_standby.png | Bin 402 -> 359 bytes techage/textures/techage_laser.png | Bin 0 -> 116 bytes techage/textures/techage_lighter_burn.png | Bin 924 -> 793 bytes .../techage_liquid_filter_filler_bottom.png | Bin 281 -> 234 bytes techage/textures/techage_liquidsampler4.png | Bin 662 -> 563 bytes techage/textures/techage_lua_controller.png | Bin 346 -> 288 bytes techage/textures/techage_meridiumaxe.png | Bin 162 -> 157 bytes techage/textures/techage_oil_drill.png | Bin 397 -> 349 bytes techage/textures/techage_oil_tower_top.png | Bin 265 -> 219 bytes techage/textures/techage_powder_inv.png | Bin 827 -> 734 bytes .../textures/techage_power_terminal_front.png | Bin 346 -> 286 bytes .../textures/techage_power_terminal_side.png | Bin 168 -> 166 bytes techage/textures/techage_programmer.png | Bin 879 -> 750 bytes techage/textures/techage_ramchip.png | Bin 593 -> 592 bytes .../textures/techage_reactor_filler_plan.png | Bin 385 -> 350 bytes techage/textures/techage_reactor_side.png | Bin 3237 -> 3172 bytes techage/textures/techage_server2_back.png | Bin 0 -> 272 bytes techage/textures/techage_server2_front.png | Bin 0 -> 269 bytes techage/textures/techage_server2_side.png | Bin 0 -> 231 bytes techage/textures/techage_server2_top.png | Bin 0 -> 169 bytes techage/textures/techage_smoke.png | Bin 256 -> 255 bytes .../textures/techage_solar_cell_mini_side.png | Bin 125 -> 115 bytes techage/textures/techage_steam_knee.png | Bin 242 -> 229 bytes techage/textures/techage_steam_knee2.png | Bin 201 -> 169 bytes techage/textures/techage_ta3b.png | Bin 3802 -> 3778 bytes techage/textures/techage_ta4_cable_hole.png | Bin 0 -> 115 bytes techage/textures/techage_trowel.png | Bin 297 -> 277 bytes techage/textures/techage_tube_junction.png | Bin 0 -> 396 bytes techage/textures/techage_tubeta4_junction.png | Bin 0 -> 404 bytes techage/textures/techage_vacuum_tube.png | Bin 319 -> 311 bytes techage/textures/techage_wlanchip.png | Bin 1052 -> 878 bytes techage/tools/trowel.lua | 2 +- techage/wind_turbine/rotor.lua | 79 +- techage/wind_turbine/signallamp.lua | 2 + techpack_stairway/COPYING.txt | 2 +- techpack_stairway/init.lua | 36 +- techpack_stairway/readme.md | 3 +- towercrane/README.md | 3 +- towercrane/config.lua | 8 +- towercrane/init.lua | 16 +- .../textures/morelights_extras_blocklight.png | Bin 0 -> 204 bytes unified_inventory/.luacheckrc | 1 + unified_inventory/README.md | 4 +- unified_inventory/api.lua | 122 +- unified_inventory/bags.lua | 81 +- unified_inventory/callbacks.lua | 38 + unified_inventory/category.lua | 149 ++ unified_inventory/default-categories.lua | 704 +++++++ unified_inventory/doc/mod_api.txt | 77 + unified_inventory/init.lua | 115 +- unified_inventory/internal.lua | 345 ++-- unified_inventory/mod.conf | 1 + unified_inventory/register.lua | 261 +-- unified_inventory/settingtypes.txt | 3 + .../textures/ui_bags_lg_form.png | Bin 8119 -> 0 bytes .../textures/ui_bags_main_form.png | Bin 6068 -> 0 bytes .../textures/ui_bags_med_form.png | Bin 7197 -> 0 bytes .../textures/ui_bags_sm_form.png | Bin 6411 -> 0 bytes unified_inventory/textures/ui_bags_trash.png | Bin 1382 -> 0 bytes .../textures/ui_category_all.png | Bin 0 -> 1233 bytes .../textures/ui_category_none.png | Bin 0 -> 7966 bytes .../textures/ui_craftguide_form.png | Bin 962 -> 0 bytes .../textures/ui_crafting_arrow.png | Bin 0 -> 788 bytes .../textures/ui_crafting_form.png | Bin 2343 -> 0 bytes unified_inventory/textures/ui_form_bg.png | Bin 1650 -> 0 bytes .../textures/ui_formbg_9_sliced.png | Bin 0 -> 240 bytes .../textures/ui_main_inventory.png | Bin 4112 -> 0 bytes unified_inventory/textures/ui_misc_form.png | Bin 5368 -> 0 bytes unified_inventory/textures/ui_single_slot.png | Bin 629 -> 648 bytes .../textures/ui_single_slot_bright.png | Bin 0 -> 1032 bytes .../textures/ui_smallbg_9_sliced.png | Bin 0 -> 139 bytes .../textures/ui_trash_slot_icon.png | Bin 0 -> 697 bytes .../textures/ui_xyz_off_icon.png | Bin 8606 -> 0 bytes unified_inventory/textures/ui_xyz_on_icon.png | Bin 2182 -> 0 bytes unified_inventory/waypoints.lua | 127 +- 344 files changed, 10258 insertions(+), 8400 deletions(-) create mode 100644 basic_materials/.gitlab-ci.yml create mode 100644 minecart/api.lua create mode 100644 minecart/baselib.lua delete mode 100644 minecart/cart_lib1.lua delete mode 100644 minecart/cart_lib2e.lua delete mode 100644 minecart/cart_lib2n.lua delete mode 100644 minecart/cart_lib3.lua create mode 100644 minecart/entitylib.lua create mode 100644 minecart/hopperlib.lua delete mode 100644 minecart/lib.lua create mode 100644 minecart/nodelib.lua create mode 100644 minecart/pusher.lua create mode 100644 minecart/rails.lua create mode 100644 minecart/signs.lua create mode 100644 minecart/terminal.lua create mode 100644 minecart/textures/minecart_appl_cart_top.png create mode 100644 minecart/textures/minecart_marker_cube.png create mode 100644 minecart/textures/minecart_pusher.png create mode 100644 minecart/textures/minecart_pusher_top.png create mode 100644 minecart/textures/minecart_sign1.png create mode 100644 minecart/textures/minecart_sign2.png create mode 100644 minecart/textures/minecart_sign4.png create mode 100644 minecart/textures/minecart_sign8.png create mode 100644 minecart/textures/minecart_terminal_back.png create mode 100644 minecart/textures/minecart_terminal_front.png create mode 100644 minecart/textures/minecart_terminal_side.png create mode 100644 minecart/textures/minecart_terminal_top.png create mode 100644 minecart/textures/minecart_tool.png create mode 100644 minecart/textures/minecart_waypoint.png delete mode 100644 minecart/textures/shrink.py create mode 100755 minecart/textures/shrink.sh create mode 100644 minecart/tool.lua create mode 100644 signs_bot/compost.lua create mode 100644 signs_bot/i18n.py delete mode 100644 signs_bot/intllib.lua delete mode 100644 signs_bot/locale/de.mo delete mode 100644 signs_bot/locale/de.po delete mode 100644 signs_bot/locale/de.po~ create mode 100644 signs_bot/locale/signs_bot.de.tr delete mode 100644 signs_bot/locale/template.pot create mode 100644 signs_bot/locale/template.txt create mode 100644 techage/basic_machines/concentrator.lua create mode 100644 techage/basis/laser_lib.lua create mode 100644 techage/items/ceramic.lua create mode 100644 techage/liquids/waterinlet.lua create mode 100644 techage/logic/logic_block.lua create mode 100644 techage/power/laser.lua create mode 100644 techage/power/ta4_cable_wall_entry.lua create mode 100755 techage/textures/shrink.sh create mode 100644 techage/textures/techage_appl_arrow2.png create mode 100644 techage/textures/techage_appl_laser.png create mode 100644 techage/textures/techage_appl_laser_hole.png create mode 100644 techage/textures/techage_ceramic_material.png create mode 100644 techage/textures/techage_furnace_ceramic.png create mode 100644 techage/textures/techage_laser.png create mode 100644 techage/textures/techage_server2_back.png create mode 100644 techage/textures/techage_server2_front.png create mode 100644 techage/textures/techage_server2_side.png create mode 100644 techage/textures/techage_server2_top.png create mode 100644 techage/textures/techage_ta4_cable_hole.png create mode 100644 techage/textures/techage_tube_junction.png create mode 100644 techage/textures/techage_tubeta4_junction.png create mode 100644 towercrane/textures/morelights_extras_blocklight.png create mode 100644 unified_inventory/category.lua create mode 100644 unified_inventory/default-categories.lua delete mode 100644 unified_inventory/textures/ui_bags_lg_form.png delete mode 100644 unified_inventory/textures/ui_bags_main_form.png delete mode 100644 unified_inventory/textures/ui_bags_med_form.png delete mode 100644 unified_inventory/textures/ui_bags_sm_form.png delete mode 100644 unified_inventory/textures/ui_bags_trash.png create mode 100644 unified_inventory/textures/ui_category_all.png create mode 100644 unified_inventory/textures/ui_category_none.png delete mode 100644 unified_inventory/textures/ui_craftguide_form.png create mode 100644 unified_inventory/textures/ui_crafting_arrow.png delete mode 100644 unified_inventory/textures/ui_crafting_form.png delete mode 100644 unified_inventory/textures/ui_form_bg.png create mode 100644 unified_inventory/textures/ui_formbg_9_sliced.png delete mode 100644 unified_inventory/textures/ui_main_inventory.png delete mode 100644 unified_inventory/textures/ui_misc_form.png create mode 100644 unified_inventory/textures/ui_single_slot_bright.png create mode 100644 unified_inventory/textures/ui_smallbg_9_sliced.png create mode 100644 unified_inventory/textures/ui_trash_slot_icon.png delete mode 100644 unified_inventory/textures/ui_xyz_off_icon.png delete mode 100644 unified_inventory/textures/ui_xyz_on_icon.png diff --git a/autobahn/init.lua b/autobahn/init.lua index 5083365..a55b0ad 100644 --- a/autobahn/init.lua +++ b/autobahn/init.lua @@ -184,7 +184,9 @@ local function update_node(pos) nnode = minetest.get_node(npos) if NodeTbl1[nnode.name] and NodeTbl3[node.name] then node.name = node.name .. "1" - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 2 @@ -192,7 +194,9 @@ local function update_node(pos) nnode = minetest.get_node(npos) if NodeTbl2[nnode.name] then node.name = string.sub(node.name,1,-1) .. "2" - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 3 @@ -203,7 +207,9 @@ local function update_node(pos) if NodeTbl1[nnode.name] and NodeTbl3[node.name] then node.name = node.name .. "1" node.param2 = 3 - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 4 @@ -212,7 +218,9 @@ local function update_node(pos) if NodeTbl2[nnode.name] then node.name = string.sub(node.name,1,-1) .. "2" node.param2 = 3 - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end end diff --git a/basic_materials/.gitlab-ci.yml b/basic_materials/.gitlab-ci.yml new file mode 100644 index 0000000..ff51e7e --- /dev/null +++ b/basic_materials/.gitlab-ci.yml @@ -0,0 +1,8 @@ +stages: + - test + +luacheck: + stage: test + image: pipelinecomponents/luacheck:latest + script: + - luacheck . diff --git a/compost/init.lua b/compost/init.lua index 561e1c9..b2b6395 100644 --- a/compost/init.lua +++ b/compost/init.lua @@ -2,7 +2,7 @@ local S = minetest.get_translator("compost") compost = {} -local CYCLE_TIME = 10 +local CYCLE_TIME = 30 -- Version for compatibility checks compost.version = 1.0 diff --git a/hyperloop/README.md b/hyperloop/README.md index fc20f34..f1cd423 100644 --- a/hyperloop/README.md +++ b/hyperloop/README.md @@ -11,8 +11,7 @@ It is the fast and modern way of travelling. * It can be used even on small servers without lagging * No configuration or programming of the tube network is necessary (only the station names have to be entered) - -**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** +**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** ![screenshot](https://github.com/joe7575/Minetest-Hyperloop/blob/master/screenshot.png) @@ -33,9 +32,9 @@ The mod includes many different kind of blocks: ..and more. -Browse on: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop) +Browse on: [GitHub](https://github.com/joe7575/Minetest-Hyperloop) -Download: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip) +Download: [GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip) ## Migration from v1 to v2 @@ -59,7 +58,7 @@ has some risks. Therefore: ## Introduction -**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** +**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** ## Configuration @@ -68,23 +67,25 @@ The following can be changed in the minetest menu (Settings -> Advanced Settings * "WiFi block crafting enabled" - To enable the crafting of WiFi blocks (default: false) * "free tube placement enabled" - If enabled Hyperloop Tubes and Elevator Shafts can be build in all directions (default: true) When this option is disabled, Hyperloop tubes can only be built in the horizontal direction and elevator shafts in the vertical direction. +* "enable building of subnets" - If enabled the ticket block has an additional field for specifying a subnet name. Stations with the same subnet name (optional) represent an isolated subnet within the Hyperloop network. Example for 'minetest.conf': ```LUA -hyperloop_wifi_enabled = true -hyperloop_wifi_crafting_enabled = false -hyperloop_free_tube_placement_enabled = true +hyperloop_wifi_enabled = true -- WiFi block enabled +hyperloop_wifi_crafting_enabled = false -- WiFi block crafting enabled +hyperloop_free_tube_placement_enabled = true -- free tube placement enabled +hyperloop_subnet_enabled = true -- enable building of subnets ``` ## Dependencies -tubelib2 (![GitHub](https://github.com/joe7575/tubelib2)) +tubelib2 ([GitHub](https://github.com/joe7575/tubelib2)) default intllib optional: worldedit, techage # License -Copyright (C) 2017,2020 Joachim Stolberg +Copyright (C) 2017,2021 Joachim Stolberg Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt Textures: CC0 Display: Derived from the work of kaeza, sofar and others (digilines) LGPLv2.1+ diff --git a/hyperloop/booking_node.lua b/hyperloop/booking_node.lua index 494d4cb..4b4809d 100644 --- a/hyperloop/booking_node.lua +++ b/hyperloop/booking_node.lua @@ -75,35 +75,72 @@ local function remove_junctions(sortedList) return tbl end -local function station_list_as_string(pos) - -- Generate a distance sorted list of all connected stations - local sortedList = Stations:station_list(pos, pos, "dist") - -- Delete the own station from list - table.remove(sortedList, 1) +local function filter_subnet(sortedList, subnet) + if hyperloop.subnet_enabled then + if subnet == "" then + subnet = nil + end + + local tbl = {} + for idx,item in ipairs(sortedList) do + if item.subnet == subnet then + tbl[#tbl+1] = item + end + end + return tbl + end + return sortedList +end + +-- Used to update the station list for booking machine +-- and teleport list. +local function station_list_as_string(pos, subnet) + local meta = M(pos) + -- Generate a name sorted list of all connected stations + local sortedList = Stations:station_list(pos, pos, "name") -- remove all junctions from the list sortedList = remove_junctions(sortedList) + -- use subnet pattern to reduce the list + sortedList = filter_subnet(sortedList, subnet) -- store the list for later use store_station_list(pos, sortedList) -- Generate the formspec string return generate_string(sortedList) end +local naming_formspec = nil -local function naming_formspec(pos) - local meta = minetest.get_meta(pos) - local formspec = "size[6,4]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. - "field[0.5,1.5;5,1;name;"..S("Station name")..";MyTown]" .. - "field[0.5,2.7;5,1;info;"..S("Additional station information")..";]" .. - "button_exit[2,3.6;2,1;exit;Save]" - meta:set_string("formspec", formspec) - meta:set_int("change_counter", 0) +if hyperloop.subnet_enabled then + naming_formspec = function(pos) + local meta = M(pos) + local formspec = "size[7,5.4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. + "field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" .. + "field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" .. + "field[0.2,3.9;7.1,1;subnet;"..S("Subnet name (optional)")..";]" .. + "button_exit[2.5,4.7;2,1;exit;Save]" + meta:set_string("formspec", formspec) + meta:set_int("change_counter", 0) + end +else + naming_formspec = function(pos) + local meta = M(pos) + local formspec = "size[7,4.4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. + "field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" .. + "field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" .. + "button_exit[2.5,3.7;2,1;exit;Save]" + meta:set_string("formspec", formspec) + meta:set_int("change_counter", 0) + end end - local function booking_machine_update(pos) local meta = M(pos) local sStationPos = meta:get_string("sStationPos") @@ -111,25 +148,19 @@ local function booking_machine_update(pos) local station_pos = P(sStationPos) local counter = meta:get_int("change_counter") or 0 local changed, newcounter = Stations:changed(counter) - if changed then - meta:set_string("formspec", station_list_as_string(station_pos)) + if changed or not tStationList[sStationPos] then + local subnet = meta:get_string("subnet") + meta:set_string("formspec", station_list_as_string(station_pos, subnet)) meta:set_int("change_counter", newcounter) end - if not tStationList[sStationPos] then - local sortedList = Stations:station_list(station_pos, station_pos, "dist") - -- Delete the own station from list - table.remove(sortedList, 1) - -- remove all junctions from the list - sortedList = remove_junctions(sortedList) - -- store the list for later use - store_station_list(station_pos, sortedList) - end end end +local function on_rightclick(pos) + booking_machine_update(pos) +end local function on_receive_fields(pos, formname, fields, player) - booking_machine_update(pos) -- station name entered? if fields.name ~= nil then local station_name = string.trim(fields.name) @@ -142,17 +173,25 @@ local function on_receive_fields(pos, formname, fields, player) hyperloop.chat(player, S("Station has already a booking machine!")) return end + -- add subnet name if available + local subnet = string.trim(fields.subnet or "") + if subnet == "" then + subnet = nil + end -- store meta and generate station formspec Stations:update(stationPos, { name = station_name, booking_pos = pos, booking_info = string.trim(fields.info), + subnet = subnet, }) local meta = M(pos) meta:set_string("sStationPos", SP(stationPos)) meta:set_string("infotext", "Station: "..station_name) - meta:set_string("formspec", station_list_as_string(stationPos)) + meta:set_string("subnet", string.trim(fields.subnet or "")) + meta:set_int("change_counter", 0) -- force update + booking_machine_update(pos) else hyperloop.chat(player, S("Invalid station name!")) end @@ -233,6 +272,7 @@ minetest.register_node("hyperloop:booking", { on_rotate = screwdriver.disallow, on_receive_fields = on_receive_fields, on_destruct = on_destruct, + on_rightclick = on_rightclick, paramtype = 'light', light_source = 2, @@ -279,10 +319,4 @@ minetest.register_node("hyperloop:booking_ground", { }) -minetest.register_lbm({ - label = "[Hyperloop] Booking machine update", - name = "hyperloop:update", - nodenames = {"hyperloop:booking", "hyperloop:booking_ground"}, - run_at_every_load = true, - action = booking_machine_update -}) + diff --git a/hyperloop/init.lua b/hyperloop/init.lua index 2d4cff9..8798e07 100644 --- a/hyperloop/init.lua +++ b/hyperloop/init.lua @@ -5,7 +5,7 @@ v2.06 by JoSt - Copyright (C) 2017-2019 Joachim Stolberg + Copyright (C) 2017-2021 Joachim Stolberg LGPLv2.1+ See LICENSE.txt for more information @@ -34,6 +34,7 @@ 2020-01-03 v2.04 Elevator door bugfix (MT 5+) 2020-03-12 v2.05 minetest translator added (thanks to acmgit/Clyde) 2020-06-14 v2.06 The default value for `hyperloop_free_tube_placement_enabled` is now true + 2021-02-07 v2.07 tube_crowbar: Add tube length check ]]-- @@ -65,7 +66,8 @@ else hyperloop.wifi_enabled = minetest.settings:get_bool("hyperloop_wifi_enabled") hyperloop.wifi_crafting_enabled = minetest.settings:get_bool("hyperloop_wifi_crafting_enabled") hyperloop.free_tube_placement_enabled = minetest.settings:get_bool("hyperloop_free_tube_placement_enabled", true) - + hyperloop.subnet_enabled = minetest.settings:get_bool("hyperloop_subnet_enabled") + dofile(minetest.get_modpath("hyperloop") .. "/network.lua") dofile(minetest.get_modpath("hyperloop") .. "/data_base.lua") dofile(minetest.get_modpath("hyperloop") .. "/booking.lua") diff --git a/hyperloop/network.lua b/hyperloop/network.lua index ddb8fa8..ea9611f 100644 --- a/hyperloop/network.lua +++ b/hyperloop/network.lua @@ -96,6 +96,15 @@ local function sort_based_on_distance(tStations, pos) return lStations end +-- Return a list with sorted stations +local function sort_based_on_name(tStations, pos) + local lStations = table_to_list(table.copy(tStations)) + -- Add distance + lStations = add_distance_to_list(lStations, pos) + table.sort(lStations, function(a,b) return a.name < b.name end) + return lStations +end + -- -- Class Network @@ -233,8 +242,12 @@ function Network:station_list(pos, station_pos, sorted) end if sorted == "dist" then lStations = sort_based_on_distance(tStations, pos) - else + elseif sorted == "level" then lStations = sort_based_on_level(tStations) + else + -- delete own station from list + tStations[S(station_pos)] = nil + lStations = sort_based_on_name(tStations, pos) end return lStations end @@ -263,4 +276,4 @@ end function Network:serialize() return minetest.serialize(self) end - \ No newline at end of file + diff --git a/hyperloop/settingtypes.txt b/hyperloop/settingtypes.txt index 1fc0431..2c2e90e 100644 --- a/hyperloop/settingtypes.txt +++ b/hyperloop/settingtypes.txt @@ -8,3 +8,8 @@ hyperloop_wifi_crafting_enabled (WiFi block crafting enabled) bool false # If disabled, connected stations have to be on one level, # typically underground. hyperloop_free_tube_placement_enabled (free tube placement enabled) bool false + +# The ticket block has an additional field for specifying a subnet name. +# Stations with the same subnet name (optional) represent an isolated +# subnet within the Hyperloop network. +hyperloop_subnet_enabled (enable building of subnets) bool false \ No newline at end of file diff --git a/minecart/README.md b/minecart/README.md index 27a267c..0a9f5c2 100644 --- a/minecart/README.md +++ b/minecart/README.md @@ -4,9 +4,9 @@ Minecart **Minecart, the lean railway transportation automation system** -Browse on: ![GitHub](https://github.com/joe7575/minecart) +Browse on: [GitHub](https://github.com/joe7575/minecart) -Download: ![GitHub](https://github.com/joe7575/minecart/archive/master.zip) +Download: [GitHub](https://github.com/joe7575/minecart/archive/master.zip) ![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png) @@ -26,21 +26,15 @@ license). 3. https://github.com/stujones11/railcart/ -Original Cart Features ----------------------- - -- A fast cart for your railway or roller coaster (up to 7 m/s!) -- Boost and brake rails -- Rail junction switching with the 'right-left' walking keys -- Handbrake with the 'back' key - - Minecart Features ----------------- The mod Minecart has its own cart (called Minecart) in addition to the standard cart. Minecarts are used for automated item transport on private and public rail networks. The mod features are: +- a fast cart for your railway or roller coaster (up to 8 m/s!) +- boost rails and speed limit signs +- rail junction switching with the 'right-left' walking keys - configurable timetables and routes for Minecarts - automated loading/unloading of Minecarts by means of a Minecart Hopper - rail network protection based on protection blocks called Land Marks @@ -73,17 +67,16 @@ Introduction 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart) 6. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge") -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters) 9. Place a Minecart in front of the buffer and check whether it starts after the configured time 10. Check the cart state via the chat command: /mycart - '' is the cart number + '' is the cart number, or get a list of carts with /mycart 11. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the - Minecart to get the items back -12. Dig the empty cart with a second "sneak+click" (as usual) + Minecart to get cart and items back Hopper @@ -97,6 +90,43 @@ to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart. +Cart Pusher +----------- + +Used to push a cart if the cart does not stop directly at a buffer. +The block has to be placed below the rail. + + +Cart Speed / Speed Limit Signs +------------------------------ + +As before, the speed of the carts is also influenced by power rails. +Brake rails are irrelevant, the cart does not brake here. +The maximum speed is 8 m/s. This assumes a ratio of power rails +to normal rails of 1 to 4 on a flat section of rail. A rail section is a +series of rail nodes without a change of direction. After every curve / kink, +the speed for the next section of the route is newly determined, +taking into account the swing of the cart. This means that a cart can +roll over short rail sections without power rails. + +In order to additionally brake the cart at certain points +(at switches or in front of a buffer), speed limit signs can be placed +on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s. +The "No speed limit" sign can be used to remove the speed limit. + +The speed limit signs must be placed next to the track so that they can +be read from the cart. This allows different speeds in each direction of travel. + + +Migration to v2 +--------------- + +The way how carts are monitored and the cart speed is calculated has changed. +Therefore, it is necessary that all carts are repositioned and the +recording is repeated. +Rails and buffers are not affected and can be kept unchanged. + + History ------- @@ -117,4 +147,6 @@ History 2020-06-27 v1.07 Route storage and cart command bugfixes 2020-07-24 V1.08 Adapted to new techage ICTA style 2020-08-14 V1.09 Hopper support for digtron, protector:chest and default:furnace added -2020-11-12 V1.10 Make carts more robust against server lag +2020-11-12 V1.10 Make carts more robust against server lag +2021-04-10 V2.00 Complete revision to make carts robust against server load/lag, + Speed limit signs and cart terminal added diff --git a/minecart/api.lua b/minecart/api.lua new file mode 100644 index 0000000..d93e188 --- /dev/null +++ b/minecart/api.lua @@ -0,0 +1,52 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- +-- API functions +-- + +-- 'pos' is the position of the puncher/sensor, the cart +-- position will be determined by means of 'param2' and 'radius' +function minecart.is_cart_available(pos, param2, radius) + local pos2 = minecart.get_nodecart_nearby(pos, param2, radius) + if pos2 then + return true + end + -- The entity check is needed for a cart with driver + local entity = minecart.get_entitycart_nearby(pos, param2, radius) + if entity then + return true + end +end + +function minecart.is_nodecart_available(pos, param2, radius) + local pos2 = minecart.get_nodecart_nearby(pos, param2, radius) + if pos2 then + return true + end +end + +-- 'pos' is the position of the puncher/sensor, the cart +-- position will be determined by means of 'param2' and 'radius' +function minecart.punch_cart(pos, param2, radius, punch_dir) + local pos2, node = minecart.get_nodecart_nearby(pos, param2, radius) + if pos2 then + minecart.start_nodecart(pos2, node.name, nil, punch_dir) + return true + end + -- The entity check is needed for a cart with driver + local entity = minecart.get_entitycart_nearby(pos, param2, radius) + if entity and entity.driver then + minecart.push_entitycart(entity, punch_dir) + return true + end +end \ No newline at end of file diff --git a/minecart/baselib.lua b/minecart/baselib.lua new file mode 100644 index 0000000..d0c035a --- /dev/null +++ b/minecart/baselib.lua @@ -0,0 +1,360 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2020 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash + +local param2_to_dir = {[0]= + {x=0, y=0, z=1}, + {x=1, y=0, z=0}, + {x=0, y=0, z=-1}, + {x=-1, y=0, z=0}, + {x=0, y=-1, z=0}, + {x=0, y=1, z=0} +} + +-- Registered carts +minecart.tNodeNames = {} -- [] = +minecart.tEntityNames = {} -- [] = true +minecart.lCartNodeNames = {} -- {, , ...} +minecart.tCartTypes = {} + +function minecart.param2_to_dir(param2) + return param2_to_dir[param2 % 6] +end + +function minecart.get_node_lvm(pos) + local node = minetest.get_node_or_nil(pos) + if node then + return node + end + local vm = minetest.get_voxel_manip() + local MinEdge, MaxEdge = vm:read_from_map(pos, pos) + local data = vm:get_data() + local param2_data = vm:get_param2_data() + local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) + local idx = area:indexp(pos) + if data[idx] and param2_data[idx] then + return { + name = minetest.get_name_from_content_id(data[idx]), + param2 = param2_data[idx] + } + end + return {name="ignore", param2=0} +end + +function minecart.find_node_near_lvm(pos, radius, items) + local npos = minetest.find_node_near(pos, radius, items) + if npos then + return npos + end + local tItems = {} + for _,v in ipairs(items) do + tItems[v] = true + end + local pos1 = {x = pos.x - radius, y = pos.y - radius, z = pos.z - radius} + local pos2 = {x = pos.x + radius, y = pos.y + radius, z = pos.z + radius} + local vm = minetest.get_voxel_manip() + local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2) + local data = vm:get_data() + local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) + for x = pos1.x, pos2.x do + for y = pos1.y, pos2.y do + for z = pos1.z, pos2.z do + local idx = area:indexp({x = x, y = y, z = z}) + if minetest.get_name_from_content_id(data[idx]) then + return {x = x, y = y, z = z} + end + end + end + end +end + +-- Marker entities for debugging purposes +function minecart.set_marker(pos, text, size, ttl) + local marker = minetest.add_entity(pos, "minecart:marker_cube") + if marker ~= nil then + marker:set_nametag_attributes({color = "#FFFFFF", text = text}) + size = size or 1 + marker:set_properties({visual_size = {x = size, y = size}}) + if ttl then + minetest.after(ttl, marker.remove, marker) + end + end +end + +minetest.register_entity(":minecart:marker_cube", { + initial_properties = { + visual = "cube", + textures = { + "minecart_marker_cube.png", + "minecart_marker_cube.png", + "minecart_marker_cube.png", + "minecart_marker_cube.png", + "minecart_marker_cube.png", + "minecart_marker_cube.png", + }, + physical = false, + visual_size = {x = 1, y = 1}, + collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25}, + glow = 8, + static_save = false, + }, + on_punch = function(self) + self.object:remove() + end, +}) + +function minecart.is_air_like(name) + local ndef = minetest.registered_nodes[name] + if ndef and ndef.buildable_to then + return true + end + return false +end + +function minecart.range(val, min, max) + val = tonumber(val) + if val < min then return min end + if val > max then return max end + return val +end + +function minecart.get_next_node(pos, param2) + local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos + local node = minetest.get_node(pos2) + return pos2, node +end + +function minecart.get_object_id(object) + for id, entity in pairs(minetest.luaentities) do + if entity.object == object then + return id + end + end +end + +function minecart.is_owner(player, owner) + if not player or not player:is_player() or not owner or owner == "" then + return true + end + + local name = player:get_player_name() + if minetest.check_player_privs(name, "minecart") then + return true + end + return name == owner +end + +function minecart.get_buffer_pos(pos, player_name) + local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"}) + if pos1 then + local meta = minetest.get_meta(pos1) + if player_name == nil or player_name == meta:get_string("owner") then + return pos1 + end + end +end + +function minecart.get_buffer_name(pos) + local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"}) + if pos1 then + local name = M(pos1):get_string("name") + if name ~= "" then + return name + end + return P2S(pos1) + end +end + +function minecart.manage_attachment(player, entity, get_on) + if not player then + return + end + local player_name = player:get_player_name() + if player_api.player_attached[player_name] == get_on then + return + end + player_api.player_attached[player_name] = get_on + + local obj = entity.object + if get_on then + player:set_attach(obj, "", {x=0, y=-4.5, z=-4}, {x=0, y=0, z=0}) + player:set_eye_offset({x=0, y=-6, z=0},{x=0, y=-6, z=0}) + player:set_properties({visual_size = {x = 2.5, y = 2.5}}) + player_api.set_animation(player, "sit") + entity.driver = player:get_player_name() + else + player:set_detach() + player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0}) + player:set_properties({visual_size = {x = 1, y = 1}}) + player_api.set_animation(player, "stand") + entity.driver = nil + end +end + +function minecart.register_cart_names(node_name, entity_name, cart_type) + minecart.tNodeNames[node_name] = entity_name + minecart.tEntityNames[entity_name] = true + minecart.lCartNodeNames[#minecart.lCartNodeNames+1] = node_name + minecart.add_raillike_nodes(node_name) + minecart.tCartTypes[node_name] = cart_type +end + +function minecart.add_nodecart(pos, node_name, param2, cargo, owner, userID) + if pos and node_name and param2 and cargo and owner and userID then + local pos2 + if not minecart.is_rail(pos) then + pos2 = minetest.find_node_near(pos, 1, minecart.lRails) + if not pos2 or not minecart.is_rail(pos2) then + pos2 = minetest.find_node_near(pos, 2, minecart.lRails) + if not pos2 or not minecart.is_rail(pos2) then + pos2 = minetest.find_node_near(pos, 2, {"air"}) + end + end + else + pos2 = vector.new(pos) + end + if pos2 then + local node = minetest.get_node(pos2) + local ndef = minetest.registered_nodes[node_name] + local rail = node.name + minetest.swap_node(pos2, {name = node_name, param2 = param2}) + local meta = M(pos2) + meta:set_string("removed_rail", rail) + meta:set_string("owner", owner) + meta:set_int("userID", userID) + meta:set_string("infotext", owner .. ": " .. userID) + + if cargo and ndef.set_cargo then + ndef.set_cargo(pos2, cargo) + end + if ndef.after_place_node then + ndef.after_place_node(pos2) + end + return pos2 + else + minetest.add_item(pos, ItemStack({name = node_name})) + end + end +end + +function minecart.add_entitycart(pos, node_name, entity_name, vel, cargo, owner, userID) + local obj = minetest.add_entity(pos, entity_name) + local objID = minecart.get_object_id(obj) + + if objID then + local entity = obj:get_luaentity() + entity.start_pos = pos + entity.owner = owner + entity.node_name = node_name + entity.userID = userID + entity.objID = objID + entity.cargo = cargo + obj:set_nametag_attributes({color = "#ffff00", text = owner..": "..userID}) + obj:set_velocity(vel) + return obj + end +end + +function minecart.start_entitycart(self, pos) + local route = {} + + self.is_running = true + self.arrival_time = 0 + self.start_pos = minecart.get_buffer_pos(pos, self.owner) + if self.start_pos then + -- Read buffer route for the junction info + route = minecart.get_route(self.start_pos) or {} + self.junctions = route and route.junctions + end + -- If set the start waypoint will be deleted + self.no_normal_start = self.start_pos == nil + if self.driver == nil then + minecart.start_monitoring(self.owner, self.userID, pos, self.objID, + route.checkpoints, route.junctions, self.cargo or {}) + end +end + +function minecart.remove_nodecart(pos) + local node = minetest.get_node(pos) + local ndef = minetest.registered_nodes[node.name] + local meta = M(pos) + local rail = meta:get_string("removed_rail") + if rail == "" then rail = "air" end + local userID = meta:get_int("userID") + local owner = meta:get_string("owner") + meta:set_string("infotext", "") + meta:set_string("formspec", "") + local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {} + minetest.swap_node(pos, {name = rail}) + return cargo, owner, userID +end + +function minecart.node_to_entity(pos, node_name, entity_name) + -- Remove node + local cargo, owner, userID = minecart.remove_nodecart(pos) + local obj = minecart.add_entitycart(pos, node_name, entity_name, + {x = 0, y = 0, z = 0}, cargo, owner, userID) + if obj then + return obj + else + print("Entity has no ID") + end +end + +function minecart.entity_to_node(pos, entity) + -- Stop sound + if entity.sound_handle then + minetest.sound_stop(entity.sound_handle) + entity.sound_handle = nil + end + + local rot = entity.object:get_rotation() + local dir = minetest.yaw_to_dir(rot.y) + local facedir = minetest.dir_to_facedir(dir) + minecart.stop_recording(entity, pos) + entity.object:remove() + local pos2 = minecart.add_nodecart(pos, entity.node_name, facedir, entity.cargo, entity.owner, entity.userID) + minecart.stop_monitoring(entity.owner, entity.userID, pos2) +end + +function minecart.add_node_to_player_inventory(pos, player, node_name) + local inv = player:get_inventory() + if not (creative and creative.is_enabled_for + and creative.is_enabled_for(player:get_player_name())) + or not inv:contains_item("main", node_name) then + local leftover = inv:add_item("main", node_name) + -- If no room in inventory, drop the cart + if not leftover:is_empty() then + minetest.add_item(pos, leftover) + end + end +end + +-- Player removes the node +function minecart.remove_entity(self, pos, player) + -- Stop sound + if self.sound_handle then + minetest.sound_stop(self.sound_handle) + self.sound_handle = nil + end + minecart.add_node_to_player_inventory(pos, player, self.node_name or "minecart:cart") + minecart.stop_monitoring(self.owner, self.userID, pos) + minecart.stop_recording(self, pos) + minecart.monitoring_remove_cart(self.owner, self.userID) + self.object:remove() +end diff --git a/minecart/buffer.lua b/minecart/buffer.lua index 89e642b..3c2100c 100644 --- a/minecart/buffer.lua +++ b/minecart/buffer.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -23,21 +23,17 @@ local StopTime = {} local function formspec(pos) local name = M(pos):get_string("name") local time = M(pos):get_int("time") - local s = "size[4,4.2]" .. + return "size[4,4.2]" .. "label[0,0;Configuration]" .. "field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]".. - "button_exit[1,3.4;2,1;exit;Save]" - if minecart.hopper_enabled then - return s.."field[0.5,2.5;3.6,1;time;"..S("Stop time/sec")..":;"..time.."]" - end - return s + "button_exit[1,3.4;2,1;exit;Save]".. + "field[0.5,2.5;3.6,1;time;"..S("Waiting time/sec")..":;"..time.."]" end local function remote_station_name(pos) - local route = minecart.get_route(P2S(pos)) + local route = minecart.get_route(pos) if route and route.dest_pos then - local pos2 = S2P(route.dest_pos) - return M(pos2):get_string("name") + return M(route.dest_pos):get_string("name") end return "none" end @@ -46,23 +42,21 @@ local function on_punch(pos, node, puncher) local name = M(pos):get_string("name") M(pos):set_string("infotext", name..": "..S("connected to").." "..remote_station_name(pos)) M(pos):set_string("formspec", formspec(pos)) - if minecart.hopper_enabled then - minetest.get_node_timer(pos):start(CYCLE_TIME) - end + minetest.get_node_timer(pos):start(CYCLE_TIME) + -- Optional Teleport function if not minecart.teleport_enabled then return end - local route = minecart.get_route(P2S(pos)) + local route = minecart.get_route(pos) if route and route.dest_pos and puncher and puncher:is_player() then -- only teleport if the user is not pressing shift if not puncher:get_player_control()['sneak'] then local playername = puncher:get_player_name() - local pos = S2P(route.dest_pos) local teleport = function() -- Make sure the player object still exists local player = minetest.get_player_by_name(playername) - if player and pos then player:set_pos(pos) end + if player then player:set_pos(route.dest_pos) end end minetest.after(0.25, teleport) end @@ -95,24 +89,21 @@ minetest.register_node("minecart:buffer", { }, after_place_node = function(pos, placer) M(pos):set_string("owner", placer:get_player_name()) - minecart.del_route(minetest.pos_to_string(pos)) + minecart.del_route(pos) M(pos):set_string("formspec", formspec(pos)) - if minecart.hopper_enabled then - minetest.get_node_timer(pos):start(CYCLE_TIME) - end + minetest.get_node_timer(pos):start(CYCLE_TIME) end, on_timer = function(pos, elapsed) local time = M(pos):get_int("time") if time > 0 then local hash = minetest.hash_node_position(pos) local param2 = (minetest.get_node(pos).param2 + 2) % 4 - if minecart.check_cart_for_pushing(pos, param2) then + if minecart.is_cart_available(pos, param2, 0.5) then if StopTime[hash] then if StopTime[hash] < minetest.get_gametime() then StopTime[hash] = nil - local node = minetest.get_node(pos) - local dir = minetest.facedir_to_dir(node.param2) - minecart.punch_cart(pos, param2, 0, dir) + local dir = minetest.facedir_to_dir(param2) + minecart.punch_cart(pos, param2, 0.5, dir) end else StopTime[hash] = minetest.get_gametime() + time @@ -124,7 +115,7 @@ minetest.register_node("minecart:buffer", { return true end, after_dig_node = function(pos) - minecart.del_route(minetest.pos_to_string(pos)) + minecart.del_route(pos) local hash = minetest.hash_node_position(pos) StopTime[hash] = nil end, @@ -137,6 +128,7 @@ minetest.register_node("minecart:buffer", { M(pos):set_int("time", tonumber(fields.time) or 0) M(pos):set_string("formspec", formspec(pos)) M(pos):set_string("infotext", fields.name.." "..S("connected to").." "..remote_station_name(pos)) + minetest.get_node_timer(pos):start(CYCLE_TIME) end end, on_punch = on_punch, @@ -156,3 +148,15 @@ minetest.register_craft({ {"default:steel_ingot", "default:junglewood", "default:steel_ingot"}, }, }) + +minetest.register_lbm({ + label = "Delete waiting times", + name = "minecart:del_time", + nodenames = {"minecart:buffer"}, + run_at_every_load = false, + action = function(pos, node) + -- delete old data + minecart.get_route(pos) + M(pos):set_string("formspec", formspec(pos)) + end, +}) diff --git a/minecart/cart_lib1.lua b/minecart/cart_lib1.lua deleted file mode 100644 index afde093..0000000 --- a/minecart/cart_lib1.lua +++ /dev/null @@ -1,397 +0,0 @@ ---[[ - - Minecart - ======== - - Copyright (C) 2019-2020 Joachim Stolberg - - MIT - See license.txt for more information - - Cart library functions (level 1) - -]]-- - --- Notes: --- 1) Only the owner can punch der cart --- 2) Only the owner can start the recording --- 3) But any player can act as cargo, cart punched by owner or buffer - -local SLOPE_ACCELERATION = 3 -local MAX_SPEED = 7 -local PUNCH_SPEED = 3 -local SLOWDOWN = 0.4 -local RAILTYPE = minetest.get_item_group("carts:rail", "connect_to_raillike") -local Y_OFFS_ON_SLOPES = 0.5 - --- for lazy programmers -local M = minetest.get_meta -local S = minecart.S -local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local S2P = minetest.string_to_pos -local MP = minetest.get_modpath("minecart") -local D = function(pos) return minetest.pos_to_string(vector.round(pos)) end - -local tRails = { - ["carts:rail"] = true, - ["carts:powerrail"] = true, - ["carts:brakerail"] = true, -} - -local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail"} - -local function get_rail_node(pos) - local rail_pos = vector.round(pos) - local node = minecart.get_node_lvm(rail_pos) - if tRails[node.name] then - return rail_pos, node - end -end - -local function find_rail_node(pos) - local rail_pos = vector.round(pos) - local node = get_rail_node(rail_pos) - if node then - return rail_pos, node - end - local pos1 = {x=rail_pos.x-1, y=rail_pos.y-1, z=rail_pos.z-1} - local pos2 = {x=rail_pos.x+1, y=rail_pos.y+1, z=rail_pos.z+1} - for _,pos3 in ipairs(minetest.find_nodes_in_area(pos1, pos2, lRails)) do - --print("invalid position1", D(pos), D(pos3)) - return pos3, minecart.get_node_lvm(pos3) - end - --print("invalid position2", D(pos)) -end - -local function get_pitch(dir) - local pitch = 0 - if dir.y == -1 then - pitch = -math.pi/4 - elseif dir.y == 1 then - pitch = math.pi/4 - end - return pitch * (dir.z == 0 and -1 or 1) -end - -local function get_yaw(dir) - local yaw = 0 - if dir.x < 0 then - yaw = math.pi/2*3 - elseif dir.x > 0 then - yaw = math.pi/2 - elseif dir.z < 0 then - yaw = math.pi - end - return yaw -end - -local function push_cart(self, pos, punch_dir, puncher) - local vel = self.object:get_velocity() - punch_dir = punch_dir or carts:velocity_to_dir(puncher:get_look_dir()) - punch_dir.y = 0 - local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, RAILTYPE) - - -- Always start in horizontal direction - cart_dir.y = 0 - - if vector.equals(cart_dir, {x=0, y=0, z=0}) then return end - - local speed = vector.multiply(cart_dir, PUNCH_SPEED) - local new_vel = vector.add(vel, speed) - local yaw = get_yaw(cart_dir) - local pitch = get_pitch(cart_dir) - - self.object:set_rotation({x = pitch, y = yaw, z = 0}) - self.object:set_velocity(new_vel) - - self.old_pos = vector.round(pos) - self.stopped = false -end - -local api = {} - -function api:init(is_node_cart) - local lib - - if is_node_cart then - lib = dofile(MP.."/cart_lib2n.lua") - else - lib = dofile(MP.."/cart_lib2e.lua") - end - - -- add lib to local api - for k,v in pairs(lib) do - api[k] = v - end -end - --- Player get on / off -function api:on_rightclick(clicker) - if not clicker or not clicker:is_player() then - return - end - local player_name = clicker:get_player_name() - if self.driver and player_name == self.driver then - self.driver = nil - carts:manage_attachment(clicker, nil) - elseif not self.driver then - self.driver = player_name - carts:manage_attachment(clicker, self.object) - - -- player_api does not update the animation - -- when the player is attached, reset to default animation - player_api.set_animation(clicker, "stand") - end -end - -function api:on_activate(staticdata, dtime_s) - self.object:set_armor_groups({immortal=1}) -end - -function api:on_detach_child(child) - if child and child:get_player_name() == self.driver then - self.driver = nil - end -end - -function api:on_punch(puncher, time_from_last_punch, tool_capabilities, direction) - local pos = self.object:get_pos() - local vel = self.object:get_velocity() - local stopped = vector.equals(vel, {x=0, y=0, z=0}) - local is_minecart = self.node_name == nil - local node_name = self.node_name or "minecart:cart" - local puncher_name = puncher and puncher:is_player() and puncher:get_player_name() - local puncher_is_owner = puncher_name and (not self.owner or self.owner == "" or - puncher_name == self.owner or - minetest.check_player_privs(puncher_name, "minecart")) - local puncher_is_driver = self.driver and self.driver == puncher_name - local sneak_punch = puncher_name and puncher:get_player_control().sneak - local no_cargo = next(self.cargo or {}) == nil - - -- driver wants to leave/remove the empty Minecart by sneak-punch - if is_minecart and sneak_punch and puncher_is_driver and no_cargo then - if puncher_is_owner then - api.remove_cart(self, pos, puncher) - end - carts:manage_attachment(puncher, nil) - return - end - - -- Punched by non-authorized player - if puncher_name and not puncher_is_owner then - minetest.chat_send_player(puncher_name, S("[minecart] Cart is protected by ")..(self.owner or "")) - return - end - - -- Punched by non-player - if not puncher_name then - local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, RAILTYPE) - if vector.equals(cart_dir, {x=0, y=0, z=0}) then - return - end - api.load_cargo(self, pos) - push_cart(self, pos, cart_dir) - minecart.start_cart(pos, self.myID) - return - end - - -- Sneak-punched by owner - if sneak_punch then - -- Unload the cargo - if api.add_cargo_to_player_inv(self, pos, puncher) then - return - end - -- detach driver - if self.driver then - carts:manage_attachment(puncher, nil) - end - -- Pick up cart - api.remove_cart(self, pos, puncher) - return - end - - -- Cart with driver punched to start recording - if puncher_is_driver then - minecart.start_recording(self, pos, vel, puncher) - else - minecart.start_cart(pos, self.myID) - end - - api.load_cargo(self, pos) - - push_cart(self, pos, nil, puncher) -end - --- sound refresh interval = 1.0sec -local function rail_sound(self, dtime) - if not self.sound_ttl then - self.sound_ttl = 1.0 - return - elseif self.sound_ttl > 0 then - self.sound_ttl = self.sound_ttl - dtime - return - end - self.sound_ttl = 1.0 - if self.sound_handle then - local handle = self.sound_handle - self.sound_handle = nil - minetest.after(0.2, minetest.sound_stop, handle) - end - if not self.stopped then - local vel = self.object:get_velocity() or {x=0, y=0, z=0} - local speed = vector.length(vel) - self.sound_handle = minetest.sound_play( - "carts_cart_moving", { - object = self.object, - gain = (speed / carts.speed_max) / 2, - loop = true, - }) - end -end - -local function rail_on_step(self) - -- Check if same position as before - local pos = self.object:get_pos() - local rot = self.object:get_rotation() - local on_slope = rot.x ~= 0 - --print("rail_on_step_new", P2S(pos), rot.x) - - -- cart position correction on slopes - if on_slope then - pos.y = pos.y - Y_OFFS_ON_SLOPES - end - - -- Used as fallback position - self.old_pos = self.old_pos or pos - local pos_rounded = vector.round(pos) - -- Same pos as before - if vector.equals(pos_rounded, self.old_pos) then - return -- nothing todo - end - - -- Check if stopped - local vel = self.object:get_velocity() - local stopped = not on_slope and minecart.stopped(vel) - local is_minecart = self.node_name == nil - local recording = is_minecart and self.driver == self.owner - - if stopped then - if not self.stopped then - local param2 = minetest.dir_to_facedir(self.old_dir) - api.stop_cart(pos, self, self.node_name or "minecart:cart", param2) - if recording then - minecart.stop_recording(self, pos_rounded, vel, self.driver) - end - api.unload_cargo(self, pos) - self.stopped = true - end - self.old_pos = pos_rounded - return -- nothing todo - end - - -- Check if invalid position (not on rail anymore) - local rail_pos, node = get_rail_node(pos) - if not node then - rail_pos, node = find_rail_node(self.old_pos) - if rail_pos then - pos_rounded = rail_pos - if on_slope then - self.object:set_pos({x=rail_pos.x, y=rail_pos.y + Y_OFFS_ON_SLOPES, z=rail_pos.z}) - else - self.object:set_pos(rail_pos) - end - else - self.object:set_pos(pos) - minetest.log("error", "[minecart] No valid position "..(P2S(pos) or "nil")) - return -- no valid position - end - end - - -- Calc speed (value) - local speed = math.sqrt((vel.x+vel.z)^2 + vel.y^2) - -- Check if slope position - if pos_rounded.y > self.old_pos.y then - speed = speed - SLOPE_ACCELERATION - elseif pos_rounded.y < self.old_pos.y then - speed = speed + SLOPE_ACCELERATION - else - speed = speed - SLOWDOWN - end - -- Add power/brake rail acceleration - local acc = (carts.railparams[node.name] or {}).acceleration or 0 - speed = speed + acc - - -- Determine new direction - local dir = carts:velocity_to_dir(vel) - if speed < 0 then - if on_slope then - dir = vector.multiply(dir, -1) - -- start with a value > 0 - speed = 0.5 - else - speed = 0 - end - end - - -- Get player controls - local ctrl, player - if recording then - player = minetest.get_player_by_name(self.driver) - if player then - ctrl = player:get_player_control() - end - end - - -- new_dir: New moving direction of the cart - -- keys: Currently pressed L/R key, used to ignore the key on the next rail node - local new_dir, keys = carts:get_rail_direction(rail_pos, dir, ctrl, self.old_keys, RAILTYPE) - - -- handle junctions - if recording and keys then - minecart.set_junction(self, rail_pos, new_dir, keys) - else -- normal run - new_dir, keys = minecart.get_junction(self, rail_pos, new_dir) - end - self.old_keys = keys - - -- Detect U-turn - if (dir.x ~= 0 and dir.x == -new_dir.x) or (dir.z ~= 0 and dir.z == -new_dir.z) then - -- Stop the cart - self.object:set_velocity({x=0, y=0, z=0}) - self.object:move_to(pos_rounded) - return - -- New direction - elseif not vector.equals(dir, new_dir) then - if new_dir.y ~= 0 then - self.object:set_pos({x=pos_rounded.x, y=pos_rounded.y + Y_OFFS_ON_SLOPES, z=pos_rounded.z}) - else - self.object:set_pos(pos_rounded) - end - end - - -- Set velocity and rotation - local new_vel = vector.multiply(new_dir, math.min(speed, MAX_SPEED)) - local yaw = get_yaw(new_dir) - local pitch = get_pitch(new_dir) - - self.object:set_rotation({x = pitch, y = yaw, z = 0}) - self.object:set_velocity(new_vel) - - - if recording then - minecart.store_next_waypoint(self, rail_pos, vel) - end - - self.old_pos = pos_rounded -end - -function api:on_step(dtime) - self.delay = (self.delay or 0) + dtime - if self.delay > 0.09 then - rail_on_step(self) - rail_sound(self, self.delay) - self.delay = 0 - end -end - -return api diff --git a/minecart/cart_lib2e.lua b/minecart/cart_lib2e.lua deleted file mode 100644 index cea4700..0000000 --- a/minecart/cart_lib2e.lua +++ /dev/null @@ -1,150 +0,0 @@ ---[[ - - Minecart - ======== - - Copyright (C) 2019-2020 Joachim Stolberg - - MIT - See license.txt for more information - - Cart library functions for entity based carts (level 2) - -]]-- - --- for lazy programmers -local M = minetest.get_meta -local S = minecart.S -local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local S2P = minetest.string_to_pos -local MP = minetest.get_modpath("minecart") - -local api = dofile(MP.."/cart_lib3.lua") - --- Add node, set metadata, and load carge -local function add_cart(pos, node_name, param2, owner, userID, cargo) - local obj = minetest.add_entity(pos, node_name) - local myID = api.get_object_id(obj) - if myID then - -- Copy item data to cart entity - local entity = obj:get_luaentity() - entity.owner = owner - entity.userID = userID - entity.cargo = cargo - entity.myID = myID - obj:set_nametag_attributes({color = "#FFFF00", text = owner..": "..userID}) - minecart.add_to_monitoring(obj, myID, owner, userID) - return myID - else - print("Entity has no ID") - end -end - -function api.stop_cart(pos, entity, node_name, param2) - -- Stop sound - if entity.sound_handle then - minetest.sound_stop(entity.sound_handle) - entity.sound_handle = nil - end - minecart.stop_cart(pos, entity.myID) -end - - --- Player adds the node -function api.add_cart(itemstack, placer, pointed_thing, node_name) - local owner = placer:get_player_name() - local meta = placer:get_meta() - local param2 = minetest.dir_to_facedir(placer:get_look_dir()) - local userID = 0 - local cargo = {} - - -- Add node - if carts:is_rail(pointed_thing.under) then - add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo) - meta:set_string("cart_pos", P2S(pointed_thing.under)) - elseif carts:is_rail(pointed_thing.above) then - add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo) - meta:set_string("cart_pos", P2S(pointed_thing.above)) - else - return - end - - minetest.sound_play({name = "default_place_node_metal", gain = 0.5}, - {pos = pointed_thing.above}) - - if not (creative and creative.is_enabled_for - and creative.is_enabled_for(placer:get_player_name())) then - itemstack:take_item() - end - - minetest.show_formspec(owner, "minecart:userID_entity", - "size[4,3]" .. - "label[0,0;Enter cart number:]" .. - "field[1,1;3,1;userID;;]" .. - "button_exit[1,2;2,1;exit;Save]") - - return itemstack -end - --- Player removes the node -function api.remove_cart(self, pos, player) - -- Add cart to player inventory - local inv = player:get_inventory() - if not (creative and creative.is_enabled_for - and creative.is_enabled_for(player:get_player_name())) - or not inv:contains_item("main", "minecart:cart") then - local leftover = inv:add_item("main", "minecart:cart") - -- If no room in inventory add a replacement cart to the world - if not leftover:is_empty() then - minetest.add_item(pos, leftover) - end - end - minecart.remove_from_monitoring(self.myID) - self.object:remove() - -- Stop sound - if self.sound_handle then - minetest.sound_stop(self.sound_handle) - self.sound_handle = nil - end - return true -end - -function api.load_cargo(self, pos) - self.cargo = self.cargo or {} - for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do - local entity = obj_:get_luaentity() - if not obj_:is_player() and entity and entity.name == "__builtin:item" then - obj_:remove() - self.cargo[#self.cargo + 1] = entity.itemstring - end - end -end - -function api.unload_cargo(self, pos) - -- Spawn loaded items again - for _,item in ipairs(self.cargo or {}) do - minetest.add_item(pos, ItemStack(item)) - end - self.cargo = {} -end - --- in the case the owner punches the cart -function api.add_cargo_to_player_inv(self, pos, puncher) - local added = false - local inv = puncher:get_inventory() - for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do - local entity = obj:get_luaentity() - if not obj:is_player() and entity and entity.name == "__builtin:item" then - obj:remove() - local item = ItemStack(entity.itemstring) - local leftover = inv:add_item("main", item) - if leftover:get_count() > 0 then - minetest.add_item(pos, leftover) - end - added = true -- don't dig the cart - end - end - return added -end - -return api diff --git a/minecart/cart_lib2n.lua b/minecart/cart_lib2n.lua deleted file mode 100644 index 4625491..0000000 --- a/minecart/cart_lib2n.lua +++ /dev/null @@ -1,198 +0,0 @@ ---[[ - - Minecart - ======== - - Copyright (C) 2019-2020 Joachim Stolberg - - MIT - See license.txt for more information - - Cart library functions for node based carts (level 2) - -]]-- - --- for lazy programmers -local M = minetest.get_meta -local S = minecart.S -local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local S2P = minetest.string_to_pos -local MP = minetest.get_modpath("minecart") - -local api = dofile(MP.."/cart_lib3.lua") - --- Add node, set metadata, and load carge -local function add_cart(pos, node_name, param2, owner, userID, cargo) - local ndef = minetest.registered_nodes[node_name] - local node = minetest.get_node(pos) - local meta = M(pos) - local rail = node.name - minetest.add_node(pos, {name = node_name, param2 = param2}) - meta:set_string("removed_rail", rail) - meta:set_string("owner", owner) - meta:set_string("userID", userID) - meta:set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..owner..": "..userID) - if ndef.after_place_node then - ndef.after_place_node(pos) - end - if cargo and ndef.set_cargo then - ndef.set_cargo(pos, cargo) - end -end - --- called after punch cart -local function start_cart(pos, node_name, entity_name, puncher, dir) - -- Read node metadata - local ndef = minetest.registered_nodes[node_name] - if ndef then - local meta = M(pos) - local rail = meta:get_string("removed_rail") - local userID = meta:get_int("userID") - local cart_owner = meta:get_string("owner") - local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {} - -- swap node to rail - minetest.remove_node(pos) - minetest.add_node(pos, {name = rail}) - -- Add entity - local obj = minetest.add_entity(pos, entity_name) - -- Determine ID - local myID = api.get_object_id(obj) - if myID then - -- Copy metadata to cart entity - local entity = obj:get_luaentity() - entity.owner = cart_owner - entity.userID = userID - entity.cargo = cargo - entity.myID = myID - obj:set_nametag_attributes({color = "#ffff00", text = cart_owner..": "..userID}) - minecart.add_to_monitoring(obj, myID, cart_owner, userID) - minecart.node_at_station(cart_owner, userID, nil) - -- punch cart to prevent the stopped handling - obj:punch(puncher or obj, 1, { - full_punch_interval = 1.0, - damage_groups = {fleshy = 1}, - }, dir) - return myID - else - print("Entity has no ID") - end - end -end - -function api.stop_cart(pos, entity, node_name, param2) - -- rail buffer reached? - if api.get_route_key(pos) then - -- Read entity data - local owner = entity.owner or "" - local userID = entity.userID or 0 - local cargo = entity.cargo or {} - -- Remove entity - minecart.remove_from_monitoring(entity.myID) - minecart.node_at_station(owner, userID, pos) - entity.object:remove() - -- Add cart node - add_cart(pos, node_name, param2, owner, userID, cargo) - end - -- Stop sound - if entity.sound_handle then - minetest.sound_stop(entity.sound_handle) - entity.sound_handle = nil - end -end - --- Player adds the node -function api.add_cart(itemstack, placer, pointed_thing, node_name) - local owner = placer:get_player_name() - local meta = placer:get_meta() - local param2 = minetest.dir_to_facedir(placer:get_look_dir()) - local userID = 0 - local cargo = {} - - -- Add node - if carts:is_rail(pointed_thing.under) then - add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo) - meta:set_string("cart_pos", P2S(pointed_thing.under)) - elseif carts:is_rail(pointed_thing.above) then - add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo) - meta:set_string("cart_pos", P2S(pointed_thing.above)) - else - return - end - - minetest.sound_play({name = "default_place_node_metal", gain = 0.5}, - {pos = pointed_thing.above}) - - if not (creative and creative.is_enabled_for - and creative.is_enabled_for(placer:get_player_name())) then - itemstack:take_item() - end - - minetest.show_formspec(owner, "minecart:userID_node", - "size[4,3]" .. - "label[0,0;Enter cart number:]" .. - "field[1,1;3,1;userID;;]" .. - "button_exit[1,2;2,1;exit;Save]") - - return itemstack -end - -function api.node_on_punch(pos, node, puncher, pointed_thing, entity_name, dir) - local ndef = minetest.registered_nodes[node.name] - -- Player digs cart by sneak-punch - if puncher and puncher:get_player_control().sneak then - api.remove_cart(nil, pos, puncher) - return - end - start_cart(pos, node.name, entity_name, puncher, dir) -end - -local function add_to_player_inventory(pos, player, node_name) - local inv = player:get_inventory() - if not (creative and creative.is_enabled_for - and creative.is_enabled_for(player:get_player_name())) - or not inv:contains_item("main", node_name) then - local leftover = inv:add_item("main", node_name) - -- If no room in inventory add a replacement cart to the world - if not leftover:is_empty() then - minetest.add_item(pos, leftover) - end - end -end - --- Player removes the node -function api.remove_cart(self, pos, player) - if self then -- cart is still an entity - add_to_player_inventory(pos, player, self.node_name or "minecart:cart") - minecart.remove_from_monitoring(self.myID) - self.object:remove() - else - local node = minetest.get_node(pos) - local ndef = minetest.registered_nodes[node.name] - if ndef.can_dig and ndef.can_dig(pos, player) then - add_to_player_inventory(pos, player, node.name) - node.name = M(pos):get_string("removed_rail") - if node.name == "" then - node.name = "carts:rail" - end - minetest.remove_node(pos) - minetest.add_node(pos, node) - end - end -end - -function api.load_cargo() - -- nothing to load -end - -function api.unload_cargo() - -- nothing to unload -end - -function api.add_cargo_to_player_inv() - -- nothing to do -end - --- needed by minecart.punch_cart and node carts -minecart.node_on_punch = api.node_on_punch - -return api \ No newline at end of file diff --git a/minecart/cart_lib3.lua b/minecart/cart_lib3.lua deleted file mode 100644 index 6cecc9c..0000000 --- a/minecart/cart_lib3.lua +++ /dev/null @@ -1,90 +0,0 @@ ---[[ - - Minecart - ======== - - Copyright (C) 2019-2020 Joachim Stolberg - - MIT - See license.txt for more information - - Cart library base functions (level 3) - -]]-- - --- for lazy programmers -local M = minetest.get_meta -local S = minecart.S -local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local S2P = minetest.string_to_pos - -local api = {} - -function api.get_object_id(object) - for id, entity in pairs(minetest.luaentities) do - if entity.object == object then - return id - end - end -end - -function api.get_route_key(pos, player_name) - local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"}) - if pos1 then - local meta = minetest.get_meta(pos1) - if player_name == nil or player_name == meta:get_string("owner") then - return P2S(pos1) - end - end -end - -function api.get_station_name(pos) - local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"}) - if pos1 then - local name = M(pos1):get_string("name") - if name ~= "" then - return name - end - return "-" - end -end - -function api.load_cart(pos, vel, pitch, yaw, item) - -- Add cart to map - local obj = minetest.add_entity(pos, item.entity_name or "minecart:cart", nil) - -- Determine ID - local myID = api.get_object_id(obj) - if myID then - -- Copy item data to cart entity - local entity = obj:get_luaentity() - entity.owner = item.owner or "" - entity.userID = item.userID or 0 - entity.cargo = item.cargo or {} - entity.myID = myID - obj:set_nametag_attributes({color = "#FFFF00", text = entity.owner..": "..entity.userID}) - -- Update item data - item.owner = entity.owner - item.cargo = nil - -- Start cart - obj:set_velocity(vel) - obj:set_rotation({x = pitch or 0, y = yaw or 0, z = 0}) - return myID - else - print("Entity has no ID") - end -end - -function api.unload_cart(pos, vel, entity, item) - -- Copy entity data to item - item.cargo = entity.cargo - item.entity_name = entity.object:get_entity_name() - -- Remove entity from map - entity.object:remove() - -- Stop sound - if entity.sound_handle then - minetest.sound_stop(entity.sound_handle) - entity.sound_handle = nil - end -end - -return api diff --git a/minecart/doc.lua b/minecart/doc.lua index 3bb7cb6..3f43b37 100644 --- a/minecart/doc.lua +++ b/minecart/doc.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -29,15 +29,15 @@ local summary_doc = table.concat({ S("4. Place a Minecart at a buffer and give it a cart number (1..999)"), S("5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."), S("6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."), - S("7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time."), + S("7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time."), S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."), S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."), S("10. Check the cart state via the chat command: /mycart \n '' is the cart number"), - S("11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back."), - S("12. Dig the empty cart with a second 'sneak+click' (as usual)."), + S("11. Drop items into the Minecart and punch the cart to start it."), + S("12. Dig the cart with 'sneak+click' (as usual). The items will be drop down."), }, "\n") -local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back") +local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back") local buffer_doc = S("Used as buffer on both rail ends. Needed to be able to record the cart routes") @@ -45,6 +45,29 @@ local landmark_doc = S("Protect your rails with the Landmarks (one Landmark at l local hopper_doc = S("Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.") +local pusher_doc = S([[If several carts are running on one route, +it can happen that a buffer position is already occupied and one cart therefore stops earlier. +In this case, the cart pusher is used to push the cart towards the buffer again. +This block must be placed under the rail at a distance of 2 m in front of the buffer.]]) + +local speed_doc = S([[Limit the cart speed with speed limit signs. + +As before, the speed of the carts is also influenced by power rails. +Brake rails are irrelevant, the cart does not brake here. +The maximum speed is 8 m/s. This assumes a ratio of power rails +to normal rails of 1 to 4 on a flat section of rail. A rail section is a +series of rail nodes without a change of direction. After every curve / kink, +the speed for the next section of the route is newly determined, +taking into account the swing of the cart. This means that a cart can +roll over short rail sections without power rails. + +In order to additionally brake the cart at certain points +(at switches or in front of a buffer), speed limit signs can be placed +on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s. +The "No speed limit" sign can be used to remove the speed limit. + +The speed limit signs must be placed next to the track so that they can +be read from the cart. This allows different speeds in each direction of travel.]]) local function formspec(data) if data.image then @@ -90,6 +113,16 @@ doc.add_entry("minecart", "landmark", { data = {text = landmark_doc, item="minecart:landmark"}, }) +doc.add_entry("minecart", "speed signs", { + name = S("Minecart Speed Signs"), + data = {text = speed_doc, item="minecart:speed4"}, +}) + +doc.add_entry("minecart", "cart pusher", { + name = S("Cart Pusher"), + data = {text = pusher_doc, item="minecart:cart_pusher"}, +}) + if minecart.hopper_enabled then doc.add_entry("minecart", "hopper", { name = S("Minecart Hopper"), diff --git a/minecart/entitylib.lua b/minecart/entitylib.lua new file mode 100644 index 0000000..bf20953 --- /dev/null +++ b/minecart/entitylib.lua @@ -0,0 +1,318 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash +local MAX_SPEED = minecart.MAX_SPEED +local dot2dir = minecart.dot2dir +local get_waypoint = minecart.get_waypoint +local recording_waypoints = minecart.recording_waypoints +local recording_junctions = minecart.recording_junctions +local set_junctions = minecart.set_junctions +local player_ctrl = minecart.player_ctrl +local tEntityNames = minecart.tEntityNames + +local function stop_cart(self, cart_pos) + self.is_running = false + self.arrival_time = 0 + + if self.driver then + local player = minetest.get_player_by_name(self.driver) + if player then + minecart.stop_recording(self, cart_pos) + minecart.manage_attachment(player, self, false) + end + end + if not minecart.get_buffer_pos(cart_pos, self.owner) then + -- Probably somewhere in the pampas + minecart.delete_cart_waypoint(cart_pos) + end + minecart.entity_to_node(cart_pos, self) +end + +local function get_ctrl(self, pos) + -- Use player ctrl or junction data from recorded routes + return (self.driver and self.ctrl) or (self.junctions and self.junctions[P2H(pos)]) or {} +end + +local function new_speed(self, new_dir) + self.cart_speed = self.cart_speed or 0 + local rail_speed = (self.waypoint.speed or 0) / 10 + + if rail_speed <= 0 then + rail_speed = math.max(self.cart_speed + rail_speed, 0) + elseif rail_speed <= self.cart_speed then + rail_speed = math.max((self.cart_speed + rail_speed) / 2, 0) + end + + -- Speed corrections + if new_dir.y == 1 then + if rail_speed < 1 then rail_speed = 0 end + else + if rail_speed < 0.4 then rail_speed = 0 end + end + + self.cart_speed = rail_speed -- store for next cycle + return rail_speed +end + +local function running(self) + local rot = self.object:get_rotation() + local dir = minetest.yaw_to_dir(rot.y) + dir.y = math.floor((rot.x / (math.pi/4)) + 0.5) + dir = vector.round(dir) + local facedir = minetest.dir_to_facedir(dir) + local cart_pos, wayp_pos, is_junction + + if self.reenter then -- through monitoring + cart_pos = H2P(self.reenter[1]) + wayp_pos = cart_pos + is_junction = false + self.waypoint = {pos = H2P(self.reenter[2]), power = 0, dot = self.reenter[4]} + self.cart_speed = self.reenter[3] + self.speed_limit = MAX_SPEED + self.reenter = nil + elseif not self.waypoint then + -- get waypoint + cart_pos = vector.round(self.object:get_pos()) + wayp_pos = cart_pos + is_junction = false + self.waypoint = get_waypoint(cart_pos, facedir, get_ctrl(self, cart_pos), true) + if self.no_normal_start then + -- Probably somewhere in the pampas + minecart.delete_waypoint(cart_pos) + self.no_normal_start = nil + end + self.cart_speed = 2 -- push speed + self.speed_limit = MAX_SPEED + else + -- next waypoint + cart_pos = vector.new(self.waypoint.cart_pos or self.waypoint.pos) + wayp_pos = self.waypoint.pos + local vel = self.object:get_velocity() + self.waypoint, is_junction = get_waypoint(wayp_pos, facedir, get_ctrl(self, wayp_pos), self.cart_speed < 0.1) + end + + if not self.waypoint then + stop_cart(self, wayp_pos) + return + end + + if is_junction then + if self.is_recording then + set_junctions(self, wayp_pos) + end + self.ctrl = nil + end + + --print("dist", P2S(cart_pos), P2S(self.waypoint.pos), P2S(self.waypoint.cart_pos), self.waypoint.dot) + local dist = vector.distance(cart_pos, self.waypoint.cart_pos or self.waypoint.pos) + local new_dir = dot2dir(self.waypoint.dot) + local new_speed = new_speed(self, new_dir) + local straight_ahead = vector.equals(new_dir, dir) + -- If straight_ahead, then it's probably a speed limit sign + if straight_ahead then + self.speed_limit = minecart.get_speedlimit(wayp_pos, facedir) or self.speed_limit + end + new_speed = math.min(new_speed, self.speed_limit) + + local new_cart_pos, extra_cycle = minecart.get_current_cart_pos_correction( + wayp_pos, facedir, dir.y, self.waypoint.dot) -- TODO: Why has self.waypoint no dot? + if extra_cycle and not vector.equals(cart_pos, new_cart_pos) then + self.waypoint = {pos = wayp_pos, cart_pos = new_cart_pos} + new_dir = vector.direction(cart_pos, new_cart_pos) + dist = vector.distance(cart_pos, new_cart_pos) + --print("extra_cycle", P2S(cart_pos), P2S(wayp_pos), P2S(new_cart_pos), new_speed) + end + + -- Slope corrections + --print("Slope corrections", P2S(new_dir), P2S(cart_pos)) + if new_dir.y ~= 0 then + cart_pos.y = cart_pos.y + 0.2 + end + + -- Calc velocity, rotation and arrival_time + local yaw = minetest.dir_to_yaw(new_dir) + local pitch = new_dir.y * math.pi/4 + --print("new_speed", new_speed / (new_dir.y ~= 0 and 1.41 or 1)) + local vel = vector.multiply(new_dir, new_speed / ((new_dir.y ~= 0) and 1.41 or 1)) + self.arrival_time = self.timebase + (dist / new_speed) + -- needed for recording + self.curr_speed = new_speed + self.num_sections = (self.num_sections or 0) + 1 + + -- Got stuck somewhere + if new_speed < 0.1 or dist < 0 then + print("Got stuck somewhere", new_speed, dist) + stop_cart(self, wayp_pos) + return + end + + self.object:set_pos(cart_pos) + self.object:set_rotation({x = pitch, y = yaw, z = 0}) + self.object:set_velocity(vel) + return +end + +local function play_sound(self) + if self.sound_handle then + local handle = self.sound_handle + self.sound_handle = nil + minetest.after(0.2, minetest.sound_stop, handle) + end + if self.object then + self.sound_handle = minetest.sound_play( + "carts_cart_moving", { + object = self.object, + gain = self.curr_speed / MAX_SPEED, + }) + end +end + +local function on_step(self, dtime) + self.timebase = (self.timebase or 0) + dtime + + if self.is_running then + if self.arrival_time <= self.timebase then + running(self) + end + + if (self.sound_ttl or 0) <= self.timebase then + play_sound(self) + self.sound_ttl = self.timebase + 1.0 + end + else + if self.sound_handle then + minetest.sound_stop(self.sound_handle) + self.sound_handle = nil + end + end + + if self.driver then + if self.is_recording then + if self.rec_time <= self.timebase then + recording_waypoints(self) + self.rec_time = self.rec_time + 2.0 + end + recording_junctions(self) + else + player_ctrl(self) + end + end +end + +local function on_entitycard_activate(self, staticdata, dtime_s) + self.object:set_armor_groups({immortal=1}) +end + +-- Start the entity cart (or dig by shift+leftclick) +local function on_entitycard_punch(self, puncher, time_from_last_punch, tool_capabilities, dir) + if minecart.is_owner(puncher, self.owner) then + if puncher:get_player_control().sneak then + if not self.only_dig_if_empty or not next(self.cargo) then + -- drop items + local pos = vector.round(self.object:get_pos()) + for _,item in ipairs(self.cargo or {}) do + minetest.add_item(pos, ItemStack(item)) + end + -- Dig cart + if self.driver then + -- remove cart as driver + minecart.stop_recording(self, pos) + minecart.monitoring_remove_cart(self.owner, self.userID) + minecart.remove_entity(self, pos, puncher) + minecart.manage_attachment(puncher, self, false) + else + -- remove cart from outside + minecart.monitoring_remove_cart(self.owner, self.userID) + minecart.remove_entity(self, pos, puncher) + end + end + elseif not self.is_running then + -- start the cart + local pos = vector.round(self.object:get_pos()) + if puncher then + local yaw = puncher:get_look_horizontal() + self.object:set_rotation({x = 0, y = yaw, z = 0}) + end + minecart.start_entitycart(self, pos) + minecart.start_recording(self, pos) + end + end +end + +-- Player get on / off +local function on_entitycard_rightclick(self, clicker) + if clicker and clicker:is_player() and self.driver_allowed then + -- Get on / off + if self.driver then + -- get off + local pos = vector.round(self.object:get_pos()) + minecart.manage_attachment(clicker, self, false) + minecart.entity_to_node(pos, self) + else + -- get on + local pos = vector.round(self.object:get_pos()) + minecart.stop_recording(self, pos) + minecart.manage_attachment(clicker, self, true) + end + end +end + +local function on_entitycard_detach_child(self, child) + if child and child:get_player_name() == self.driver then + self.driver = nil + end +end + +function minecart.get_entitycart_nearby(pos, param2, radius) + local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos + for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do + local entity = object:get_luaentity() + if entity and entity.name and tEntityNames[entity.name] then + local vel = object:get_velocity() + if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing? + return entity + end + end + end +end + +function minecart.push_entitycart(self, punch_dir) + --print("push_entitycart") + local vel = self.object:get_velocity() + punch_dir.y = 0 + local yaw = minetest.dir_to_yaw(punch_dir) + self.object:set_rotation({x = 0, y = yaw, z = 0}) + self.is_running = true + self.arrival_time = 0 +end + +function minecart.register_cart_entity(entity_name, node_name, cart_type, entity_def) + entity_def.entity_name = entity_name + entity_def.node_name = node_name + entity_def.on_activate = on_entitycard_activate + entity_def.on_punch = on_entitycard_punch + entity_def.on_step = on_step + entity_def.on_rightclick = on_entitycard_rightclick + entity_def.on_detach_child = on_entitycard_detach_child + + entity_def.owner = nil + entity_def.driver = nil + entity_def.cargo = {} + + minetest.register_entity(entity_name, entity_def) + -- register node for punching + minecart.register_cart_names(node_name, entity_name, cart_type) +end + diff --git a/minecart/hopper.lua b/minecart/hopper.lua index 49f045b..7b7fbbf 100644 --- a/minecart/hopper.lua +++ b/minecart/hopper.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -155,6 +155,7 @@ minetest.register_node("minecart:hopper", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = minecart.CLIP, groups = {choppy=2, cracky=2, crumbly=2}, is_ground_content = false, sounds = default.node_sound_wood_defaults(), diff --git a/minecart/hopperlib.lua b/minecart/hopperlib.lua new file mode 100644 index 0000000..6f2a583 --- /dev/null +++ b/minecart/hopperlib.lua @@ -0,0 +1,141 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2020 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta + +local RegisteredInventories = {} + +-- Take the given number of items from the inv. +-- Returns nil if ItemList is empty. +function minecart.inv_take_items(inv, listname, num) + if inv:is_empty(listname) then + return nil + end + local size = inv:get_size(listname) + for idx = 1, size do + local items = inv:get_stack(listname, idx) + if items:get_count() > 0 then + local taken = items:take_item(num) + inv:set_stack(listname, idx, items) + return taken + end + end + return nil +end + +function minecart.take_items(pos, param2, num) + local npos, node + if param2 then + npos, node = minecart.get_next_node(pos, (param2 + 2) % 4) + else + npos, node = pos, minetest.get_node(pos) + end + local def = RegisteredInventories[node.name] + local owner = M(pos):get_string("owner") + local inv = minetest.get_inventory({type="node", pos=npos}) + + if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then + return minecart.inv_take_items(inv, def.take_listname, num) + elseif def and def.take_item then + return def.take_item(npos, num, owner) + else + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.minecart_hopper_takeitem then + return ndef.minecart_hopper_takeitem(npos, num) + end + end +end + +function minecart.put_items(pos, param2, stack) + local npos, node = minecart.get_next_node(pos, param2) + local def = RegisteredInventories[node.name] + local owner = M(pos):get_string("owner") + local inv = minetest.get_inventory({type="node", pos=npos}) + + if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then + local leftover = inv:add_item(def.put_listname, stack) + if leftover:get_count() > 0 then + return leftover + end + elseif def and def.put_item then + return def.put_item(npos, stack, owner) + elseif minecart.is_air_like(node.name) or minecart.is_nodecart_available(npos) then + minetest.add_item(npos, stack) + else + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.minecart_hopper_additem then + local leftover = ndef.minecart_hopper_additem(npos, stack) + if leftover:get_count() > 0 then + return leftover + end + else + return stack + end + end +end + +function minecart.untake_items(pos, param2, stack) + local npos, node + if param2 then + npos, node = minecart.get_next_node(pos, (param2 + 2) % 4) + else + npos, node = pos, minetest.get_node(pos) + end + local def = RegisteredInventories[node.name] + local inv = minetest.get_inventory({type="node", pos=npos}) + + if def and inv and def.put_listname then + return inv:add_item(def.put_listname, stack) + elseif def and def.untake_item then + return def.untake_item(npos, stack) + else + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.minecart_hopper_untakeitem then + return ndef.minecart_hopper_untakeitem(npos, stack) + end + end +end + +-- Register inventory node for hopper access +-- (for example, see below) +function minecart.register_inventory(node_names, def) + for _, name in ipairs(node_names) do + RegisteredInventories[name] = { + allow_put = def.put and def.put.allow_inventory_put, + put_listname = def.put and def.put.listname, + allow_take = def.take and def.take.allow_inventory_take, + take_listname = def.take and def.take.listname, + put_item = def.put and def.put.put_item, + take_item = def.take and def.take.take_item, + untake_item = def.take and def.take.untake_item, + } + end +end + +-- Allow the hopper the access to itself +minecart.register_inventory({"minecart:hopper"}, { + put = { + allow_inventory_put = function(pos, stack, player_name) + local owner = M(pos):get_string("owner") + return owner == player_name + end, + listname = "main", + }, + take = { + allow_inventory_take = function(pos, stack, player_name) + local owner = M(pos):get_string("owner") + return owner == player_name + end, + listname = "main", + }, +}) diff --git a/minecart/i18n.py b/minecart/i18n.py index b218fcf..3c6ce1b 100755 --- a/minecart/i18n.py +++ b/minecart/i18n.py @@ -1,79 +1,469 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. # -# Copyright (C) 2019 Joachim Stolberg +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer # LGPLv2.1+ -# -# Copy the script into the mod root folder and adapt the last code lines to you needs. +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. from __future__ import print_function -import os, fnmatch, re, shutil +import os, fnmatch, re, shutil, errno +from sys import argv as _argv +from sys import stderr as _stderr -pattern_lua = re.compile(r'[ \.=^\t]S\("(.+?)"\)', re.DOTALL) -pattern_tr = re.compile(r'(.+?[^@])=(.+)') +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False, + "break-long-lines": False, + "sort": False, + "print-source": False +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods', '-m'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file', '-O'], + "break-long-lines": ['--break-long-lines', '-b'], + "sort": ['--sort', '-s'], + "print-source": ['--print-source', '-p'] +} -def gen_template(templ_file, lkeyStrings): - lOut = [] - lkeyStrings.sort() - for s in lkeyStrings: - lOut.append("%s=" % s) - open(templ_file, "wt").write("\n".join(lOut)) +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 80 +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["sort"])} + sort output strings alphabetically + {', '.join(options["break-long-lines"])} + add extra line breaks before and after long strings + {', '.join(options["verbose"])} + add output information +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}") + run_all_subfolders(os.path.expanduser("~/.minetest/mods")) + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) +pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.*?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + if not os.path.isfile(os.path.join(folder, "modpack.txt")): + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + if folder_name == "builtin": + return "__builtin" + else: + return folder_name + else: + return None + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = f'{modname}.{language_code}.tr' + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name, header_comments): + lOut = [f"# textdomain: {mod_name}"] + if header_comments is not None: + lOut.append(header_comments) + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + if params["sort"]: + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + if params["sort"]: + localizedStrings.sort() + if params["print-source"]: + if lOut[-1] != "": + lOut.append("") + lOut.append(source) + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None and comment != "" and not comment.startswith("# textdomain:"): + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if params["break-long-lines"] and len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2]) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file def read_lua_file_strings(lua_file): lOut = [] - text = open(lua_file).read() - for s in pattern_lua.findall(text): - s = re.sub(r'"\.\.\s+"', "", s) - s = re.sub("@[^@=n]", "@@", s) - s = s.replace("\n", "@n") - s = s.replace("\\n", "@n") - s = s.replace("=", "@=") - lOut.append(s) + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua_s.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_s.findall(text): + strings.append(s) + for s in pattern_lua_fs.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_fs.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) return lOut -def inport_tr_file(tr_file): +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +# Returns also header comments in the third return value. +def import_tr_file(tr_file): dOut = {} + text = None + header_comment = None if os.path.exists(tr_file): - for line in open(tr_file, "r").readlines(): - s = line.strip() - if s == "" or s[0] == "#": - continue - match = pattern_tr.match(s) - if match: - dOut[match.group(1)] = match.group(2) - return dOut + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line.startswith("###"): + if header_comment is None and not latest_comment_block is None: + # Save header comments + header_comment = latest_comment_block + # Strip textdomain line + tmp_h_c = "" + for l in header_comment.split('\n'): + if not l.startswith("# textdomain:"): + tmp_h_c += l + '\n' + header_comment = tmp_h_c -def generate_template(templ_file): - lOut = [] - for root, dirs, files in os.walk('./'): + # Reset comment block if we hit a header + latest_comment_block = None + continue + elif line.startswith("#"): + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text, header_comment) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): for name in files: if fnmatch.fnmatch(name, "*.lua"): fname = os.path.join(root, name) found = read_lua_file_strings(fname) - print(fname, len(found)) - lOut.extend(found) - lOut = list(set(lOut)) - lOut.sort() - gen_template(templ_file, lOut) - return lOut + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") -def update_tr_file(lNew, mod_name, tr_file): - lOut = ["# textdomain: %s\n" % mod_name] - if os.path.exists(tr_file): - shutil.copyfile(tr_file, tr_file+".old") - dOld = inport_tr_file(tr_file) - for key in lNew: - val = dOld.get(key, "") - lOut.append("%s=%s" % (key, val)) - lOut.append("##### not used anymore #####") - for key in dOld: - if key not in lNew: - lOut.append("%s=%s" % (key, dOld[key])) - open(tr_file, "w").write("\n".join(lOut)) - -data = generate_template("./locale/template.txt") -update_tr_file(data, "minecart", "./locale/minecart.de.tr") -print("Done.\n") + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2]) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr) + exit(1) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] + for subfolder in subfolders: + update_mod(subfolder) + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]: + update_folder(modfolder) + + +main() diff --git a/minecart/init.lua b/minecart/init.lua index c484085..b1e66b1 100644 --- a/minecart/init.lua +++ b/minecart/init.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -13,24 +13,37 @@ minecart = {} -- Version for compatibility checks, see readme.md/history -minecart.version = 1.10 +minecart.version = 2.00 minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true +-- Test for MT 5.4 new string mode +minecart.CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or false + minecart.S = minetest.get_translator("minecart") local MP = minetest.get_modpath("minecart") -dofile(MP.."/storage.lua") -dofile(MP.."/lib.lua") -dofile(MP.."/monitoring.lua") -dofile(MP.."/recording.lua") -dofile(MP.."/minecart.lua") -dofile(MP.."/buffer.lua") -dofile(MP.."/protection.lua") +dofile(MP .. "/baselib.lua") +dofile(MP .. "/storage.lua") +dofile(MP .. "/rails.lua") +dofile(MP .. "/monitoring.lua") +dofile(MP .. "/recording.lua") +dofile(MP .. "/hopperlib.lua") +dofile(MP .. "/nodelib.lua") +dofile(MP .. "/entitylib.lua") +dofile(MP .. "/api.lua") +dofile(MP .. "/minecart.lua") +dofile(MP .. "/buffer.lua") +dofile(MP .. "/protection.lua") +--dofile(MP .. "/tool.lua") # for debugging only +dofile(MP .. "/signs.lua") +dofile(MP .. "/terminal.lua") +dofile(MP .. "/pusher.lua") if minecart.hopper_enabled then - dofile(MP.."/hopper.lua") - dofile(MP.."/mods_support.lua") + dofile(MP .. "/hopper.lua") + dofile(MP .. "/mods_support.lua") end -dofile(MP.."/doc.lua") + +dofile(MP .. "/doc.lua") minetest.log("info", "[MOD] Minecart loaded") diff --git a/minecart/lib.lua b/minecart/lib.lua deleted file mode 100644 index efdcf72..0000000 --- a/minecart/lib.lua +++ /dev/null @@ -1,326 +0,0 @@ ---[[ - - Minecart - ======== - - Copyright (C) 2019-2020 Joachim Stolberg - - MIT - See license.txt for more information - -]]-- - --- for lazy programmers -local M = minetest.get_meta -local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local S2P = minetest.string_to_pos -local S = minecart.S - -local RegisteredInventories = {} - -local param2_to_dir = {[0]= - {x=0, y=0, z=1}, - {x=1, y=0, z=0}, - {x=0, y=0, z=-1}, - {x=-1, y=0, z=0}, - {x=0, y=-1, z=0}, - {x=0, y=1, z=0} -} - --- Registered carts -local tValidCarts = {} -- [] = -local lValidCartNodes = {} -local tValidCartEntities = {} - -minetest.tValidCarts = tValidCarts - -function minecart.register_cart_names(cart_name_stopped, cart_name_running) - tValidCarts[cart_name_stopped] = cart_name_running - - if minetest.registered_nodes[cart_name_stopped] then - lValidCartNodes[#lValidCartNodes+1] = cart_name_stopped - end - if minetest.registered_nodes[cart_name_running] then - lValidCartNodes[#lValidCartNodes+1] = cart_name_running - end - if minetest.registered_entities[cart_name_stopped] then - tValidCartEntities[cart_name_stopped] = true - end - if minetest.registered_entities[cart_name_running] then - tValidCartEntities[cart_name_running] = true - end -end - -function minecart.get_node_lvm(pos) - local node = minetest.get_node_or_nil(pos) - if node then - return node - end - local vm = minetest.get_voxel_manip() - local MinEdge, MaxEdge = vm:read_from_map(pos, pos) - local data = vm:get_data() - local param2_data = vm:get_param2_data() - local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) - local idx = area:indexp(pos) - if data[idx] and param2_data[idx] then - return { - name = minetest.get_name_from_content_id(data[idx]), - param2 = param2_data[idx] - } - end - return {name="ignore", param2=0} -end - -function minecart.stopped(vel, tolerance) - tolerance = tolerance or 0.05 - return math.abs(vel.x) < tolerance and math.abs(vel.z) < tolerance -end - -local function is_air_like(name) - local ndef = minetest.registered_nodes[name] - if ndef and ndef.buildable_to then - return true - end - return false -end - -function minecart.range(val, min, max) - val = tonumber(val) - if val < min then return min end - if val > max then return max end - return val -end - -function minecart.get_next_node(pos, param2) - local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos - local node = minetest.get_node(pos2) - return pos2, node -end - -local function get_cart_object(pos, radius) - for _, object in pairs(minetest.get_objects_inside_radius(pos, radius or 0.5)) do - if tValidCartEntities[object:get_entity_name()] then - local vel = object:get_velocity() - if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing? - return object - end - end - end -end - --- check if cart can be pushed -function minecart.check_cart_for_pushing(pos, param2, radius) - local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos - - if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then - return true - end - - return get_cart_object(pos2, radius) ~= nil -end - --- check if cargo can be loaded -function minecart.check_cart_for_loading(pos, param2, radius) - local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos - - if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then - return true - end - - for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do - if object:get_entity_name() == "minecart:cart" then - local vel = object:get_velocity() - if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing? - return true - end - end - end - - return false -end - -local get_next_node = minecart.get_next_node -local check_cart_for_loading = minecart.check_cart_for_loading -local check_cart_for_pushing = minecart.check_cart_for_pushing - --- Take the given number of items from the inv. --- Returns nil if ItemList is empty. -function minecart.inv_take_items(inv, listname, num) - if inv:is_empty(listname) then - return nil - end - local size = inv:get_size(listname) - for idx = 1, size do - local items = inv:get_stack(listname, idx) - if items:get_count() > 0 then - local taken = items:take_item(num) - inv:set_stack(listname, idx, items) - return taken - end - end - return nil -end - -function minecart.take_items(pos, param2, num) - local npos, node - if param2 then - npos, node = get_next_node(pos, (param2 + 2) % 4) - else - npos, node = pos, minetest.get_node(pos) - end - local def = RegisteredInventories[node.name] - local owner = M(pos):get_string("owner") - local inv = minetest.get_inventory({type="node", pos=npos}) - - if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then - return minecart.inv_take_items(inv, def.take_listname, num) - elseif def and def.take_item then - return def.take_item(npos, num, owner) - else - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.minecart_hopper_takeitem then - return ndef.minecart_hopper_takeitem(npos, num) - end - end -end - -function minecart.put_items(pos, param2, stack) - local npos, node = get_next_node(pos, param2) - local def = RegisteredInventories[node.name] - local owner = M(pos):get_string("owner") - local inv = minetest.get_inventory({type="node", pos=npos}) - - if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then - local leftover = inv:add_item(def.put_listname, stack) - if leftover:get_count() > 0 then - return leftover - end - elseif def and def.put_item then - return def.put_item(npos, stack, owner) - elseif is_air_like(node.name) or check_cart_for_loading(npos) then - minetest.add_item(npos, stack) - else - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.minecart_hopper_additem then - local leftover = ndef.minecart_hopper_additem(npos, stack) - if leftover:get_count() > 0 then - return leftover - end - else - return stack - end - end -end - -function minecart.untake_items(pos, param2, stack) - local npos, node - if param2 then - npos, node = get_next_node(pos, (param2 + 2) % 4) - else - npos, node = pos, minetest.get_node(pos) - end - local def = RegisteredInventories[node.name] - local inv = minetest.get_inventory({type="node", pos=npos}) - - if def and inv and def.put_listname then - return inv:add_item(def.put_listname, stack) - elseif def and def.untake_item then - return def.untake_item(npos, stack) - else - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.minecart_hopper_untakeitem then - return ndef.minecart_hopper_untakeitem(npos, stack) - end - end -end - -function minecart.punch_cart(pos, param2, radius, dir) - local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos - - local pos3 = minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) - if pos3 then - local node = minetest.get_node(pos3) - --print(node.name) - minecart.node_on_punch(pos3, node, nil, nil, tValidCarts[node.name], dir) - return true - end - - local obj = get_cart_object(pos2, radius) - if obj then - obj:punch(obj, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = 1}, - }, dir) - end -end - --- Register inventory node for hopper access --- (for examples, see below) -function minecart.register_inventory(node_names, def) - for _, name in ipairs(node_names) do - RegisteredInventories[name] = { - allow_put = def.put and def.put.allow_inventory_put, - put_listname = def.put and def.put.listname, - allow_take = def.take and def.take.allow_inventory_take, - take_listname = def.take and def.take.listname, - put_item = def.put and def.put.put_item, - take_item = def.take and def.take.take_item, - untake_item = def.take and def.take.untake_item, - } - end -end - -function minecart.register_cart_entity(entity_name, node_name, entity_def) - entity_def.velocity = {x=0, y=0, z=0} -- only used on punch - entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch - entity_def.old_pos = nil - entity_def.old_switch = 0 - entity_def.node_name = node_name - minetest.register_entity(entity_name, entity_def) - -- register node for punching - minecart.register_cart_names(node_name, entity_name) -end - -minetest.register_on_player_receive_fields(function(player, formname, fields) - if formname == "minecart:userID_node" then - if fields.exit == "Save" or fields.key_enter == "true" then - local cart_pos = S2P(player:get_meta():get_string("cart_pos")) - local userID = tonumber(fields.userID) or 0 - M(cart_pos):set_int("userID", userID) - M(cart_pos):set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..player:get_player_name()..": "..userID) - minecart.node_at_station(player:get_player_name(), userID, cart_pos) - end - return true - end - if formname == "minecart:userID_entity" then - if fields.exit == "Save" or fields.key_enter == "true" then - local cart_pos = S2P(player:get_meta():get_string("cart_pos")) - local obj = get_cart_object(cart_pos) - if obj then - local entity = obj:get_luaentity() - entity.userID = tonumber(fields.userID) or 0 - obj:set_nametag_attributes({color = "#ffff00", text = entity.owner..": "..entity.userID}) - minecart.update_userID(entity.myID, entity.userID) - end - end - return true - end - return false -end) - -minecart.register_inventory({"minecart:hopper"}, { - put = { - allow_inventory_put = function(pos, stack, player_name) - local owner = M(pos):get_string("owner") - return owner == player_name - end, - listname = "main", - }, - take = { - allow_inventory_take = function(pos, stack, player_name) - local owner = M(pos):get_string("owner") - return owner == player_name - end, - listname = "main", - }, -}) diff --git a/minecart/locale/minecart.de.tr b/minecart/locale/minecart.de.tr index 94954c5..615b0cc 100644 --- a/minecart/locale/minecart.de.tr +++ b/minecart/locale/minecart.de.tr @@ -1,37 +1,58 @@ # textdomain: minecart - +Station name=Stationsname +Waiting time/sec=Wartezeit/s +connected to=verbunden mit +Minecart Railway Buffer=Minecart Prellbock +Summary=Zusammenfassung 1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=1. Baue eine Schienenstrecke mit zwei Enden. Kreuzungen sind zulässig, solange jede Route ihre eigenen Start- und Endpunkte hat. -10. Check the cart state via the chat command: /mycart @n '' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart @n ist die Wagennummer -11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. Klicke mit gedrückter Shift-Taste auf den Wagen, um Gegenstände wieder auszuladen. -12. Dig the empty cart with a second 'sneak+click' (as usual).=10. Klicke erneut mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. 2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=2. Platziere einen Prellbock an beide Schienenenden (Prellböcke sind zwingend notwendig, sie speichern die Routen- und Zeit-Informationen). 3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=3. Gib beiden Prellböcken eindeutige Stationsnamen wie: Stuttgart und München. 4. Place a Minecart at a buffer and give it a cart number (1..999)=4. Platziere einen Minecart Wagen an einem Prellbock und gib dem Wagen eine Wagennummer (1..999) 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit dem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung. 6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart') -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in einem oder in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch. +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch. 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke). 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet. -Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen -Minecart=Minecart -Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts) -Minecart Cart=Wagen -Minecart Hopper=Minecart Hopper -Minecart Landmark=Minecart Meilenstein -Minecart Railway Buffer=Minecart Prellbock -Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem -Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um die Gegenstände wieder auszuladen -Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang) -Station name=Stationsname -Stop time/sec=Haltezeit/s -Summary=Zusammenfassung +10. Check the cart state via the chat command: /mycart @n '' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart @n ist die Wagennummer +11. Drop items into the Minecart and punch the cart to start it.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. +12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=10. Klicke mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. Die Gegenstände fallen dann zu Boden. +Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um Cart und Gegenstände zurückzuerhalten Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können. +Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang) Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Um Wagen zu be- und entladen. Der Hopper kann Gegenstände aus Kisten Holen und legen, sowie diese in Wagen fallen lassen bzw. aus Wagen entnehmen. Um einen Wagen zu entladen, muss der Hopper unter die Schiene platziert werden. Um einen Wagen zu beladen, muss der Hopper direkt neben die Schiene platziert werden. +Minecart=Minecart +Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem +Minecart Cart=Wagen +Minecart Speed Signs=Geschwindigkeitsbegrenzungszeichen +If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Wenn mehrere Wagen auf einer Route fahren, kann es vorkommen,@ndass eine Prellbock Position bereits belegt ist und ein Wagen daher früher anhält.@nDer Cart Anschieber dient in diesem Fall dazu, die Wagen wieder in Richtung Prellbock anzuschieben.@nDieser Block muss unter der Schiene mit 2 m Abstand vor dem Prellbock platziert werden. +Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Begrenze die Geschwindigkeit der Wagen mit Geschwindigkeitsbegrenzungszeichen@n@nDie Geschwindigkeit der Carts wird wie bisher auch über "power rails" beeinflusst. "Brake rails" sind ohne Bedeutung, das Cart bremst hier nicht. Die maximale Geschwindigkeit beträgt 8 m/s. Dies setzt eine Verhältnis von "power rails" zu "normal rails" von 1 zu 4 auf einem ebenen Streckenabschnitt voraus. Ein Streckenabschnitt ist dabei ein Reihe von Schienenblöcken ohne Richtungsänderung. Nach jeder Kurve/Knick wird die Geschwindigkeit für den nächsten Streckenabschnitt neu bestimmt, wobei hier der Schwung des Carts mit berücksichtigt wird. So kann ein Cart auch über kurze Streckenabschnitt ohne "power rails" rollen.@n@nUm das Cart zusätzlich an bestimmten Stellen abzubremsen (an Weichen oder vor einen Puffer), können Geschwindigkeitsbegrenzungszeichen an der Strecke platziert werden. Durch diese Zeichen kann die Geschwindigkeit auf 4, 2, oder 1 m/s reduziert werden. Durch das Aufhebungszeichen kann die Geschwindigkeitsbegrenzung wieder aufgehoben werden.@n@nDie Geschwindigkeitsbegrenzungszeichen müssen so neben die Strecke platziert werden, dass sie vom Cart ablesbar sind. Dies erlaubt damit unterschiedliche Geschwindigkeiten pro Fahrtrichtung. +Minecart Hopper=Minecart Hopper +Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts) +Output cart state and position, or a list of carts, if no cart number is given.=Gibt Status und Position des Wagens, oder eine Liste aller Wagen aus, wenn keine Wagennummer angegeben ist. +List of carts=Liste aller Wagen +Enter cart number=Gebe Cart Nummer ein +Save=Speichern [minecart] Area is protected!=[minecart] Bereich ist geschützt! -[minecart] Cart is protected by = Wagen ist geschützt durch -[minecart] Recording canceled!=[minecart] Aufzeichnung abgebrochen! +Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen +Minecart Landmark=Minecart Meilenstein +Cart Pusher=Wagen Anschieber +left=links +right=rechts +straight=geradeaus +Recording=Aufzeichnung +speed=Tempo +next junction=nächste Weiche +Travel time=Fahrzeit [minecart] Route stored!=[minecart] Strecke gespeichert -[minecart] Start route recording!=[minecart] Starte die Streckenaufzeichnung! -connected to=verbunden mit +[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Geschw. @= %u m/s, Zeit @= %u s, Routenlänge @= %u m +Speed "1"=Tempo "1" +Speed "2"=Tempo "2" +Speed "4"=Tempo "4" +No speed limit=Keine Geschwindigkeitsbegrenzung +Cart List=Cart Liste +Cart Terminal=Cart Terminal + + ##### not used anymore ##### +Used to push a cart if the cart does not stop directly at a buffer. Block has to be placed below the rail.=Wird verwendet, um einen Wagen anzuschieben, wenn der Wagen nicht direkt an einem Puffer anhält. Der Block muss unter der Schiene platziert werden. diff --git a/minecart/locale/template.txt b/minecart/locale/template.txt index 8d66077..b9a4ae5 100644 --- a/minecart/locale/template.txt +++ b/minecart/locale/template.txt @@ -1,33 +1,53 @@ +# textdomain: minecart +Station name= +Waiting time/sec= +connected to= +Minecart Railway Buffer= +Summary= 1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.= -10. Check the cart state via the chat command: /mycart @n '' is the cart number= -11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.= -12. Dig the empty cart with a second 'sneak+click' (as usual).= 2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).= 3. Give both Railway Buffers unique station names, like Oxford and Cambridge.= 4. Place a Minecart at a buffer and give it a cart number (1..999)= 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).= 6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').= -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.= +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.= 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).= 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.= -Allow to dig/place rails in Minecart Landmark areas= -Minecart= -Minecart (Sneak+Click to pick up)= -Minecart Cart= -Minecart Hopper= -Minecart Landmark= -Minecart Railway Buffer= -Minecart, the lean railway transportation automation system= -Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back= -Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)= -Station name= -Stop time/sec= -Summary= +10. Check the cart state via the chat command: /mycart @n '' is the cart number= +11. Drop items into the Minecart and punch the cart to start it.= +12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.= +Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back= Used as buffer on both rail ends. Needed to be able to record the cart routes= +Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)= Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.= +Minecart= +Minecart, the lean railway transportation automation system= +Minecart Cart= +Minecart Speed Signs= +If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.= +Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.= +Minecart Hopper= +Minecart (Sneak+Click to pick up)= +Output cart state and position, or a list of carts, if no cart number is given.= +List of carts= +Enter cart number= +Save= [minecart] Area is protected!= -[minecart] Cart is protected by = -[minecart] Recording canceled!= +Allow to dig/place rails in Minecart Landmark areas= +Minecart Landmark= +Cart Pusher= +left= +right= +straight= +Recording= +speed= +next junction= +Travel time= [minecart] Route stored!= -[minecart] Start route recording!= -connected to= \ No newline at end of file +[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m= +Speed "1"= +Speed "2"= +Speed "4"= +No speed limit= +Cart List= +Cart Terminal= diff --git a/minecart/minecart.lua b/minecart/minecart.lua index 6918d89..1237019 100644 --- a/minecart/minecart.lua +++ b/minecart/minecart.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -11,69 +11,92 @@ ]]-- local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib1.lua") +local M = minetest.get_meta -lib:init(false) - -local cart_entity = { - initial_properties = { - physical = false, -- otherwise going uphill breaks - collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, - visual = "mesh", - mesh = "carts_cart.b3d", - visual_size = {x=1, y=1}, - textures = {"carts_cart.png^minecart_cart.png"}, - static_save = false, - }, - ------------------------------------ changed - owner = nil, - ------------------------------------ changed - driver = nil, - punched = false, -- used to re-send velocity and position - velocity = {x=0, y=0, z=0}, -- only used on punch - old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch - old_pos = nil, - old_switch = 0, - railtype = nil, - cargo = {}, - on_rightclick = lib.on_rightclick, - on_activate = lib.on_activate, - on_detach_child = lib.on_detach_child, - on_punch = lib.on_punch, - on_step = lib.on_step, -} - - -minetest.register_entity("minecart:cart", cart_entity) - -minecart.register_cart_names("minecart:cart", "minecart:cart") - - -minetest.register_craftitem("minecart:cart", { +minetest.register_node("minecart:cart", { description = S("Minecart (Sneak+Click to pick up)"), - inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"), - wield_image = "carts_cart_side.png", - on_place = function(itemstack, placer, pointed_thing) - -- use cart as tool - local under = pointed_thing.under - local node = minetest.get_node(under) - local udef = minetest.registered_nodes[node.name] - if udef and udef.on_rightclick and - not (placer and placer:is_player() and - placer:get_player_control().sneak) then - return udef.on_rightclick(under, node, placer, itemstack, - pointed_thing) or itemstack - end + tiles = { + -- up, down, right, left, back, front + "carts_cart_top.png^minecart_appl_cart_top.png", + "carts_cart_top.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-8/16,-8/16,-8/16, 8/16, 8/16,-7/16}, + {-8/16,-8/16, 7/16, 8/16, 8/16, 8/16}, + {-8/16,-8/16,-8/16, -7/16, 8/16, 8/16}, + { 7/16,-8/16,-8/16, 8/16, 8/16, 8/16}, + {-8/16,-8/16,-8/16, 8/16,-6/16, 8/16}, + }, + }, +-- collision_box = { +-- type = "fixed", +-- fixed = { +-- {-8/16,-8/16,-8/16, 8/16,-4/16, 8/16}, +-- }, +-- }, + paramtype2 = "facedir", + paramtype = "light", + use_texture_alpha = minecart.CLIP, + sunlight_propagates = true, + is_ground_content = false, + groups = {cracky = 2, crumbly = 2, choppy = 2}, + node_placement_prediction = "", + diggable = false, + + on_place = minecart.on_nodecart_place, + on_punch = minecart.on_nodecart_punch, - if not pointed_thing.type == "node" then - return + on_rightclick = function(pos, node, clicker) + if clicker and clicker:is_player() then + if M(pos):get_int("userID") ~= 0 then + -- enter the cart + local object = minecart.node_to_entity(pos, "minecart:cart", "minecart:cart_entity") + minecart.manage_attachment(clicker, object:get_luaentity(), true) + else + minecart.show_formspec(pos, clicker) + end end - - return lib.add_cart(itemstack, placer, pointed_thing, "minecart:cart") + end, + + set_cargo = function(pos, data) + for _,item in ipairs(data or {}) do + minetest.add_item(pos, ItemStack(item)) + end + end, + + get_cargo = function(pos) + local data = {} + for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do + local entity = obj:get_luaentity() + if not obj:is_player() and entity and entity.name == "__builtin:item" then + obj:remove() + data[#data + 1] = entity.itemstring + end + end + return data end, }) +minecart.register_cart_entity("minecart:cart_entity", "minecart:cart", "default", { + initial_properties = { + physical = false, + collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + visual = "wielditem", + textures = {"minecart:cart"}, + visual_size = {x=0.66, y=0.66, z=0.66}, + static_save = false, + }, + driver_allowed = true, +}) + + minetest.register_craft({ output = "minecart:cart", recipe = { diff --git a/minecart/mods_support.lua b/minecart/mods_support.lua index 14aaaa7..cebb9ea 100644 --- a/minecart/mods_support.lua +++ b/minecart/mods_support.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information diff --git a/minecart/monitoring.lua b/minecart/monitoring.lua index a77d89e..4d66b5e 100644 --- a/minecart/monitoring.lua +++ b/minecart/monitoring.lua @@ -3,274 +3,271 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information ]]-- --- Some notes: --- 1) Entity IDs are volatile. For each server restart all carts get new IDs. --- 2) Monitoring is performed for entities only. Stopped carts in form of --- real nodes need no monitoring. --- 3) But nodes at stations have to call 'node_at_station' to be "visible" --- for the chat commands - - -- for lazy programmers local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib3.lua") -local CartsOnRail = minecart.CartsOnRail -- from storage.lua -local get_route = minecart.get_route -- from storage.lua -local NodesAtStation = {} +local tCartsOnRail = minecart.CartsOnRail +local Queue = {} +local first = 0 +local last = -1 --- --- Helper functions --- -local function get_pos_vel_pitch_yaw(item) - if item.start_time and item.start_key then -- cart on recorded route - local run_time = minetest.get_gametime() - item.start_time - local waypoints = get_route(item.start_key).waypoints - local waypoint = waypoints[run_time] - if waypoint then - return S2P(waypoint[1]), S2P(waypoint[2]), 0, 0 - end - end - if item.last_pos then - item.last_pos = vector.round(item.last_pos) - if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then - return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0 - end - item.last_pos.y = item.last_pos.y - 1 - if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then - return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0 - end - end - return item.start_pos, {x=0, y=0, z=0}, 0, 0 +local function push(cycle, item) + last = last + 1 + item.cycle = cycle + Queue[last] = item end --- --- Monitoring of cart entities --- -function minecart.add_to_monitoring(obj, myID, owner, userID) - local pos = vector.round(obj:get_pos()) - CartsOnRail[myID] = { - start_key = lib.get_route_key(pos), - start_pos = pos, - owner = owner, -- needed for query API - userID = userID, -- needed for query API - stopped = true, - entity_name = obj:get_entity_name() - } -end - --- Called after cart number formspec is closed -function minecart.update_userID(myID, userID) - if CartsOnRail[myID] then - CartsOnRail[myID].userID = userID +local function pop(cycle) + if first > last then return end + local item = Queue[first] + if item.cycle < cycle then + Queue[first] = nil -- to allow garbage collection + first = first + 1 + return item end end --- When cart entity is removed -function minecart.remove_from_monitoring(myID) - if myID then - CartsOnRail[myID] = nil - minecart.store_carts() - end -end - --- For node carts at stations -function minecart.node_at_station(owner, userID, pos) - NodesAtStation[owner] = NodesAtStation[owner] or {} - NodesAtStation[owner][userID] = pos -end - -function minecart.start_cart(pos, myID) - local item = CartsOnRail[myID] - if item and item.stopped then - item.stopped = false - item.start_pos = pos - item.start_time = nil - -- cart started from a buffer? - local start_key = lib.get_route_key(pos) - if start_key then - item.start_time = minetest.get_gametime() - item.start_key = start_key - item.junctions = minecart.get_route(start_key).junctions - minecart.store_carts() +local function is_player_nearby(pos) + for _, object in pairs(minetest.get_objects_inside_radius(pos, 64)) do + if object:is_player() then return true end end - return false end -function minecart.stop_cart(pos, myID) - local item = CartsOnRail[myID] - if item and not item.stopped then - item.start_time = nil - item.start_key = nil - item.start_pos = nil - item.junctions = nil - item.stopped = true - minecart.store_carts() - return true +local function zombie_to_entity(pos, cart, checkpoint) + local vel = {x = 0, y = 0, z = 0} + local obj = minecart.add_entitycart(pos, cart.node_name, cart.entity_name, + vel, cart.cargo, cart.owner, cart.userID) + if obj then + local entity = obj:get_luaentity() + entity.reenter = checkpoint + entity.junctions = cart.junctions + entity.is_running = true + entity.arrival_time = 0 + cart.objID = entity.objID end - return false end -local function monitoring() - local to_be_added = {} - for key, item in pairs(CartsOnRail) do - local entity = minetest.luaentities[key] - --print("Cart:", key, item.owner, item.userID, item.stopped) - if entity then -- cart entity running - local pos = entity.object:get_pos() - local vel = entity.object:get_velocity() - local rot = entity.object:get_rotation() - if pos and vel and rot then - if not minetest.get_node_or_nil(pos) then -- unloaded area - lib.unload_cart(pos, vel, entity, item) - item.stopped = minecart.stopped(vel) - end - -- store last pos from cart - item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, rot.x, rot.y - end - else -- no cart running - local pos, vel, pitch, yaw = get_pos_vel_pitch_yaw(item) - if pos and vel then - if minetest.get_node_or_nil(pos) then -- loaded area - if pitch > 0 then - pos.y = pos.y + 0.5 - end - local myID = lib.load_cart(pos, vel, pitch, yaw, item) - if myID then - item.stopped = minecart.stopped(vel) - to_be_added[myID] = table.copy(item) - CartsOnRail[key] = nil -- invalid old ID - end - end - item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, pitch, yaw - else - -- should never happen - minetest.log("error", "[minecart] Cart of owner "..(item.owner or "nil").." got lost") - CartsOnRail[key] = nil - end +local function get_checkpoint(cart) + local cp = cart.checkpoints[cart.idx] + if not cp then + cart.idx = #cart.checkpoints + cp = cart.checkpoints[cart.idx] + end + local pos = H2P(cp[1]) +-- if M(pos):contains("waypoints") then +-- print("get_checkpoint", P2S(H2P(cp[1])), P2S(H2P(cp[2]))) +-- end + return cp, cart.idx == #cart.checkpoints +end + +-- Function returns the cart state ("running" / "stopped") and +-- the station name or position string, or if cart is running, +-- the distance to the query_pos. +local function get_cart_state_and_loc(name, userID, query_pos) + if tCartsOnRail[name] and tCartsOnRail[name][userID] then + local cart = tCartsOnRail[name][userID] + local pos = cart.last_pos or cart.pos + local loc = minecart.get_buffer_name(cart.pos) or + math.floor(vector.distance(pos, query_pos)) + if cart.objID == 0 then + return "stopped", minecart.get_buffer_name(cart.pos) or + math.floor(vector.distance(pos, query_pos)), cart.node_name + else + return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name end end - -- table maintenance - local is_changed = false - for key,val in pairs(to_be_added) do - CartsOnRail[key] = val - is_changed = true + return "unknown", 0, "unknown" +end + +local function get_cart_info(owner, userID, query_pos) + local state, loc, name = get_cart_state_and_loc(owner, userID, query_pos) + local cart_type = minecart.tCartTypes[name] or "unknown" + if type(loc) == "number" then + return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " " .. loc .. " m away " + else + return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " at ".. loc .. " " end - if is_changed then +end + +local function monitoring(cycle) + local cart = pop(cycle) + + while cart do + -- All running cars + if cart.objID and cart.objID ~= 0 then + cart.idx = cart.idx + 1 + local entity = minetest.luaentities[cart.objID] + if entity then -- cart entity running + local pos = entity.object:get_pos() + if pos then + cart.last_pos = vector.round(pos) + --print("entity card " .. cart.userID .. " at " .. P2S(cart.last_pos)) + else + print("entity card without pos!") + end + push(cycle, cart) + elseif cart.checkpoints then + local cp, last_cp = get_checkpoint(cart) + if cp then + cart.last_pos = H2P(cp[1]) + --print("zombie " .. cart.userID .. " at " .. P2S(cart.last_pos)) + if is_player_nearby(cart.last_pos) or last_cp then + zombie_to_entity(cart.last_pos, cart, cp) + end + push(cycle, cart) + else + print("zombie got lost") + end + else + local pos = cart.last_pos or cart.pos + pos = minecart.add_nodecart(pos, cart.node_name, 0, cart.cargo, cart.owner, cart.userID) + cart.objID = 0 + cart.pos = pos + --print("cart to node", cycle, cart.userID, P2S(pos)) + end + elseif cart and not cart.objID and tCartsOnRail[cart.owner] then + -- Delete carts marked as "to be deleted" + tCartsOnRail[cart.owner][cart.userID] = nil + end + cart = pop(cycle) + end + minetest.after(2, monitoring, cycle + 1) +end + +minetest.after(5, monitoring, 2) + + +function minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name) + --print("monitoring_add_cart", owner, userID) + tCartsOnRail[owner] = tCartsOnRail[owner] or {} + tCartsOnRail[owner][userID] = { + owner = owner, + userID = userID, + objID = 0, + pos = pos, + idx = 0, + node_name = node_name, + entity_name = entity_name, + } + minecart.store_carts() +end + +function minecart.start_monitoring(owner, userID, pos, objID, checkpoints, junctions, cargo) + --print("start_monitoring", owner, userID) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + tCartsOnRail[owner][userID].pos = pos + tCartsOnRail[owner][userID].objID = objID + tCartsOnRail[owner][userID].checkpoints = checkpoints + tCartsOnRail[owner][userID].junctions = junctions + tCartsOnRail[owner][userID].cargo = cargo + tCartsOnRail[owner][userID].idx = 0 + push(0, tCartsOnRail[owner][userID]) minecart.store_carts() end - minetest.after(1, monitoring) end --- delay the start to prevent cart disappear into nirvana -minetest.register_on_mods_loaded(function() - minetest.after(10, monitoring) -end) + +function minecart.stop_monitoring(owner, userID, pos) + --print("stop_monitoring", owner, userID) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + tCartsOnRail[owner][userID].pos = pos + tCartsOnRail[owner][userID].objID = 0 + minecart.store_carts() + end +end + +function minecart.monitoring_remove_cart(owner, userID) + --print("monitoring_remove_cart", owner, userID) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + tCartsOnRail[owner][userID].objID = nil + tCartsOnRail[owner][userID] = nil + minecart.store_carts() + end +end + +function minecart.monitoring_valid_cart(owner, userID, pos, node_name) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + return vector.equals(tCartsOnRail[owner][userID].pos, pos) and + tCartsOnRail[owner][userID].node_name == node_name + end +end + +function minecart.userID_available(owner, userID) + return not tCartsOnRail[owner] or tCartsOnRail[owner][userID] == nil +end + +function minecart.get_cart_monitoring_data(owner, userID) + if tCartsOnRail[owner] then + return tCartsOnRail[owner][userID] + end +end -- -- API functions -- --- Return a list of carts with current position and speed. -function minecart.get_cart_list() - local tbl = {} - for id, item in pairs(CartsOnRail) do - local pos, speed = calc_pos_and_vel(item) - tbl[#tbl+1] = {pos = pos, speed = speed, id = id} - end - return tbl -end - -local function get_cart_pos(query_pos, cart_pos) - local dist = math.floor(vector.distance(cart_pos, query_pos)) - local station = lib.get_station_name(cart_pos) - return station or dist -end - -local function get_cart_state(name, userID) - for id, item in pairs(CartsOnRail) do - if item.owner == name and item.userID == userID then - return item.stopped and "stopped" or "running", item.last_pos - end - end - return nil, nil -end +-- Needed by storage to re-construct the queue after server start +minecart.push = push minetest.register_chatcommand("mycart", { params = "", - description = "Output cart state and position, or a list of carts, if no cart number is given.", - func = function(name, param) + description = S("Output cart state and position, or a list of carts, if no cart number is given."), + func = function(owner, param) local userID = tonumber(param) - local query_pos = minetest.get_player_by_name(name):get_pos() + local query_pos = minetest.get_player_by_name(owner):get_pos() if userID then - -- First check if it is a node cart at a station - local cart_pos = NodesAtStation[name] and NodesAtStation[name][userID] - if cart_pos then - local pos = get_cart_pos(query_pos, cart_pos) - return true, "Cart #"..userID.." stopped at "..pos.." " - end - -- Check all running carts - local state, cart_pos = get_cart_state(name, userID) - if state and cart_pos then - local pos = get_cart_pos(query_pos, cart_pos) - if type(pos) == "string" then - return true, "Cart #"..userID.." stopped at "..pos.." " - elseif state == "running" then - return true, "Cart #"..userID.." running "..pos.." m away " - else - return true, "Cart #"..userID.." stopped "..pos.." m away " - end - end - return false, "Cart #"..userID.." is unknown" - else + return true, get_cart_info(owner, userID, query_pos) + elseif tCartsOnRail[owner] then -- Output a list with all numbers local tbl = {} - for userID, pos in pairs(NodesAtStation[name] or {}) do + for userID, cart in pairs(tCartsOnRail[owner]) do tbl[#tbl + 1] = userID end - for id, item in pairs(CartsOnRail) do - if item.owner == name then - tbl[#tbl + 1] = item.userID - end - end - return true, "List of carts: "..table.concat(tbl, ", ").." " + return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." " end end }) function minecart.cmnd_cart_state(name, userID) - -- First check if it is a node cart at a station - local pos = NodesAtStation[name] and NodesAtStation[name][userID] - if pos then - return "stopped" - end - return get_cart_state(name, userID) + local state, loc = get_cart_state_and_loc(name, userID, {x=0, y=0, z=0}) + return state end function minecart.cmnd_cart_location(name, userID, query_pos) - -- First check if it is a node cart at a station - local station = NodesAtStation[name] and NodesAtStation[name][userID] - if station then - return station + local state, loc = get_cart_state_and_loc(name, userID, query_pos) + return loc +end + +function minecart.get_cart_list(pos, name) + local userIDs = {} + local carts = {} + + for userID, cart in pairs(tCartsOnRail[name] or {}) do + userIDs[#userIDs + 1] = userID end - local state, cart_pos = get_cart_state(name, userID) - if state then - return get_cart_pos(query_pos, cart_pos) + + table.sort(userIDs, function(a,b) return a < b end) + + for _, userID in ipairs(userIDs) do + carts[#carts + 1] = get_cart_info(name, userID, pos) end + + return table.concat(carts, "\n") end minetest.register_on_mods_loaded(function() @@ -357,4 +354,3 @@ minetest.register_on_mods_loaded(function() }) end end) - diff --git a/minecart/nodelib.lua b/minecart/nodelib.lua new file mode 100644 index 0000000..efb540c --- /dev/null +++ b/minecart/nodelib.lua @@ -0,0 +1,139 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = minecart.S +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos + +function minecart.get_nodecart_nearby(pos, param2, radius) + local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos + local pos3 = minetest.find_node_near(pos2, radius or 0.5, minecart.lCartNodeNames, true) + if pos3 then + return pos3, minetest.get_node(pos3) + end +end + +-- Convert node to entity and start cart +function minecart.start_nodecart(pos, node_name, puncher, punch_dir) + local owner = M(pos):get_string("owner") + local userID = M(pos):get_int("userID") + -- check if valid cart + if not minecart.monitoring_valid_cart(owner, userID, pos, node_name) then + --print("invalid cart", owner, userID, P2S(pos), node_name) + M(pos):set_string("infotext", + minetest.get_color_escape_sequence("#FFFF00") .. owner .. ": 0") + return + end + -- Only the owner or a noplayer can start the cart, but owner has to be online + if minecart.is_owner(puncher, owner) and minetest.get_player_by_name(owner) and + userID ~= 0 then + local entity_name = minecart.tNodeNames[node_name] + local obj = minecart.node_to_entity(pos, node_name, entity_name) + if obj then + local entity = obj:get_luaentity() + if puncher then + local yaw = puncher:get_look_horizontal() + entity.object:set_rotation({x = 0, y = yaw, z = 0}) + elseif punch_dir then + local yaw = minetest.dir_to_yaw(punch_dir) + entity.object:set_rotation({x = 0, y = yaw, z = 0}) + end + minecart.start_entitycart(entity, pos) + end + end +end + +function minecart.show_formspec(pos, clicker) + local owner = M(pos):get_string("owner") + if minecart.is_owner(clicker, owner) then + clicker:get_meta():set_string("cart_pos", P2S(pos)) + minetest.show_formspec(owner, "minecart:userID_node", + "size[4,3]" .. + "label[0,0;" .. S("Enter cart number") .. ":]" .. + "field[1,1;3,1;userID;;]" .. + "button_exit[1,2;2,1;exit;" .. S("Save") .. "]") + end +end + +-- Player places the node +function minecart.on_nodecart_place(itemstack, placer, pointed_thing) + local node_name = itemstack:get_name() + local param2 = minetest.dir_to_facedir(placer:get_look_dir()) + local owner = placer:get_player_name() + + -- Add node + if minecart.is_rail(pointed_thing.under) then + minecart.add_nodecart(pointed_thing.under, node_name, param2, {}, owner, 0) + placer:get_meta():set_string("cart_pos", P2S(pointed_thing.under)) + minecart.show_formspec(pointed_thing.under, placer) + elseif minecart.is_rail(pointed_thing.above) then + minecart.add_nodecart(pointed_thing.above, node_name, param2, {}, owner, 0) + placer:get_meta():set_string("cart_pos", P2S(pointed_thing.above)) + minecart.show_formspec(pointed_thing.above, placer) + else + return itemstack + end + + minetest.sound_play({name = "default_place_node_metal", gain = 0.5}, + {pos = pointed_thing.above}) + + if not (creative and creative.is_enabled_for + and creative.is_enabled_for(placer:get_player_name())) then + itemstack:take_item() + end + + return itemstack +end + +-- Start the node cart (or dig by shift+leftclick) +function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing) + --print("on_nodecart_punch") + local owner = M(pos):get_string("owner") + local userID = M(pos):get_int("userID") + if minecart.is_owner(puncher, owner) then + if puncher:get_player_control().sneak then + local ndef = minetest.registered_nodes[node.name] + if not ndef.has_cargo or not ndef.has_cargo(pos) then + minecart.remove_nodecart(pos) + minecart.add_node_to_player_inventory(pos, puncher, node.name) + minecart.monitoring_remove_cart(owner, userID) + end + else + minecart.start_nodecart(pos, node.name, puncher) + end + end +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname == "minecart:userID_node" then + if fields.exit or fields.key_enter == "true" then + local cart_pos = S2P(player:get_meta():get_string("cart_pos")) + local owner = M(cart_pos):get_string("owner") + if minecart.is_owner(player, owner) then + local userID = tonumber(fields.userID) or 0 + if minecart.userID_available(owner, userID) then + M(cart_pos):set_int("userID", userID) + M(cart_pos):set_string("infotext", + minetest.get_color_escape_sequence("#FFFF00") .. + player:get_player_name() .. ": " .. userID) + local node = minetest.get_node(cart_pos) + local entity_name = minecart.tNodeNames[node.name] + minecart.monitoring_add_cart(owner, userID, cart_pos, node.name, entity_name) + end + end + end + return true + end + return false +end) diff --git a/minecart/protection.lua b/minecart/protection.lua index bc02bfc..6d4a542 100644 --- a/minecart/protection.lua +++ b/minecart/protection.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -198,3 +198,8 @@ minecart.register_protected_node("minecart:buffer") minecart.register_protected_node("minecart:ballast") minecart.register_protected_node("minecart:ballast_slope") minecart.register_protected_node("minecart:ballast_ramp") +minecart.register_protected_node("minecart:speed1") +minecart.register_protected_node("minecart:speed2") +minecart.register_protected_node("minecart:speed4") +minecart.register_protected_node("minecart:speed8") + diff --git a/minecart/pusher.lua b/minecart/pusher.lua new file mode 100644 index 0000000..bc49ad5 --- /dev/null +++ b/minecart/pusher.lua @@ -0,0 +1,67 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = minecart.S +local CYCLE_TIME = 4 + +local function node_timer(pos) + local node = minetest.get_node(pos) + local dir = minetest.facedir_to_dir(node.param2) + minecart.punch_cart({x = pos.x, y = pos.y + 1, z = pos.z}, nil, 1, dir) + return true +end + + +local function after_dig_node(pos, oldnode, oldmetadata, digger) + techage.remove_node(pos, oldnode, oldmetadata) +end + +minetest.register_node("minecart:cart_pusher", { + description = S("Cart Pusher"), + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-8/16,-8/16,-8/16, 8/16, 8/16, 8/16}, + {-1/16, 8/16,-4/16, 1/16, 10/16, 4/16}, + }, + }, + tiles = { + -- up, down, right, left, back, front + "default_steel_block.png^minecart_pusher_top.png", + "default_steel_block.png", + "default_steel_block.png^minecart_pusher.png", + "default_steel_block.png^minecart_pusher.png", + "default_steel_block.png^minecart_pusher.png", + "default_steel_block.png^minecart_pusher.png", + }, + after_place_node = function(pos) + minetest.get_node_timer(pos):start(CYCLE_TIME) + end, + + on_timer = node_timer, + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = true, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_craft({ + output = "minecart:cart_pusher", + recipe = { + {"dye:black", "default:steel_ingot", "dye:yellow"}, + {"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"}, + {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, + }, +}) diff --git a/minecart/rails.lua b/minecart/rails.lua new file mode 100644 index 0000000..b7b5622 --- /dev/null +++ b/minecart/rails.lua @@ -0,0 +1,548 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local P2H = minetest.hash_node_position + +local get_node_lvm = minecart.get_node_lvm + +local MAX_SPEED = 8 +local SLOWDOWN = 0.3 +local MAX_NODES = 100 + +--waypoint = { +-- dot = travel direction, +-- pos = destination pos, +-- speed = 10 times the section speed (as int), +-- limit = 10 times the speed limit (as int), +--} +-- +-- waypoints = {facedir = waypoint,...} + +local tWaypoints = {} -- {pos_hash = waypoints, ...} + +local tRailsPower = { + ["carts:rail"] = 0, + ["carts:powerrail"] = 1, + ["minecart:rail"] = 0, + ["minecart:powerrail"] = 1, + ["carts:brakerail"] = 0, +} +-- Real rails from the mod carts +local tRails = { + ["carts:rail"] = true, + ["carts:powerrail"] = true, + ["carts:brakerail"] = true, + ["minecart:rail"] = true, + ["minecart:powerrail"] = true, +} +-- Rails plus node carts. Used to find waypoints. Added via add_raillike_nodes +local tRailsExt = { + ["carts:rail"] = true, + ["carts:powerrail"] = true, + ["carts:brakerail"] = true, + ["minecart:rail"] = true, + ["minecart:powerrail"] = true, +} + +local tSigns = { + ["minecart:speed1"] = 1, + ["minecart:speed2"] = 2, + ["minecart:speed4"] = 4, + ["minecart:speed8"] = 8, +} + +-- Real rails from the mod carts +local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"} +-- Rails plus node carts used to find waypoints, , added via add_raillike_nodes +local lRailsExt = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"} + +minecart.MAX_SPEED = MAX_SPEED +minecart.lRails = lRails +minecart.tRails = tRails +minecart.tRailsExt = tRailsExt +minecart.lRailsExt = lRailsExt + +local Dot2Dir = {} +local Dir2Dot = {} +local Facedir2Dir = {[0] = + {x= 0, y=0, z= 1}, + {x= 1, y=0, z= 0}, + {x= 0, y=0, z=-1}, + {x=-1, y=0, z= 0}, + {x= 0, y=-1, z= 0}, + {x= 0, y=1, z= 0}, +} + +local flip = { + [0] = 2, + [1] = 3, + [2] = 0, + [3] = 1, + [4] = 5, + [5] = 4, +} + +-- facedir = math.floor(dot / 4) +-- y = (dot % 4) - 1 + +-- Create helper tables +for facedir = 0,3 do + for y = -1,1 do + local dot = 1 + facedir * 4 + y + local dir = vector.new(Facedir2Dir[facedir]) + dir.y = y + Dot2Dir[dot] = dir + Dir2Dot[P2H(dir)] = dot + end +end + +local function dot2dir(dot) return vector.new(Dot2Dir[dot]) end +local function facedir2dir(fd) return vector.new(Facedir2Dir[fd]) end + +minecart.dot2dir = dot2dir +minecart.facedir2dir = facedir2dir + +------------------------------------------------------------------------------- +-- waypoint metadata +------------------------------------------------------------------------------- +local function has_metadata(pos) + return M(pos):contains("waypoints") +end + +local function get_metadata(pos) + local hash = P2H(pos) + if tWaypoints[hash] then + return tWaypoints[hash] + end + local s = M(pos):get_string("waypoints") + if s ~= "" then + tWaypoints[hash] = minetest.deserialize(s) + return tWaypoints[hash] + end +end + +local function get_oldmetadata(meta) + local s = meta:get_string("waypoints") + if s ~= "" then + return minetest.deserialize(s) + end +end + +local function set_metadata(pos, t) + local hash = P2H(pos) + tWaypoints[hash] = t + local s = minetest.serialize(t) + M(pos):set_string("waypoints", s) + -- visualization + local name = get_node_lvm(pos).name + if name == "carts:rail" then + minetest.swap_node(pos, {name = "minecart:rail"}) + elseif name == "carts:powerrail" then + minetest.swap_node(pos, {name = "minecart:powerrail"}) + end +end + +local function del_metadata(pos) + local hash = P2H(pos) + tWaypoints[hash] = nil + local meta = M(pos) + if meta:contains("waypoints") then + meta:set_string("waypoints", "") + -- visualization + local name = get_node_lvm(pos).name + if name == "minecart:rail" then + minetest.swap_node(pos, {name = "carts:rail"}) + elseif name == "minecart:powerrail" then + minetest.swap_node(pos, {name = "carts:powerrail"}) + end + end +end + +------------------------------------------------------------------------------- +-- find_next_waypoint +------------------------------------------------------------------------------- +local function check_right(pos, facedir) + local fdr = (facedir + 1) % 4 -- right + local new_pos = vector.add(pos, facedir2dir(fdr)) + + local name = get_node_lvm(new_pos).name + if tRailsExt[name] or tSigns[name] then + return true + end + new_pos.y = new_pos.y - 1 + if tRailsExt[get_node_lvm(new_pos).name] then + return true + end +end + +local function check_left(pos, facedir) + local fdl = (facedir + 3) % 4 -- left + local new_pos = vector.add(pos, facedir2dir(fdl)) + + local name = get_node_lvm(new_pos).name + if tRailsExt[name] or tSigns[name] then + return true + end + new_pos.y = new_pos.y - 1 + if tRailsExt[get_node_lvm(new_pos).name] then + return true + end +end + +local function get_next_pos(pos, facedir, y) + local new_pos = vector.add(pos, facedir2dir(facedir)) + new_pos.y = new_pos.y + y + local name = get_node_lvm(new_pos).name + return tRailsExt[name] ~= nil, new_pos, tRailsPower[name] or 0 +end + +local function is_ramp(pos) + return tRailsExt[get_node_lvm({x = pos.x, y = pos.y + 1, z = pos.z}).name] ~= nil +end + +-- Check also the next position to detect a ramp +local function slope_detection(pos, facedir) + local is_rail, new_pos = get_next_pos(pos, facedir, 0) + if not is_rail then + return is_ramp(new_pos) + end +end + +local function find_next_waypoint(pos, facedir, y) + local cnt = 0 + local name = get_node_lvm(pos).name + local speed = tRailsPower[name] or 0 + local is_rail, new_pos, _speed + + while cnt < MAX_NODES do + is_rail, new_pos, _speed = get_next_pos(pos, facedir, y) + speed = speed + _speed + if not is_rail then + return pos, y == 0 and is_ramp(new_pos), speed + end + if y == 0 then -- no slope + if check_right(new_pos, facedir) then + return new_pos, slope_detection(new_pos, facedir), speed + elseif check_left(new_pos, facedir) then + return new_pos, slope_detection(new_pos, facedir), speed + end + end + pos = new_pos + cnt = cnt + 1 + end + return new_pos, false, speed +end + +------------------------------------------------------------------------------- +-- find_all_next_waypoints +------------------------------------------------------------------------------- +local function check_front_up_down(pos, facedir) + local new_pos = vector.add(pos, facedir2dir(facedir)) + + if tRailsExt[get_node_lvm(new_pos).name] then + return 0 + end + new_pos.y = new_pos.y - 1 + if tRailsExt[get_node_lvm(new_pos).name] then + return -1 + end + new_pos.y = new_pos.y + 2 + if tRailsExt[get_node_lvm(new_pos).name] then + return 1 + end +end + +-- Search for rails in all 4 directions +local function find_all_rails_nearby(pos) + --print("find_all_rails_nearby") + local tbl = {} + for fd = 0, 3 do + tbl[#tbl + 1] = check_front_up_down(pos, fd, true) + end + return tbl +end + +-- Recalc the value based on waypoint length and slope +local function recalc_speed(num_pow_rails, pos1, pos2, y) + local num_norm_rails = vector.distance(pos1, pos2) - num_pow_rails + local ratio, speed + + if y ~= 0 then + num_norm_rails = math.floor(num_norm_rails / 1.41 + 0.5) + end + + if y ~= -1 then + if num_pow_rails == 0 then + return num_norm_rails * -SLOWDOWN + else + ratio = math.floor(num_norm_rails / num_pow_rails) + ratio = minecart.range(ratio, 0, 11) + end + else + ratio = 3 + num_norm_rails * SLOWDOWN + num_pow_rails + end + + if y == 1 then + speed = 7 - ratio + elseif y == -1 then + speed = 15 - ratio + else + speed = 11 - ratio + end + + return minecart.range(speed, 0, 8) +end + +local function find_all_next_waypoints(pos) + local wp = {} + local dots = {} + + for facedir = 0,3 do + local y = check_front_up_down(pos, facedir) + if y then + local new_pos, is_ramp, speed = find_next_waypoint(pos, facedir, y) + --print("find_all_next_waypoints", P2S(new_pos), is_ramp, speed) + local dot = 1 + facedir * 4 + y + speed = recalc_speed(speed, pos, new_pos, y) * 10 + wp[facedir] = {dot = dot, pos = new_pos, speed = speed, is_ramp = is_ramp} + end + end + + return wp +end + +------------------------------------------------------------------------------- +-- get_waypoint +------------------------------------------------------------------------------- +-- If ramp, stop 0.5 nodes earlier or later +local function ramp_correction(pos, wp, facedir) + if wp.is_ramp or pos.y < wp.pos.y then -- ramp detection + local dir = facedir2dir(facedir) + local pos = wp.pos + + wp.cart_pos = { + x = pos.x - dir.x / 2, + y = pos.y, + z = pos.z - dir.z / 2} + elseif pos.y > wp.pos.y then + local dir = facedir2dir(facedir) + local pos = wp.pos + + wp.cart_pos = { + x = pos.x + dir.x / 2, + y = pos.y, + z = pos.z + dir.z / 2} + end + return wp +end + +-- Returns waypoint and is_junction +function minecart.get_waypoint(pos, facedir, ctrl, uturn) + local t = get_metadata(pos) + if not t then + t = find_all_next_waypoints(pos) + set_metadata(pos, t) + end + + local left = (facedir + 3) % 4 + local right = (facedir + 1) % 4 + local back = (facedir + 2) % 4 + + if ctrl.right and t[right] then return t[right], t[facedir] ~= nil or t[left] ~= nil end + if ctrl.left and t[left] then return t[left] , t[facedir] ~= nil or t[right] ~= nil end + + if t[facedir] then return ramp_correction(pos, t[facedir], facedir), false end + if t[right] then return ramp_correction(pos, t[right], right), false end + if t[left] then return ramp_correction(pos, t[left], left), false end + + if uturn and t[back] then return t[back], false end +end + +------------------------------------------------------------------------------- +-- delete waypoints +------------------------------------------------------------------------------- +local function delete_counterpart_metadata(pos, wp) + for facedir = 0,3 do + if wp[facedir] then + del_metadata(wp[facedir].pos) + end + end + del_metadata(pos) +end + +local function delete_next_metadata(pos, facedir, y) + local cnt = 0 + while cnt <= MAX_NODES do + local is_rail, new_pos = get_next_pos(pos, facedir, y) + if not is_rail then + return + end + + if has_metadata(new_pos) then + del_metadata(new_pos) + end + + pos = new_pos + cnt = cnt + 1 + end + if has_metadata(pos) then + del_metadata(pos) + end +end + +function minecart.delete_waypoint(pos) + if has_metadata(pos) then + local wp = get_metadata(pos) + delete_counterpart_metadata(pos, wp) + return + end + + for facedir = 0,3 do + local y = check_front_up_down(pos, facedir) + if y then + local new_pos = vector.add(pos, facedir2dir(facedir)) + new_pos.y = new_pos.y + y + if has_metadata(new_pos) then + local wp = get_metadata(new_pos) + delete_counterpart_metadata(new_pos, wp) + else + delete_next_metadata(pos, facedir, y) + end + end + end +end + +carts:register_rail("minecart:rail", { + description = "Rail", + tiles = { + "carts_rail_straight.png^minecart_waypoint.png", "carts_rail_curved.png^minecart_waypoint.png", + "carts_rail_t_junction.png^minecart_waypoint.png", "carts_rail_crossing.png^minecart_waypoint.png" + }, + inventory_image = "carts_rail_straight.png", + wield_image = "carts_rail_straight.png", + groups = carts:get_rail_groups({not_in_creative_inventory = 1}), + drop = "carts:rail", +}, {}) + +carts:register_rail("minecart:powerrail", { + description = "Powered Rail", + tiles = { + "carts_rail_straight_pwr.png^minecart_waypoint.png", "carts_rail_curved_pwr.png^minecart_waypoint.png", + "carts_rail_t_junction_pwr.png^minecart_waypoint.png", "carts_rail_crossing_pwr.png^minecart_waypoint.png" + }, + inventory_image = "carts_rail_straight.png", + wield_image = "carts_rail_straight.png", + groups = carts:get_rail_groups({not_in_creative_inventory = 1}), + drop = "carts:powerrail", +}, {}) + +for name,_ in pairs(tRails) do + minetest.override_item(name, { + after_destruct = minecart.delete_waypoint, + after_place_node = minecart.delete_waypoint, + }) +end + +------------------------------------------------------------------------------- +-- API functions +------------------------------------------------------------------------------- +-- Return new cart pos and if an extra move cycle is needed +function minecart.get_current_cart_pos_correction(curr_pos, curr_fd, curr_y, new_dot) + if new_dot then + local new_y = (new_dot % 4) - 1 + local new_fd = math.floor(new_dot / 4) + + if curr_y == -1 or new_y == -1 then + local new_fd = math.floor(new_dot / 4) + local dir = facedir2dir(new_fd) + return { + x = curr_pos.x + dir.x / 2, + y = curr_pos.y, + z = curr_pos.z + dir.z / 2}, new_y == -1 + elseif curr_y == 1 and curr_fd ~= new_fd then + local dir = facedir2dir(new_fd) + return { + x = curr_pos.x + dir.x / 2, + y = curr_pos.y, + z = curr_pos.z + dir.z / 2}, true + elseif curr_y == 1 or new_y == 1 then + local dir = facedir2dir(curr_fd) + return { + x = curr_pos.x - dir.x / 2, + y = curr_pos.y, + z = curr_pos.z - dir.z / 2}, false + end + end + return curr_pos, false +end + +-- Called by carts, returns the speed value or nil +function minecart.get_speedlimit(pos, facedir) + local fd = (facedir + 1) % 4 -- right + local new_pos = vector.add(pos, facedir2dir(fd)) + local node = get_node_lvm(new_pos) + if tSigns[node.name] and node.param2 == facedir then + return tSigns[node.name] + end + + fd = (facedir + 3) % 4 -- left + new_pos = vector.add(pos, facedir2dir(fd)) + node = get_node_lvm(new_pos) + if tSigns[node.name] and node.param2 == facedir then + return tSigns[node.name] + end +end + +-- Called by carts, to delete temporarily created waypoints +function minecart.delete_cart_waypoint(pos) + del_metadata(pos) +end + +-- Called by signs, to delete the rail waypoints nearby +function minecart.delete_signs_waypoint(pos) + local node = minetest.get_node(pos) + local facedir = (node.param2 + 1) % 4 -- right + local new_pos = vector.add(pos, facedir2dir(facedir)) + if tRailsExt[get_node_lvm(new_pos).name] then + minecart.delete_waypoint(new_pos) + end + + facedir = (node.param2 + 3) % 4 -- left + new_pos = vector.add(pos, facedir2dir(facedir)) + if tRailsExt[get_node_lvm(new_pos).name] then + minecart.delete_waypoint(new_pos) + end +end + +function minecart.is_rail(pos) + return tRails[get_node_lvm(pos).name] ~= nil +end + +-- To register node cart names +function minecart.add_raillike_nodes(name) + tRailsExt[name] = true + lRailsExt[#lRailsExt + 1] = name +end + +--minetest.register_lbm({ +-- label = "Delete waypoints", +-- name = "minecart:del_meta", +-- nodenames = {"carts:brakerail"}, +-- run_at_every_load = true, +-- action = function(pos, node) +-- del_metadata(pos) +-- end, +--}) + diff --git a/minecart/recording.lua b/minecart/recording.lua index a5d5996..d516a47 100644 --- a/minecart/recording.lua +++ b/minecart/recording.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -14,76 +14,172 @@ local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib3.lua") -local CartsOnRail = minecart.CartsOnRail -- from storage.lua -local get_route = minecart.get_route -- from storage.lua +local function dashboard_destroy(self) + if self.driver and self.hud_id then + local player = minetest.get_player_by_name(self.driver) + if player then + player:hud_remove(self.hud_id) + self.hud_id = nil + end + end +end + +local function dashboard_create(self) + if self.driver then + local player = minetest.get_player_by_name(self.driver) + if player then + dashboard_destroy(self) + self.hud_id = player:hud_add({ + name = "minecart", + hud_elem_type = "text", + position = {x = 0.4, y = 0.25}, + scale = {x=100, y=100}, + text = "Recording:", + number = 0xFFFFFF, + size = {x = 1}, + }) + end + end +end + +local function dashboard_update(self) + if self.driver and self.hud_id then + local player = minetest.get_player_by_name(self.driver) + if player then + local time = self.runtime or 0 + local dir = (self.ctrl and self.ctrl.left and S("left")) or + (self.ctrl and self.ctrl.right and S("right")) or S("straight") + local speed = math.floor((self.curr_speed or 0) + 0.5) + local s = string.format(S("Recording") .. + " | " .. S("speed") .. + ": %.1f | " .. S("next junction") .. + ": %-8s | " .. S("Travel time") .. ": %.1f s", + speed, dir, time) + player:hud_change(self.hud_id, "text", s) + end + end +end + +local function check_waypoint(self, pos) + -- If next waypoint already reached but not handled + -- determine next waypoint + if vector.equals(pos, self.waypoint.pos) then + local rot = self.object:get_rotation() + local dir = minetest.yaw_to_dir(rot.y) + dir.y = math.floor((rot.x / (math.pi/4)) + 0.5) + local facedir = minetest.dir_to_facedir(dir) + local waypoint = minecart.get_waypoint(pos, facedir, self.ctrl or {}, false) + if waypoint then + return waypoint.pos + end + end + return self.waypoint.pos +end -- -- Route recording -- function minecart.start_recording(self, pos) - self.start_key = lib.get_route_key(pos, self.driver) - if self.start_key then - self.waypoints = {} - self.junctions = {} - self.recording = true - self.next_time = minetest.get_us_time() + 1000000 - minetest.chat_send_player(self.driver, S("[minecart] Start route recording!")) - end -end - -function minecart.store_next_waypoint(self, pos, vel) - if self.start_key and self.recording and self.driver and - self.next_time < minetest.get_us_time() then - self.next_time = minetest.get_us_time() + 1000000 - self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))} - elseif self.recording and not self.driver then - self.recording = false - self.waypoints = nil - self.junctions = nil - end -end - --- destination reached(speed == 0) -function minecart.stop_recording(self, pos, vel, puncher) - local dest_pos = lib.get_route_key(pos, self.driver) - if dest_pos then - if self.start_key and self.start_key ~= dest_pos then - local route = { - waypoints = self.waypoints, - dest_pos = dest_pos, - junctions = self.junctions, - } - minecart.store_route(self.start_key, route) - minetest.chat_send_player(self.driver, S("[minecart] Route stored!")) - else - minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!")) + --print("start_recording") + if self.driver then + self.start_pos = minecart.get_buffer_pos(pos, self.driver) + if self.start_pos then + self.checkpoints = {} -- {cart_pos, next_waypoint_pos, speed, dot} + self.junctions = {} + self.is_recording = true + self.rec_time = self.timebase + self.hud_time = self.timebase + self.runtime = 0 + self.num_sections = 0 + self.sum_speed = 0 + self.ctrl = {} + dashboard_create(self) + dashboard_update(self, 0) end - else - minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!")) end - self.recording = false +end + +function minecart.stop_recording(self, pos) + --print("stop_recording") + if self.driver and self.is_recording then + local dest_pos = minecart.get_buffer_pos(pos, self.driver) + local player = minetest.get_player_by_name(self.driver) + if dest_pos and player and #self.checkpoints > 3 then + -- Remove last checkpoint, because it is potentially too close to the dest_pos + table.remove(self.checkpoints) + if self.start_pos then + local route = { + dest_pos = dest_pos, + checkpoints = self.checkpoints, + junctions = self.junctions, + } + minecart.store_route(self.start_pos, route) + minetest.chat_send_player(self.driver, S("[minecart] Route stored!")) + local speed = self.sum_speed / #self.checkpoints + local length = speed * self.runtime + local fmt = S("[minecart] Speed = %u m/s, Time = %u s, Route length = %u m") + minetest.chat_send_player(self.driver, string.format(fmt, speed, self.runtime, length)) + end + end + dashboard_destroy(self) + end + self.is_recording = false + self.checkpoints = nil self.waypoints = nil self.junctions = nil end -function minecart.set_junction(self, pos, dir, switch_keys) - if self.junctions then - self.junctions[P2S(vector.round(pos))] = {dir, switch_keys} - end +function minecart.recording_waypoints(self) + local pos = vector.round(self.object:get_pos()) + -- hier müsste überprüfung dest_pos rein + self.sum_speed = self.sum_speed + self.curr_speed + local wp_pos = check_waypoint(self, pos) + self.checkpoints[#self.checkpoints+1] = { + -- cart_pos, next_waypoint_pos, speed, dot + P2H(pos), + P2H(wp_pos), + math.floor(self.curr_speed + 0.5), + self.waypoint.dot + } end -function minecart.get_junction(self, pos, dir) - local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions - if junctions then - local data = junctions[P2S(vector.round(pos))] - if data then - return data[1], data[2] +function minecart.recording_junctions(self) + local player = minetest.get_player_by_name(self.driver) + if player then + local ctrl = player:get_player_control() + if ctrl.left then + self.ctrl = {left = true} + elseif ctrl.right then + self.ctrl = {right = true} + elseif ctrl.up or ctrl.down then + self.ctrl = nil end end - return dir + if self.hud_time <= self.timebase then + dashboard_update(self) + self.hud_time = self.timebase + 0.5 + self.runtime = self.runtime + 0.5 + end end +function minecart.set_junctions(self, wayp_pos) + if self.ctrl then + self.junctions[P2H(wayp_pos)] = self.ctrl + end +end + +function minecart.player_ctrl(self) + local player = minetest.get_player_by_name(self.driver) + if player then + local ctrl = player:get_player_control() + if ctrl.left then + self.ctrl = {left = true} + elseif ctrl.right then + self.ctrl = {right = true} + end + end +end diff --git a/minecart/signs.lua b/minecart/signs.lua new file mode 100644 index 0000000..266e161 --- /dev/null +++ b/minecart/signs.lua @@ -0,0 +1,107 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg 4 + + MIT + See license.txt for more information + +]]-- + +local S = minecart.S + + +local function register_sign(def) + minetest.register_node("minecart:"..def.name, { + description = def.description, + inventory_image = def.image, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -1/16, -8/16, -1/16, 1/16,-2/16, 1/16}, + { -5/16, -2/16, -1/32, 5/16, 8/16, 1/32}, + }, + }, + paramtype2 = "facedir", + tiles = { + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png", + "default_steel_block.png^"..def.image, + }, + + after_place_node = minecart.delete_signs_waypoint, + preserve_metadata = minecart.delete_signs_waypoint, + + on_rotate = screwdriver.disallow, + paramtype = "light", + use_texture_alpha = minecart.CLIP, + sunlight_propagates = true, + is_ground_content = false, + groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, minecart_sign = 1}, + sounds = default.node_sound_wood_defaults(), + }) +end + +register_sign({ + name = "speed1", + description = S('Speed "1"'), + image = "minecart_sign1.png", +}) + +register_sign({ + name = "speed2", + description = S('Speed "2"'), + image = "minecart_sign2.png", +}) + +register_sign({ + name = "speed4", + description = S('Speed "4"'), + image = "minecart_sign4.png", +}) + +register_sign({ + name = "speed8", + description = S('No speed limit'), + image = "minecart_sign8.png", +}) + +minetest.register_craft({ + output = "minecart:speed8 8", + recipe = { + {"default:tin_ingot", "dye:red", "default:tin_ingot"}, + {"", "default:steel_ingot", ""}, + {"", "default:steel_ingot", ""} + } +}) + +minetest.register_craft({ + type = "shapeless", + output = "minecart:speed4", + recipe = {"minecart:speed8"} +}) + +minetest.register_craft({ + type = "shapeless", + output = "minecart:speed2", + recipe = {"minecart:speed4"} +}) + +minetest.register_craft({ + type = "shapeless", + output = "minecart:speed1", + recipe = {"minecart:speed2"} +}) + +minetest.register_craft({ + type = "shapeless", + output = "minecart:speed8", + recipe = {"minecart:speed1"} +}) + diff --git a/minecart/storage.lua b/minecart/storage.lua index 34d3f02..04e471f 100644 --- a/minecart/storage.lua +++ b/minecart/storage.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -14,24 +14,75 @@ local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S local storage = minetest.get_mod_storage() +local function place_carts(t) + local Carts = { + ["minecart:cart"] = "minecart:cart", + ["techage:tank_cart_entity"] = "techage:tank_cart", + ["techage:chest_cart_entity"] = "techage:chest_cart", + } + for id, item in pairs(t) do + local pos = vector.round((item.start_pos or item.last_pos)) + local name = Carts[item.entity_name] or "minecart:cart" + --print(P2S(pos), name, item.owner, item.userID) + if minetest.registered_nodes[name] then + minecart.add_nodecart(pos, name, 0, {}, item.owner or "", item.userID or 0) + end + end +end + ------------------------------------------------------------------------------- -- Store data of running carts ------------------------------------------------------------------------------- minecart.CartsOnRail = {} minetest.register_on_mods_loaded(function() - for key,val in pairs(minetest.deserialize(storage:get_string("CartsOnRail")) or {}) do - -- use invalid keys to force the cart spawning - minecart.CartsOnRail[-key] = val + local version = storage:get_int("version") + if version < 2 then + local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {} + minetest.after(5, place_carts, t) + storage:set_int("version", 2) + else + local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {} + for owner, carts in pairs(t) do + minecart.CartsOnRail[owner] = {} + for userID, cart in pairs(carts) do + print("reload cart", owner, userID, cart.objID) + minecart.CartsOnRail[owner][userID] = cart + -- mark all entity carts as zombified + if cart.objID and cart.objID ~= 0 then + cart.objID = -1 + minecart.push(1, cart) + end + end + end + end +end) + +minetest.after(10, function() + for owner, carts in pairs(minecart.CartsOnRail) do + for userID, cart in pairs(carts) do + -- Remove node carts that are not available anymore + if cart.objID == 0 or not cart.objID then + local node = minecart.get_node_lvm(cart.pos) + if not minecart.tNodeNames[node.name] then + -- Mark as "to be deleted" + print("Node cart deleted", owner, userID) + minecart.CartsOnRail[owner][userID] = nil + end + end + end end end) minetest.register_on_shutdown(function() storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail)) + print("minecart shutdown finished!!!") end) function minecart.store_carts() @@ -39,69 +90,31 @@ function minecart.store_carts() end ------------------------------------------------------------------------------- --- Store routes +-- Store routes (in buffers) ------------------------------------------------------------------------------- --- All positions as "pos_to_string" string ---Routes = { --- start_pos = { --- waypoints = {{spos, svel}, {spos, svel}, ...}, --- dest_pos = spos, --- junctions = { --- {spos = num}, --- {spos = num}, --- }, --- }, --- start_pos = {...}, ---} -local Routes = {} -local NEW_ROUTE = {waypoints = {}, junctions = {}} - -function minecart.store_route(key, route) - if key and route then - Routes[key] = route - local meta = M(S2P(key)) - if meta then - meta:set_string("route", minetest.serialize(route)) - return true - end +function minecart.store_route(pos, route) + if pos and route then + M(pos):set_string("route", minetest.serialize(route)) + return true end return false end -function minecart.get_route(key) - if not Routes[key] then - local s = M(S2P(key)):get_string("route") +function minecart.get_route(pos) + if pos then + local s = M(pos):get_string("route") if s ~= "" then - Routes[key] = minetest.deserialize(s) or NEW_ROUTE - else - Routes[key] = NEW_ROUTE - end - end - return Routes[key] -end - -function minecart.del_route(key) - Routes[key] = nil -- remove from memory - M(S2P(key)):set_string("route", "") -- and as metadata -end - -------------------------------------------------------------------------------- --- Convert data to v2 -------------------------------------------------------------------------------- -minetest.after(5, function() - local tbl = storage:to_table() - for key,s in pairs(tbl.fields) do - if key ~= "CartsOnRail" then local route = minetest.deserialize(s) - if route.waypoints and route.junctions then - if minecart.store_route(key, route) then - storage:set_string(key, "") - end - else - storage:set_string(key, "") + if route.waypoints then + M(pos):set_string("route", "") + M(pos):set_int("time", 0) + return end + return minetest.deserialize(s) end end -end) - +end +function minecart.del_route(pos) + M(pos):set_string("route", "") +end diff --git a/minecart/terminal.lua b/minecart/terminal.lua new file mode 100644 index 0000000..60420de --- /dev/null +++ b/minecart/terminal.lua @@ -0,0 +1,90 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = minecart.S + +local function is_player_nearby(pos) + for _, object in pairs(minetest.get_objects_inside_radius(pos, 6)) do + if object:is_player() then + return true + end + end +end + +local function formspec(pos, text) + text = minetest.formspec_escape(text) + text = text:gsub("\n", ",") + + return "size[11,9]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "box[0,-0.1;10.8,0.5;#c6e8ff]".. + "label[4.5,-0.1;"..minetest.colorize( "#000000", S("Cart List")).."]".. + "style_type[table,field;font=mono]".. + "table[0,0.5;10.8,8.6;output;"..text..";200]" +end + +minetest.register_node("minecart:terminal", { + description = S("Cart Terminal"), + inventory_image = "minecart_terminal_front.png", + tiles = { + "minecart_terminal_top.png", + "minecart_terminal_top.png", + "minecart_terminal_side.png", + "minecart_terminal_side.png", + "minecart_terminal_back.png", + "minecart_terminal_front.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -8/16, -8/16, 0/16, 8/16, 8/16, 8/16}, + }, + }, + + after_place_node = function(pos, placer) + local meta = M(pos) + meta:set_string("owner", placer:get_player_name()) + meta:set_string("formspec", formspec(pos, "")) + minetest.get_node_timer(pos):start(2) + end, + + on_timer = function(pos, elapsed) + if is_player_nearby(pos) then + local text = minecart.get_cart_list(pos, M(pos):get_string("owner")) + M(pos):set_string("formspec", formspec(pos, text)) + end + return true + end, + + paramtype2 = "facedir", + paramtype = "light", + use_texture_alpha = minecart.CLIP, + on_rotate = screwdriver.disallow, + sunlight_propagates = true, + is_ground_content = false, + groups = {cracky = 2, level = 2}, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_craft({ + output = "minecart:terminal", + recipe = { + {"", "default:obsidian_glass", "default:steel_ingot"}, + {"", "default:obsidian_glass", "default:copper_ingot"}, + {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, + }, +}) diff --git a/minecart/textures/minecart_appl_cart_top.png b/minecart/textures/minecart_appl_cart_top.png new file mode 100644 index 0000000000000000000000000000000000000000..9a11a0b7bf50f6e6b3b57b5e9e275fb708de2ac1 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;*aCb)T-^(NlvI=&vbW6ziZGT0 z`2{mLJiCzwIEHXsPfke4NdSVP1c^Be3}qh}j=0oTPXNj@c)I$ztaD0e0ss$* B8~^|S literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_appl_hopper.png b/minecart/textures/minecart_appl_hopper.png index 7c703e21e06e1906b9037f3311a9058ed6eabeb6..7eaf8a28e88462a1505b5204880f96e26d3237f2 100644 GIT binary patch delta 25 gcmbQw*u^-(k)JKVC&bmgz=wf>VZK&N;KWob09j@S7ytkO delta 46 ycmeBToXBeIx*fm;}a85w5Hkzin8U<>dGarMb81TtsEER>xXUry diff --git a/minecart/textures/minecart_appl_hopper_right.png b/minecart/textures/minecart_appl_hopper_right.png index 5e8c302a9bcb989a390aef0e4d1fde5761ee72ad..48e10370fc8ad25c94742069834f9f365d769009 100644 GIT binary patch delta 27 fcmZ3n4+9v8WphrRnBfQjVs{3z delta 48 ycmbQjxRP;#lUjjCWHAE+w=f7ZGR&GI!N9=48Q>G*>XTUrWCDR*UFDsL!Hxj`?h4@m diff --git a/minecart/textures/minecart_appl_hopper_top.png b/minecart/textures/minecart_appl_hopper_top.png index 97ce3c2aaba5ce873f4559e7959790461ec5c07b..4c2fb4d8b89a4c157aeb918b3e3b74ce4073aad6 100644 GIT binary patch delta 27 jcmX@jxQ%gwlVE^Ph^u>nkCKX#wVm~@*4^JHW+Vdue%A^J delta 48 zcmdnSc$#s7lUjjCWHAE+w=f7ZGR&GI!N9=48Q>G*>XTWhq@rYPXFX}c=^Yb;lK~r% B4EF#4 diff --git a/minecart/textures/minecart_ballast.png b/minecart/textures/minecart_ballast.png index 9bca948a1e50a9a9721a1968897a7290b3dde20b..0c99602198458695932f8983785dc437bec61e86 100644 GIT binary patch delta 559 zcmV+~0?_^N7`z0K7=Hu<0001iRAS{4`` zVq;})Z*Y5idjN)rW&i*I*-1n}R4C64(7TqJKo|hve};=E-2jS7H{8^8Tog%%sEZ}A z5_Y>m4cltelTEiKcr@k4^(4M+@;$-_J7wQ`RQ8txmh!-Me81vfor_zhEcf6W&p7Q*(6fFj>le~^iq!V)bD!^6cP@jK^(tbw^{U-M^35b12+dmjUw@`adM#hA5d}`#=^{fcs$yk7 zSv#;Q@{mDjBHXljH{w7KJQY{Np|7ECIg%zYMIJ*58onUtg-G!M0gsRi!j7;{^Tg-? zrYMkdir?RHd-L}p3ju8U?=E%}glE4+Kg@aX*^%IT?;DD(gWh*T+7IQ+1Xa}0N xD;gB76DVlrdT50eEN+L=dJe9ja-&j7;2)D_Zmzwah1mcA002ovPDHLkV1gPD0j2-| literal 3184 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 WLj7C;I;d6v00003UJ$jH6CyxQ8@ zr>3US1O(*c;=8)Kii}GA00007bW%=J0PA@Pm*=k!C%Fm$003o4L_t(2&tqU92s%1C zXU^#8m^l;5po+ji0VY8O7}G@18Aytk7ee{WQRRgO3{2(a%SWL~Se7rZa$sPrswx+N z2r!nH1DOyKDzJRH08mR+IaCW!cew+Kq2kMdhN4=V%rJBS0C?g+9DegURR91007*qo IM6N<$g6O(c)c^nh delta 443 zcmV;s0Yv`Z0m%c97=H)`0001UdV2H#000JJOGiWi000000Qp0^e*gdg32;bRa{vGf z6951U69E94oEQKA00(qQO+^Re1QQbn22*OY<^TWzR!KxbR9M69m9c8WFc5~XjtVsd zFA<>-TCfHQ^hAjsqC-0589X%5K7^dd=mVrf7lB-`p^(ka6n}7`irYy>bu&2)9infs zeg5V9@9eVx&Yg=_guc4LQIY?+u3`eG(ggqA0dF7xkH>TNdx1Skr3^Ud3$x$f0RW)E z;7KW!%!Fu;Nsvkz#)K>pLQ?<$W6Y|^^Z87Ox;2nW88F7)wbm1NF=K3_wZ83ZB9$^6 zkH>k_G}mQWwtsO-X_aN!%}j_o007{eFS^Mw#-O#HaLyNftB(m;+TGG z_E&7%ZbcYlu#WA$J|Kjq_Bn0`%CZFeN&*0Y;c$5Aid^wu;THF6Gy16OheKlfG4@UR zmSu@swR<$itXcy~sf01M>53U+8>LioMB$H=(yCh&r8KlE@_c@946nlQez)6wo@xxE lD0+TbuZ0~$kH_Qjd;=@6q9iwb^W^{l002ovPDHLkV1itT!Epcp diff --git a/minecart/textures/minecart_cart.png b/minecart/textures/minecart_cart.png index 621b8d27c138114575cf7864ad394fa7f670052f..2d48b12bb19db4710604b3b9640b06666cfe76f8 100644 GIT binary patch delta 42 ycmbQs)WI~tO~Jju$I8lT^XAQAVPTUePkvu!yqJN3fw3gWFPOpM*^RV`)r$a5Fc4Y* delta 63 zcmeBRn#(l7&D1Be(8|hc^XAQAVPTUePu^*KTY-Urfw3gWFPOpM*^M+HhqJ&VvY3H^ QTNs2H8D`Cqn3%Q*0H>7`K>z>% diff --git a/minecart/textures/minecart_doc_image.png b/minecart/textures/minecart_doc_image.png index 209b412dda6f18832ac599564d370097ccfc5f9e..07e22db6e6c71d0654b0a4f2017de7f13945070e 100644 GIT binary patch literal 10813 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 Hu0mjf^FQiA literal 40385 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 delta 104 zcmX@f*v>S;EzT#i(8I-P`_`>tVPO*|Oem~AT*AP>z*rLG7tG-B>_!@pBbyl#QR1AR zo12;kl4D?SE=o--Nlj5G&n(GMaQE~LNYP7W2a0nRctjR6FmMZlFeAgPIT91ok^wBl B9|-^e diff --git a/minecart/textures/minecart_marker_cube.png b/minecart/textures/minecart_marker_cube.png new file mode 100644 index 0000000000000000000000000000000000000000..132145138709c484888462feae86a83a85fb4f08 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaMM1AIbUh3qG|7x=j4?=t-zdIl)SToU9L+{w_{@a=vmkn8E`;uyklo%fg_ zSA&57%YnZ~SaVqC3uP;BXMZ_)Imb4qcl+OkG{=6JAa3^Qh?2OD?S!q?Yr@Y4b6>u7 mCGE0~Osme16OGM#KAz&&>J+uCj?@P^j=|H_&t;ucLK6UT20xVm literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_protect.png b/minecart/textures/minecart_protect.png index def72c79d9871d6b70f78518b8b2a0b974af8836..6ca8308217b5951362fb64f0de870e8512eae0ff 100644 GIT binary patch delta 111 zcmeBTYG9lok-@^uz`*eK?xP?eB^2Nj;_6=Dqp7K>sHkXYXqaK5gTe~DWM4fJ$oJM delta 250 zcmZo*>|&ZAQP07~z`!8?`F|piVoUONcLCBs@Y8vBJ&@uo@Q5sCVBi)8VMc~ob0mO* z>?NMQuI!IlBm^{MYGO%hib8p2Nrr;Er*A-t zUMf3KJkQg`F~s8Z+ewC8hYbW+(u)sss`xng#x2})DF1tZ%nK#)qPb431%d`Q-$h95 zNW5D8+)FOz%zH*NEtT54%HhrqnoDh!<>3+nUghUr(c5H`k*RMP+e diff --git a/minecart/textures/minecart_pusher.png b/minecart/textures/minecart_pusher.png new file mode 100644 index 0000000000000000000000000000000000000000..12704ed6821d78e8b458bc31893f3583750c0586 GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|uI>dsK<4i^%ttILegIjF zB|(0{3=Yq3qyaf1o-U3d9M_XQn3>vUIvjA|Y?#f&V6Dvlb7krOqd<8EPgg&ebxsLQ E087mr>Hq)$ literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_pusher_top.png b/minecart/textures/minecart_pusher_top.png new file mode 100644 index 0000000000000000000000000000000000000000..f0c4179545aacf476a6cce04486be0e3a5559a08 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-uI>ds{=U9I-tRZe{#}cB zfGoz6AirP+hi5m^fE)!+7sn8e>&XcTB?&;l%OJE!ktg859R`ⅆ%$nObop9*hK9A Sbu|LjGI+ZBxvXnE##Uv(aQ>>*;?(9lAAq_UJYD@<);T3K0RVJCC;R{a literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_sign2.png b/minecart/textures/minecart_sign2.png new file mode 100644 index 0000000000000000000000000000000000000000..adf879d9be1658b5d2596c866f62d88f95509e7f GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$efSN8&+Ck%r3?%nIUlivyy zWGo5t3ubV5b|VeQvG;Uw43W5;oRGlF+Qh)drgBV6!68|vx5;Q(o1^6AWe2Z3NIX!o yw#9H-+Choc7kFEY4RVebC^9o&XJTWkGGOSFw(grh{rpd$Q4F50elF{r5}E)m11|9Z literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_sign4.png b/minecart/textures/minecart_sign4.png new file mode 100644 index 0000000000000000000000000000000000000000..f981cc703f6f8f21bd011816ac194ea021c1e0bf GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$efSN8&+Ck%r3?%nIUlivyy zWGo5t3ubV5b|VeQvGH_q43W5;oRGlF+Qh)drgBV6!6DtJ+3?VUo~Z`Mf-IPqrAe_V un~2P9G!U5Fm}lL_Eh(`>%)sD6Gb2N+ly#hW?JG8*ISihzelF{r5}E+#Ybd<{ literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_sign8.png b/minecart/textures/minecart_sign8.png new file mode 100644 index 0000000000000000000000000000000000000000..7190be371e04e424eadf2d971a8e28471b5d2df8 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$efSN8&+Ck%r3?%nIUlivyy zWGo5t3ubV5b|VeQ3HEex43W6J_uN9>0}edSfs-fsg->dbe)x@3-?CulripJnrk;}i zB2t_1V8br%3Pt0^7w6xN&^scKBXoNbgq|t@h8&s|k2ecgk literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_terminal_back.png b/minecart/textures/minecart_terminal_back.png new file mode 100644 index 0000000000000000000000000000000000000000..daeff713a382ef1c0236d6cc72809d1745fdcff8 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;SkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?smO1^9%xX5@zTPw3Wni!KF<2z$CXhHzX@PGD&K_5c5WUOOfhhF;%{?DISN Q+JMpwp00i_>zopr0MJJvqyPW_ literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_terminal_front.png b/minecart/textures/minecart_terminal_front.png new file mode 100644 index 0000000000000000000000000000000000000000..c6bf65894c16d593dfe4ab0111c058ef5e99cbe2 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvxd5LK*X!4=O`SGLS4Sr`B?Txp z(cgc=`qi$ku2GSZ2lwt_GM6g=iZ1bVaSYKofA-u)u4V%Pwg>XwyN>^2n6cNE7TRDV~mw)9cZakfLU_wTB*@3p+asGgmBHe0ollVOGa0mn%kt5mm` zt0lJ_=nJ$uw4q_rL~F-H*2Eu&Oun`}ZoaTQobP0l+XkK^oVF2 literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_terminal_side.png b/minecart/textures/minecart_terminal_side.png new file mode 100644 index 0000000000000000000000000000000000000000..61024047f1a9c2ec0bda0f358032f5f81d846e1b GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv{s5m4*Noh-{t4Z?ckiAyb!t;X zLzmOsLqMq@PZ!4!jq}L~4(vTm2?`90GgTI_urV3TY79IYc!NV_fl>yK;DLaJZ*o|~ z0t~|rEj@JP2#d;sH*emw#I|;A-rTuS@B)u;a(eO_trw5~9@J{!W7zOU<KtBbBuVFVdQ&MBb@08(iqkN^Mx literal 0 HcmV?d00001 diff --git a/minecart/textures/minecart_tool.png b/minecart/textures/minecart_tool.png new file mode 100644 index 0000000000000000000000000000000000000000..4fddeb2eb84ce948a8a225f8ad0b7a40bf178cce GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFdsx;i?)-!M`F@)oKa)JVrfh7ayF}9>BOboJT7>>A#*gpcwGkCiCxvX>> Convert" -for filename in os.listdir("./"): - if fnmatch.fnmatch(filename, "*.png"): - print(filename) - os.system("pngquant --skip-if-larger --quality=8-32 --output ./%s.new ./%s" % (filename, filename)) - -print "\n>>> Copy" -for filename in os.listdir("./"): - if fnmatch.fnmatch(filename, "*.new"): - print(filename) - os.remove("./" + filename[:-4]) - os.rename("./" + filename, "./" + filename[:-4]) - diff --git a/minecart/textures/shrink.sh b/minecart/textures/shrink.sh new file mode 100755 index 0000000..36036c5 --- /dev/null +++ b/minecart/textures/shrink.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force \ No newline at end of file diff --git a/minecart/tool.lua b/minecart/tool.lua new file mode 100644 index 0000000..c5b192c --- /dev/null +++ b/minecart/tool.lua @@ -0,0 +1,101 @@ +--[[ + + Minecart + ======== + + Copyright (C) 2019-2021 Joachim Stolberg + + MIT + See license.txt for more information + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = minecart.S +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position + +local sDir = {[0] = "north", "east", "south", "west"} + +local function DOTS(dots) + if dots then + return table.concat(dots, ", ") + else + return "" + end +end + +local old_pos + +local function test_get_route(pos, node, player) + local yaw = player:get_look_horizontal() + local dir = minetest.yaw_to_dir(yaw) + local facedir = minetest.dir_to_facedir(dir) + local route = minecart.get_waypoint(pos, facedir, {}) + if route then +-- print(dump(route)) + minecart.set_marker(route.pos, "pos", 0.3, 10) + if route.cart_pos then + minecart.set_marker(route.cart_pos, "cart", 0.3, 10) + end + + -- determine some kind of current y + old_pos = old_pos or pos + local curr_y = pos.y > old_pos.y and 1 or pos.y < old_pos.y and -1 or 0 + + local cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(pos, facedir, curr_y, route.dot) + minecart.set_marker(cart_pos, "curr", 0.3, 10) + old_pos = pos + print(string.format("Route: dist = %u, dot = %u, speed = %d, extra cycle = %s", + vector.distance(pos, route.pos), route.dot, route.speed or 0, extra_cycle)) + end +end + +local function test_get_connections(pos, node, player, ctrl) + local wp = minecart.get_waypoints(pos) + for i = 0,3 do + if wp[i] then + local dir = minecart.Dot2Dir[ wp[i].dot] + print(sDir[i], vector.distance(pos, wp[i].pos), dir.y) + end + end + print(dump(M(pos):to_table())) +end + +local function click_left(itemstack, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.under + local node = minetest.get_node(pos) + if node.name == "carts:rail" or node.name == "carts:powerrail" then + test_get_route(pos, node, placer) + end + end +end + +local function click_right(itemstack, placer, pointed_thing) + if pointed_thing.type == "node" then + local pos = pointed_thing.under + local node = minetest.get_node(pos) + if node.name == "carts:rail" or node.name == "carts:powerrail" then + test_get_connections(pos, node, placer) + elseif node.name == "minecart:buffer" then + local route = minecart.get_route(P2S(pos)) + print(dump(route)) + end + end +end + +minetest.register_node("minecart:tool", { + description = "Tool", + inventory_image = "minecart_tool.png", + wield_image = "minecart_tool.png", + liquids_pointable = true, + use_texture_alpha = true, + groups = {cracky=1, book=1}, + on_use = click_left, + on_place = click_right, + node_placement_prediction = "", + stack_max = 1, +}) diff --git a/signs_bot/README.md b/signs_bot/README.md index b7831c5..fec834c 100644 --- a/signs_bot/README.md +++ b/signs_bot/README.md @@ -96,6 +96,7 @@ For all Inventory commands applies: If the inventory stack specified by i pattern - save the blocks behind the shield (up to 5x3x3) as template copy - make a copy of "pattern". Size is e.g. 3x3 (see ingame help) punch_cart - Punch a rail cart to start it + print - Output chat message for debug purposes #### Flow control commands @@ -169,4 +170,7 @@ optional: farming redo, node_io, doc, techage, minecart - 2020-06-21 v1.03 * Interpreter bugfixes, node and crop sensors changed - 2020-10-01 v1.04 * Many improvements and bugfixes (Thanks to Thomas-S) - 2021-01-30 v1.05 * Many improvements and bugfixes +- 2021-03-14 v1.06 * Switch translation from intllib to minetest.translator +- 2021-04-24 v1.07 * Adapted to minecart v2.0 +- 2021-05-04 v1.08 * Add print command, improve error msg diff --git a/signs_bot/basis.lua b/signs_bot/basis.lua index c5c0bf9..7f9ef00 100644 --- a/signs_bot/basis.lua +++ b/signs_bot/basis.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -112,16 +109,33 @@ local function preassigned_slots(pos) return table.concat(tbl, "") end +local function status(mem) + if mem.error then + if type(mem.error) == "string" then + return mem.error + else + return dump(mem.error) + end + end + if mem.running then + return S("running") + end + if mem.charging then + return S("charging") + end + return S("stopped") +end + local function formspec(pos, mem) mem.running = mem.running or false - local cmnd = mem.running and "stop;"..I("Off") or "start;"..I("On") + local cmnd = mem.running and "stop;"..S("Off") or "start;"..S("On") local bot = not mem.running and "image[0.6,0;1,1;signs_bot_bot_inv.png]" or "" local current_capa = mem.capa or (signs_bot.MAX_CAPA * 0.9) - return "size[9,7.6]".. + return "size[9,8.2]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[2.1,0;"..I("Signs").."]label[5.3,0;"..I("Other items").."]".. + "label[2.1,0;"..S("Signs").."]label[5.3,0;"..S("Other items").."]".. "image[0.6,0;1,1;signs_bot_form_mask.png]".. bot.. preassigned_slots(pos).. @@ -132,24 +146,27 @@ local function formspec(pos, mem) "label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]".. "list[context;main;5,1;4,2;]".. "label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]".. - "button[0.2,1;1.5,1;config;"..I("Config").."]".. + "button[0.2,1;1.5,1;config;"..S("Config").."]".. "button[0.2,2;1.5,1;"..cmnd.."]".. - "list[current_player;main;0.5,3.8;8,4;]".. + "label[1,3.6;"..status(mem).."]".. + "list[current_player;main;0.5,4.4;8,4;]".. "listring[context;main]".. "listring[current_player;main]" end -local function formspec_cfg(pos, mem) - return "size[9,7.6]".. +local function formspec_cfg() + return "size[9,8.2]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[5.3,0;"..I("Preassign slots items").."]".. + "label[5.3,0;"..S("Preassign slots items").."]".. "label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]".. "list[context;filter;5,1;4,2;]".. "label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]".. - "button[0.2,1;1.5,1;back;"..I("Back").."]".. - "list[current_player;main;0.5,3.8;8,4;]" + "button[0.2,1;1.5,1;back;"..S("Back").."]".. + "list[current_player;main;0.5,4.4;8,4;]".. + "listring[context;filter]".. + "listring[current_player;main]" end local function get_capa(itemstack) @@ -166,7 +183,7 @@ local function set_capa(pos, oldnode, digger, capa) capa = techage.power.percent(signs_bot.MAX_CAPA, capa) capa = (math.floor((capa or 0) / 5)) * 5 meta:set_int("capa", capa) - local text = I("Robot Box ").." ("..capa.." %)" + local text = S("Robot Box").." ("..capa.." %)" meta:set_string("description", text) local inv = minetest.get_inventory({type="player", name=digger:get_player_name()}) local left_over = inv:add_item("main", node) @@ -179,7 +196,7 @@ function signs_bot.infotext(pos, state) local meta = M(pos) local number = meta:get_string("number") state = state or "" - meta:set_string("infotext", I("Robot Box ")..number..": "..state) + meta:set_string("infotext", S("Robot Box").." "..number..": "..state) end local function reset_robot(pos, mem) @@ -205,7 +222,7 @@ function signs_bot.start_robot(base_pos) mem.capa = nil end meta:set_string("formspec", formspec(base_pos, mem)) - signs_bot.infotext(base_pos, I("running")) + signs_bot.infotext(base_pos, S("running")) reset_robot(base_pos, mem) minetest.get_node_timer(base_pos):start(CYCLE_TIME) return true @@ -224,9 +241,9 @@ function signs_bot.stop_robot(base_pos, mem) mem.charging = false end if mem.power_available then - signs_bot.infotext(base_pos, I("charging")) + signs_bot.infotext(base_pos, S("charging")) else - signs_bot.infotext(base_pos, I("stopped")) + signs_bot.infotext(base_pos, S("stopped")) end meta:set_string("formspec", formspec(base_pos, mem)) signs_bot.remove_robot(mem) @@ -285,7 +302,7 @@ local function on_receive_fields(pos, formname, fields, player) if fields.update then meta:set_string("formspec", formspec(pos, mem)) elseif fields.config then - meta:set_string("formspec", formspec_cfg(pos, mem)) + meta:set_string("formspec", formspec_cfg()) elseif fields.back then meta:set_string("formspec", formspec(pos, mem)) elseif fields.start then @@ -373,6 +390,7 @@ end local function on_power(pos) local mem = tubelib2.get_mem(pos) mem.power_available = true + mem.charging = true signs_bot.infotext(pos, S("charging")) end @@ -383,7 +401,7 @@ local function on_nopower(pos) end minetest.register_node("signs_bot:box", { - description = I("Signs Bot Box"), + description = S("Signs Bot Box"), stack_max = 1, tiles = { -- up, down, right, left, back, front @@ -417,7 +435,7 @@ minetest.register_node("signs_bot:box", { meta:set_string("formspec", formspec(pos, mem)) meta:set_string("signs_bot_cmnd", "turn_off") meta:set_int("err_code", 0) - signs_bot.infotext(pos, I("stopped")) + signs_bot.infotext(pos, S("stopped")) if minetest.global_exists("techage") then techage.ElectricCable:after_place_node(pos) mem.capa = get_capa(itemstack) @@ -473,12 +491,12 @@ minetest.register_node("signs_bot:box", { on_power = function(pos) local mem = tubelib2.get_mem(pos) mem.power_available = true - signs_bot.infotext(pos, I("charging")) + signs_bot.infotext(pos, S("charging")) end, on_nopower = function(pos) local mem = tubelib2.get_mem(pos) mem.power_available = false - signs_bot.infotext(pos, I("no power")) + signs_bot.infotext(pos, S("no power")) end, nominal = PWR_NEEDED, } @@ -516,22 +534,22 @@ end if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "box", { - name = I("Signs Bot Box"), + name = S("Signs Bot Box"), data = { item = "signs_bot:box", text = table.concat({ - I("The Box is the housing of the bot."), - I("Place the box and start the bot by means of the 'On' button."), - I("If the mod techage is installed, the bot needs electrical power."), + S("The Box is the housing of the bot."), + S("Place the box and start the bot by means of the 'On' button."), + S("If the mod techage is installed, the bot needs electrical power."), "", - I("The bot leaves the box on the right side."), - I("It will not start, if this position is blocked."), + S("The bot leaves the box on the right side."), + S("It will not start, if this position is blocked."), "", - I("To stop and remove the bot, press the 'Off' button."), + S("To stop and remove the bot, press the 'Off' button."), "", - I("The box inventory simulates the inventory of the bot."), - I("You will not be able to access the inventory, if the bot is running."), - I("The bot can carry up to 8 stacks and 6 signs with it."), + S("The box inventory simulates the inventory of the bot."), + S("You will not be able to access the inventory, if the bot is running."), + S("The bot can carry up to 8 stacks and 6 signs with it."), }, "\n") }, }) diff --git a/signs_bot/bot_flap.lua b/signs_bot/bot_flap.lua index e4b9347..ed2ecaa 100644 --- a/signs_bot/bot_flap.lua +++ b/signs_bot/bot_flap.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPLv3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local function formspec(cmnd) cmnd = minetest.formspec_escape(cmnd) @@ -28,7 +22,7 @@ local function formspec(cmnd) default.gui_bg_img.. default.gui_slots.. "label[0.3,0.3;"..cmnd.."]".. - "button_exit[2.5,5.5;2,1;exit;"..I("Exit").."]" + "button_exit[2.5,5.5;2,1;exit;"..S("Exit").."]" end local commands = [[dig_sign 6 @@ -37,7 +31,7 @@ place_sign_behind 6 ]] minetest.register_node("signs_bot:bot_flap", { - description = "Bot Flap", + description = S("Bot Flap"), paramtype2 = "facedir", tiles = { "signs_bot_bot_flap_top.png", @@ -69,13 +63,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "bot_flap", { - name = I("Bot Flap"), + name = S("Bot Flap"), data = { item = "signs_bot:bot_flap", text = table.concat({ - I("The flap is a simple block used as door for the bot."), - I("Place the flap in any wall, and the bot will automatically open"), - I("and close the flap as it passes through it."), + S("The flap is a simple block used as door for the bot."), + S("Place the flap in any wall, and the bot will automatically open"), + S("and close the flap as it passes through it."), }, "\n") }, }) diff --git a/signs_bot/bot_sensor.lua b/signs_bot/bot_sensor.lua index db8c037..0d2e90f 100644 --- a/signs_bot/bot_sensor.lua +++ b/signs_bot/bot_sensor.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,22 +13,19 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") - -local lib = signs_bot.lib +-- Load support for I18n. +local S = signs_bot.S local function update_infotext(pos, dest_pos, cmnd) - M(pos):set_string("infotext", I("Bot Sensor: Connected with ")..S(dest_pos).." / "..cmnd) + M(pos):set_string("infotext", S("Bot Sensor: Connected with").." "..P2S(dest_pos).." / "..cmnd) end minetest.register_node("signs_bot:bot_sensor", { - description = I("Bot Sensor"), + description = S("Bot Sensor"), inventory_image = "signs_bot_sensor_bot_inv.png", drawtype = "nodebox", node_box = { @@ -49,7 +46,7 @@ minetest.register_node("signs_bot:bot_sensor", { after_place_node = function(pos, placer) local meta = M(pos) - meta:set_string("infotext", I("Bot Sensor: Not connected")) + meta:set_string("infotext", S("Bot Sensor: Not connected")) end, update_infotext = update_infotext, @@ -57,13 +54,14 @@ minetest.register_node("signs_bot:bot_sensor", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, is_ground_content = false, groups = {sign_bot_sensor = 1, cracky = 1}, sounds = default.node_sound_metal_defaults(), }) minetest.register_node("signs_bot:bot_sensor_on", { - description = I("Bot Sensor"), + description = S("Bot Sensor"), drawtype = "nodebox", node_box = { type = "fixed", @@ -100,6 +98,7 @@ minetest.register_node("signs_bot:bot_sensor_on", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, is_ground_content = false, diggable = false, groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1}, @@ -118,13 +117,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "bot_sensor", { - name = I("Bot Sensor"), + name = S("Bot Sensor"), data = { item = "signs_bot:bot_sensor", text = table.concat({ - I("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."), - I("the sensor range is one node/meter."), - I("The sensor direction does not care."), + S("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."), + S("the sensor range is one node/meter."), + S("The sensor direction does not care."), }, "\n") }, }) diff --git a/signs_bot/cart_sensor.lua b/signs_bot/cart_sensor.lua index 4aa4420..350e035 100644 --- a/signs_bot/cart_sensor.lua +++ b/signs_bot/cart_sensor.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,20 +13,19 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local CYCLE_TIME = 2 local function update_infotext(pos, dest_pos, dest_idx) - M(pos):set_string("infotext", I("Cart Sensor: Connected with ")..S(dest_pos).." / "..dest_idx) + M(pos):set_string("infotext", S("Cart Sensor: Connected with").." "..P2S(dest_pos).." / "..dest_idx) end local function swap_node(pos, name) @@ -39,18 +38,8 @@ local function swap_node(pos, name) return true end -local function check_cart(pos) - for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do - if object:get_entity_name() == "minecart:cart" then - return true - end - end - return false -end - local function node_timer(pos) - local pos1 = lib.next_pos(pos, M(pos):get_int("param2")) - if check_cart(pos1) then + if minecart.is_cart_available(pos, M(pos):get_int("param2"), 1) then if swap_node(pos, "signs_bot:cart_sensor_on") then signs_bot.send_signal(pos) signs_bot.lib.activate_extender_nodes(pos, true) @@ -62,7 +51,7 @@ local function node_timer(pos) end minetest.register_node("signs_bot:cart_sensor", { - description = I("Cart Sensor"), + description = S("Cart Sensor"), inventory_image = "signs_bot_sensor_cart_inv.png", drawtype = "nodebox", node_box = { @@ -83,7 +72,7 @@ minetest.register_node("signs_bot:cart_sensor", { after_place_node = function(pos, placer) local meta = M(pos) - meta:set_string("infotext", "Cart Sensor: Not connected") + meta:set_string("infotext", S("Cart Sensor: Not connected")) minetest.get_node_timer(pos):start(CYCLE_TIME) local node = minetest.get_node(pos) meta:set_int("param2", (node.param2 + 2) % 4) @@ -93,6 +82,7 @@ minetest.register_node("signs_bot:cart_sensor", { update_infotext = update_infotext, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -101,7 +91,7 @@ minetest.register_node("signs_bot:cart_sensor", { }) minetest.register_node("signs_bot:cart_sensor_on", { - description = I("Cart Sensor"), + description = S("Cart Sensor"), drawtype = "nodebox", node_box = { type = "fixed", @@ -123,6 +113,7 @@ minetest.register_node("signs_bot:cart_sensor_on", { update_infotext = update_infotext, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -156,13 +147,13 @@ minetest.register_lbm({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "cart_sensor", { - name = I("Cart Sensor"), + name = S("Cart Sensor"), data = { item = "signs_bot:cart_sensor", text = table.concat({ - I("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."), - I("the sensor range is one node/meter."), - I("The sensor has an active side (red) that must point to the rail/cart."), + S("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."), + S("the sensor range is one node/meter."), + S("The sensor has an active side (red) that must point to the rail/cart."), }, "\n") }, }) diff --git a/signs_bot/changer.lua b/signs_bot/changer.lua index c299ede..6baa9c8 100644 --- a/signs_bot/changer.lua +++ b/signs_bot/changer.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -34,7 +31,7 @@ local formspec = "size[8,7]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[1,1.3;"..I("Signs:").."]".. + "label[1,1.3;"..S("Signs:").."]".. "label[2.6,0.7;1]label[5.1,0.7;2]".. "list[context;sign;3,0.5;2,2;]".. "label[2.6,1.7;3]label[5.1,1.7;4]".. @@ -100,10 +97,15 @@ local function allow_metadata_inventory() return 0 end +local function can_dig(pos) + local inv = minetest.get_inventory({type="node", pos=pos}) + return inv:is_empty("sign") +end + for idx = 1,4 do local not_in_inv = idx == 1 and 0 or 1 minetest.register_node("signs_bot:changer"..idx, { - description = I("Bot Control Unit"), + description = S("Bot Control Unit"), inventory_image = "signs_bot_ctrl_unit_inv.png", drawtype = "nodebox", node_box = { @@ -139,9 +141,11 @@ for idx = 1,4 do allow_metadata_inventory_put = allow_metadata_inventory, allow_metadata_inventory_take = allow_metadata_inventory, on_punch = swap_node, + can_dig = can_dig, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -162,18 +166,18 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "changer", { - name = I("Bot Control Unit"), + name = S("Bot Control Unit"), data = { item = "signs_bot:changer1", text = table.concat({ - I("The Bot Control Unit is used to lead the bot by means of signs."), - I("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."), + S("The Bot Control Unit is used to lead the bot by means of signs."), + S("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."), "", - I("To load the unit, place a sign on the red side of the unit and click on the unit."), - I("The sign disappears / is moved to the inventory of the unit."), - I("This can be repeated 3 times."), + S("To load the unit, place a sign on the red side of the unit and click on the unit."), + S("The sign disappears / is moved to the inventory of the unit."), + S("This can be repeated 3 times."), "", - I("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."), + S("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."), }, "\n") }, }) diff --git a/signs_bot/chest.lua b/signs_bot/chest.lua index e7f7f8b..024f977 100644 --- a/signs_bot/chest.lua +++ b/signs_bot/chest.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-0221 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,12 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local NODE_IO = minetest.global_exists("node_io") @@ -50,7 +49,7 @@ end local function update_infotext(pos, dest_pos, cmnd) local meta = M(pos) local state = get_inv_state(pos) - meta:set_string("infotext", I("Bot Chest: Sends signal to ")..S(dest_pos).." / "..cmnd..", if "..state) + meta:set_string("infotext", S("Bot Chest: Sends signal to").." "..P2S(dest_pos).." / "..cmnd..", if "..state) meta:set_string("state", state) end @@ -68,7 +67,7 @@ end if NODE_IO then minetest.register_node("signs_bot:chest", { - description = I("Signs Bot Chest"), + description = S("Signs Bot Chest"), tiles = { -- up, down, right, left, back, front 'signs_bot_chest_top.png', @@ -91,7 +90,7 @@ if NODE_IO then local meta = minetest.get_meta(pos) meta:set_string("owner", placer:get_player_name()) meta:set_string("formspec", formspec(pos, mem)) - meta:set_string("infotext", "Bot Chest: Not connected") + meta:set_string("infotext", S("Bot Chest: Not connected")) end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) @@ -158,7 +157,7 @@ if NODE_IO then }) else minetest.register_node("signs_bot:chest", { - description = I("Signs Bot Chest"), + description = S("Signs Bot Chest"), tiles = { -- up, down, right, left, back, front 'signs_bot_chest_top.png', @@ -181,7 +180,7 @@ else local meta = minetest.get_meta(pos) meta:set_string("owner", placer:get_player_name()) meta:set_string("formspec", formspec(pos, mem)) - meta:set_string("infotext", "Bot Chest: Not connected") + meta:set_string("infotext", S("Bot Chest: Not connected")) end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) @@ -245,15 +244,15 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "chest", { - name = I("Signs Bot Chest"), + name = S("Signs Bot Chest"), data = { item = "signs_bot:chest", text = table.concat({ - I("The Signs Bot Chest is a special chest with sensor function."), - I("It sends a signal depending on the chest state."), - I("Possible states are 'empty', 'not empty', 'almost full'"), + S("The Signs Bot Chest is a special chest with sensor function."), + S("It sends a signal depending on the chest state."), + S("Possible states are 'empty', 'not empty', 'almost full'"), "", - I("A typical use case is to turn off the bot, when the chest is almost full or empty."), + S("A typical use case is to turn off the bot, when the chest is almost full or empty."), }, "\n") }, }) diff --git a/signs_bot/cmd_farming.lua b/signs_bot/cmd_farming.lua index 56dffff..905be0e 100644 --- a/signs_bot/cmd_farming.lua +++ b/signs_bot/cmd_farming.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -11,14 +11,8 @@ Bot farming commands ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -65,7 +59,7 @@ signs_bot.register_botcommand("sow_seed", { mod = "farming", params = "", num_param = 1, - description = I("Sow farming seeds\nin front of the robot"), + description = S("Sow farming seeds\nin front of the robot"), check = function(slot) slot = tonumber(slot) return slot and slot > 0 and slot < 9 @@ -97,7 +91,10 @@ local function harvesting(base_pos, mem) -- Do not cache the result of get_node_drops; it is a probabilistic function! local drops = minetest.get_node_drops(node.name) for _,itemstring in ipairs(drops) do - bot_inv_put_item(base_pos, 0, ItemStack(itemstring)) + local leftover = bot_inv_put_item(base_pos, 0, ItemStack(itemstring)) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(mem.robot_pos, leftover) + end end end end @@ -107,7 +104,7 @@ signs_bot.register_botcommand("harvest", { mod = "farming", params = "", num_param = 0, - description = I("Harvest farming products\nin front of the robot\non a 3x3 field."), + description = S("Harvest farming products\nin front of the robot\non a 3x3 field."), cmnd = function(base_pos, mem) if not mem.steps then mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0) @@ -145,7 +142,7 @@ signs_bot.register_botcommand("plant_sapling", { mod = "farming", params = "", num_param = 1, - description = I("Plant a sapling\nin front of the robot"), + description = S("Plant a sapling\nin front of the robot"), check = function(slot) slot = tonumber(slot) return slot and slot > 0 and slot < 9 @@ -168,7 +165,7 @@ turn_around]] signs_bot.register_sign({ name = "farming", - description = I('Sign "farming"'), + description = S('Sign "farming"'), commands = CMD, image = "signs_bot_sign_farming.png", }) @@ -184,14 +181,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "farming", { - name = I("Sign 'farming'"), + name = S("Sign 'farming'"), data = { item = "signs_bot:farming", text = table.concat({ - I("Used to harvest and seed a 3x3 field."), - I("Place the sign in front of the field."), - I("The seed to be placed has to be in the first inventory slot of the bot."), - I("When finished, the bot turns."), + S("Used to harvest and seed a 3x3 field."), + S("Place the sign in front of the field."), + S("The seed to be placed has to be in the first inventory slot of the bot."), + S("When finished, the bot turns."), }, "\n") }, }) diff --git a/signs_bot/cmd_flowers.lua b/signs_bot/cmd_flowers.lua index 61d3c31..01fa355 100644 --- a/signs_bot/cmd_flowers.lua +++ b/signs_bot/cmd_flowers.lua @@ -11,14 +11,8 @@ Bot flower cutting command ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -48,16 +42,28 @@ minetest.after(1, function() end end) +local function is_tree(node) + if minetest.get_item_group(node.name, "tree") == 1 then + return signs_bot.handle_drop_like_a_player(node) + end + if minetest.get_item_group(node.name, "leaves") == 1 then + return signs_bot.handle_drop_like_a_player(node) + end +end + local function harvesting(base_pos, mem) local pos = mem.pos_tbl and mem.pos_tbl[mem.steps] mem.steps = (mem.steps or 1) + 1 if pos and lib.not_protected(base_pos, pos) then local node = minetest.get_node_or_nil(pos) - local drop = Flowers[node.name] + local drop = Flowers[node.name] or is_tree(node) if drop then minetest.remove_node(pos) - bot_inv_put_item(base_pos, 0, ItemStack(drop)) + local leftover = bot_inv_put_item(base_pos, 0, ItemStack(drop)) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(mem.robot_pos, leftover) + end end end end @@ -66,7 +72,7 @@ signs_bot.register_botcommand("cutting", { mod = "farming", params = "", num_param = 0, - description = I("Cutting flowers\nin front of the robot\non a 3x3 field."), + description = S("Cutting flowers, leaves and tree blocks\nin front of the robot\non a 3x3 field."), cmnd = function(base_pos, mem) if not mem.steps then mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0) @@ -91,7 +97,7 @@ turn_around]] signs_bot.register_sign({ name = "flowers", - description = I('Sign "flowers"'), + description = S('Sign "flowers"'), commands = CMD, image = "signs_bot_sign_flowers.png", }) @@ -107,13 +113,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "flowers", { - name = I("Sign 'flowers'"), + name = S("Sign 'flowers'"), data = { item = "signs_bot:flowers", text = table.concat({ - I("Used to cut flowers on a 3x3 field."), - I("Place the sign in front of the field."), - I("When finished, the bot turns."), + S("Used to cut flowers on a 3x3 field."), + S("Place the sign in front of the field."), + S("When finished, the bot turns."), }, "\n") }, }) diff --git a/signs_bot/cmd_item.lua b/signs_bot/cmd_item.lua index 6aa1d12..4ed7d70 100644 --- a/signs_bot/cmd_item.lua +++ b/signs_bot/cmd_item.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -90,7 +87,7 @@ signs_bot.register_botcommand("take_item", { mod = "item", params = " ", num_param = 2, - description = I("Take items from a chest like node\nand put it into the item inventory.\n".. + description = S("Take items from a chest like node\nand put it into the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -115,7 +112,7 @@ signs_bot.register_botcommand("add_item", { mod = "item", params = " ", num_param = 2, - description = I("Add items to a chest like node\ntaken from the item inventory.\n".. + description = S("Add items to a chest like node\ntaken from the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -140,7 +137,7 @@ signs_bot.register_botcommand("add_fuel", { mod = "item", params = " ", num_param = 2, - description = I("Add fuel to a furnace like node\ntaken from the item inventory.\n".. + description = S("Add fuel to a furnace like node\ntaken from the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -165,7 +162,7 @@ signs_bot.register_botcommand("cond_take_item", { mod = "item", params = " ", num_param = 2, - description = I("deprecated, use bot inventory configuration instead"), + description = S("deprecated, use bot inventory configuration instead"), check = function(num, slot) return false end, @@ -178,7 +175,7 @@ signs_bot.register_botcommand("cond_add_item", { mod = "item", params = " ", num_param = 2, - description = I("deprecated, use bot inventory configuration instead"), + description = S("deprecated, use bot inventory configuration instead"), check = function(num, slot) return false end, @@ -191,7 +188,7 @@ signs_bot.register_botcommand("pickup_items", { mod = "item", params = "", num_param = 1, - description = I("Pick up all objects\n".. + description = S("Pick up all objects\n".. "in a 3x3 field.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(slot) @@ -219,7 +216,7 @@ signs_bot.register_botcommand("drop_items", { mod = "item", params = " ", num_param = 2, - description = I("Drop items in front of the bot.\n".. + description = S("Drop items in front of the bot.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -246,18 +243,10 @@ signs_bot.register_botcommand("punch_cart", { mod = "item", params = "", num_param = 0, - description = I("Punch a rail cart to start it"), + description = S("Punch a rail cart to start it"), cmnd = function(base_pos, mem) - local pos = lib.dest_pos(mem.robot_pos, mem.robot_param2, {0}) - for _, object in pairs(minetest.get_objects_inside_radius(pos, 2)) do - if object:get_entity_name() == "minecart:cart" then - object:punch(object, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = 1}, - }, minetest.facedir_to_dir(mem.robot_param2)) - break -- start only one cart - end - end + local punch_dir = minetest.facedir_to_dir(mem.robot_param2) + minecart.punch_cart(mem.robot_pos, mem.robot_param2, 1, punch_dir) return signs_bot.DONE end, }) diff --git a/signs_bot/cmd_move.lua b/signs_bot/cmd_move.lua index 5d11438..76cae4d 100644 --- a/signs_bot/cmd_move.lua +++ b/signs_bot/cmd_move.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local get_node_lvm = tubelib2.get_node_lvm @@ -135,7 +129,7 @@ signs_bot.register_botcommand("backward", { mod = "move", params = "", num_param = 0, - description = I("Move the robot one step back"), + description = S("Move the robot one step back"), cmnd = function(base_pos, mem) local new_pos = backward_robot(mem) if new_pos then -- not blocked? @@ -160,7 +154,7 @@ signs_bot.register_botcommand("turn_left", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot to the left"), + description = S("Turn the robot to the left"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "L") return signs_bot.DONE @@ -171,7 +165,7 @@ signs_bot.register_botcommand("turn_right", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot to the right"), + description = S("Turn the robot to the right"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") return signs_bot.DONE @@ -182,7 +176,7 @@ signs_bot.register_botcommand("turn_around", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot around"), + description = S("Turn the robot around"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") @@ -217,7 +211,7 @@ signs_bot.register_botcommand("move_up", { mod = "move", params = "", num_param = 0, - description = I("Move the robot upwards"), + description = S("Move the robot upwards"), cmnd = function(base_pos, mem) local new_pos = robot_up(mem.robot_pos, mem.robot_param2) if new_pos then -- not blocked? @@ -251,7 +245,7 @@ signs_bot.register_botcommand("move_down", { mod = "move", params = "", num_param = 0, - description = I("Move the robot down"), + description = S("Move the robot down"), cmnd = function(base_pos, mem) local new_pos = robot_down(mem.robot_pos, mem.robot_param2) if new_pos then -- not blocked? @@ -265,14 +259,14 @@ signs_bot.register_botcommand("pause", { mod = "move", params = "", num_param = 1, - description = I("Stop the robot for seconds\n(1..9999)"), + description = S("Stop the robot for seconds\n(1..9999)"), check = function(sec) sec = tonumber(sec) or 1 return sec and sec > 0 and sec < 10000 end, cmnd = function(base_pos, mem, sec) if not mem.steps then - mem.steps = tonumber(sec or 1) + mem.steps = tonumber(sec) or 1 end mem.steps = mem.steps - 1 if mem.steps == 0 then @@ -290,7 +284,7 @@ signs_bot.register_botcommand("stop", { mod = "move", params = "", num_param = 0, - description = I("Stop the robot."), + description = S("Stop the robot."), cmnd = function(base_pos, mem, slot) if mem.capa then mem.capa = mem.capa + 2 @@ -303,7 +297,7 @@ signs_bot.register_botcommand("turn_off", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot off\n".. + description = S("Turn the robot off\n".. "and put it back in the box."), cmnd = function(base_pos, mem) signs_bot.stop_robot(base_pos, mem) diff --git a/signs_bot/cmd_pattern.lua b/signs_bot/cmd_pattern.lua index 960cd7c..867e61f 100644 --- a/signs_bot/cmd_pattern.lua +++ b/signs_bot/cmd_pattern.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -117,7 +111,7 @@ signs_bot.register_botcommand("pattern", { mod = "copy", params = "", num_param = 0, - description = I("Store pattern to be cloned."), + description = S("Store pattern to be cloned."), cmnd = function(base_pos, mem) mem.pttrn_pos = lib.next_pos(mem.robot_pos, mem.robot_param2) mem.pttrn_param2 = mem.robot_param2 @@ -129,7 +123,7 @@ signs_bot.register_botcommand("copy", { mod = "copy", params = " ", num_param = 2, - description = I("Copy the nodes from\n".. + description = S("Copy the nodes from\n".. "the stored pattern position\n".. " is: 3x1, 3x2, 3x3,\n".. "5x1, 5x2, 5x3 (wide x deep)\n".. @@ -178,7 +172,7 @@ minetest.register_node("signs_bot:missing", { signs_bot.register_sign({ name = "pattern", - description = I('Sign "pattern"'), + description = S('Sign "pattern"'), commands = "pattern\nturn_around", image = "signs_bot_sign_pattern.png", }) @@ -207,7 +201,7 @@ turn_around]] signs_bot.register_sign({ name = "copy3x3x3", - description = I('Sign "copy 3x3x3"'), + description = S('Sign "copy 3x3x3"'), commands = CMND, image = "signs_bot_sign_copy3x3x3.png", }) @@ -223,14 +217,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "pattern", { - name = I("Sign 'pattern'"), + name = S("Sign 'pattern'"), data = { item = "signs_bot:pattern", text = table.concat({ - I("Used to make a copy of a 3x3x3 cube."), - I("Place the sign in front of the pattern to be copied."), - I("Use the copy sign to make the copy of this pattern on a different location."), - I("The bot must first reach the pattern sign, then the copy sign."), + S("Used to make a copy of a 3x3x3 cube."), + S("Place the sign in front of the pattern to be copied."), + S("Use the copy sign to make the copy of this pattern on a different location."), + S("The bot must first reach the pattern sign, then the copy sign."), }, "\n") }, }) @@ -238,13 +232,13 @@ end if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "copy3x3x3", { - name = I("Sign 'copy3x3x3'"), + name = S("Sign 'copy3x3x3'"), data = { item = "signs_bot:copy3x3x3", text = table.concat({ - I("Used to make a copy of a 3x3x3 cube."), - I("Place the sign in front of the location, where the copy should be made."), - I("Use the pattern sign to mark the pattern."), + S("Used to make a copy of a 3x3x3 cube."), + S("Place the sign in front of the location, where the copy should be made."), + S("Use the pattern sign to mark the pattern."), }, "\n") }, }) diff --git a/signs_bot/cmd_place.lua b/signs_bot/cmd_place.lua index 23bfe6f..920d49e 100644 --- a/signs_bot/cmd_place.lua +++ b/signs_bot/cmd_place.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local bot_inv_take_item = signs_bot.bot_inv_take_item @@ -32,13 +26,23 @@ end local tValidLevels = {[-1] = -1, [0] = 0, [1] = 1} -- for items with paramtype2 = "facedir" -local tRotations = { - [0] = {8,20,4}, - [1] = {16,20,12}, - [2] = {4,20,8}, - [3] = {12,20,16}, +local tRotations = {} + +local Rotations = { + {0,8,22,4}, + {1,17,21,13}, + {2,6,20,10}, + {3,15,23,19}, } +for _,v in ipairs(Rotations) do + local t = table.copy(v) + for i = 1,4 do + table.insert(t, 1, table.remove(t)) + tRotations[t[1]] = {t[2], t[3], t[4]} + end +end + -- -- Place/dig items -- @@ -46,7 +50,7 @@ local function place_item(base_pos, robot_pos, param2, slot, route, level) local pos1, p2 = lib.dest_pos(robot_pos, param2, route) pos1.y = pos1.y + level if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_air_like(pos1) then local taken = signs_bot.bot_inv_take_item(base_pos, slot, 1) @@ -74,7 +78,7 @@ signs_bot.register_botcommand("place_front", { mod = "place", params = " ", num_param = 2, - description = I("Place a block in front of the robot\n".. + description = S("Place a block in front of the robot\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -95,7 +99,7 @@ signs_bot.register_botcommand("place_left", { mod = "place", params = " ", num_param = 2, - description = I("Place a block on the left side\n".. + description = S("Place a block on the left side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -116,7 +120,7 @@ signs_bot.register_botcommand("place_right", { mod = "place", params = " ", num_param = 2, - description = I("Place a block on the right side\n".. + description = S("Place a block on the right side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -136,7 +140,7 @@ signs_bot.register_botcommand("place_right", { local function place_item_below(base_pos, robot_pos, param2, slot) local pos1 = {x=robot_pos.x,y=robot_pos.y-1,z=robot_pos.z} if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end local node = tubelib2.get_node_lvm(pos1) if node.name == "signs_bot:robot_foot" then @@ -155,7 +159,7 @@ signs_bot.register_botcommand("place_below", { mod = "place", params = "", num_param = 1, - description = I("Place a block under the robot.\n".. + description = S("Place a block under the robot.\n".. "Hint: use 'move_up' first.\n".. " is the inventory slot (1..8)"), check = function(slot) @@ -171,7 +175,7 @@ signs_bot.register_botcommand("place_below", { local function place_item_above(base_pos, robot_pos, param2, slot) local pos1 = {x=robot_pos.x,y=robot_pos.y+1,z=robot_pos.z} if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_air_like(pos1) then local taken = bot_inv_take_item(base_pos, slot, 1) @@ -189,7 +193,7 @@ signs_bot.register_botcommand("place_above", { mod = "place", params = "", num_param = 1, - description = I("Place a block above the robot.\n".. + description = S("Place a block above the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -207,13 +211,13 @@ local function dig_item(base_pos, robot_pos, param2, slot, route, level) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.remove_node(pos1) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -223,7 +227,7 @@ signs_bot.register_botcommand("dig_front", { mod = "place", params = " ", num_param = 2, - description = I("Dig the block in front of the robot\n".. + description = S("Dig the block in front of the robot\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -245,7 +249,7 @@ signs_bot.register_botcommand("dig_left", { mod = "place", params = " ", num_param = 2, - description = I("Dig the block on the left side\n".. + description = S("Dig the block on the left side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -267,7 +271,7 @@ signs_bot.register_botcommand("dig_right", { mod = "place", params = " ", num_param = 2, - description = I("Dig the block on the right side\n".. + description = S("Dig the block on the right side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -290,13 +294,13 @@ local function dig_item_below(base_pos, robot_pos, param2, slot) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.set_node(pos1, {name="signs_bot:robot_foot"}) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -306,7 +310,7 @@ signs_bot.register_botcommand("dig_below", { mod = "place", params = "", num_param = 1, - description = I("Dig the block under the robot.\n".. + description = S("Dig the block under the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -324,13 +328,13 @@ local function dig_item_above(base_pos, robot_pos, param2, slot) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.remove_node(pos1) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -340,7 +344,7 @@ signs_bot.register_botcommand("dig_above", { mod = "place", params = "", num_param = 1, - description = I("Dig the block above the robot.\n".. + description = S("Dig the block above the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -358,7 +362,7 @@ local function rotate_item(base_pos, robot_pos, param2, route, level, steps) pos1.y = pos1.y + level local node = tubelib2.get_node_lvm(pos1) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_simple_node(node) then local p2 = tRotations[node.param2] and tRotations[node.param2][steps] @@ -372,7 +376,8 @@ end signs_bot.register_botcommand("rotate_item", { mod = "place", params = " ", - description = I("Rotate the block in front of the robot\n".. + num_param = 2, + description = S("Rotate the block in front of the robot\n".. " is one of: -1 0 +1\n".. " is one of: 1 2 3"), check = function(lvl, steps) @@ -391,7 +396,7 @@ signs_bot.register_botcommand("rotate_item", { -- Simplified torch which can be placed w/o a fake player minetest.register_node("signs_bot:torch", { - description = "Bot torch", + description = S("Bot torch"), inventory_image = "default_torch_on_floor.png", wield_image = "default_torch_on_floor.png", drawtype = "nodebox", @@ -428,6 +433,7 @@ minetest.register_node("signs_bot:torch", { "group:bakedclay", "group:soil"}, paramtype = "light", paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, walkable = false, liquids_pointable = false, diff --git a/signs_bot/cmd_sign.lua b/signs_bot/cmd_sign.lua index 4e9f9aa..8917dab 100644 --- a/signs_bot/cmd_sign.lua +++ b/signs_bot/cmd_sign.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -48,12 +45,12 @@ local function formspec1(meta) default.gui_bg_img.. default.gui_slots.. "style_type[textarea,table;font=mono]".. - "tabheader[0,0;tab;"..I("Commands,Help")..";1;;true]".. - "field[0.3,0.5;9,1;name;"..I("Sign name:")..";"..name.."]".. + "tabheader[0,0;tab;"..S("Commands,Help")..";1;;true]".. + "field[0.3,0.5;9,1;name;"..S("Sign name:")..";"..name.."]".. "textarea[0.3,1.2;9,7.2;cmnd;;"..cmnd.."]".. "label[0.3,7.5;"..err_msg.."]".. - "button_exit[5,7.5;2,1;cancel;"..I("Cancel").."]".. - "button[7,7.5;2,1;check;"..I("Check").."]" + "button_exit[5,7.5;2,1;cancel;"..S("Cancel").."]".. + "button[7,7.5;2,1;check;"..S("Check").."]" end local function formspec2(pos, text) @@ -62,10 +59,10 @@ local function formspec2(pos, text) default.gui_bg_img.. default.gui_slots.. "style_type[textarea,table;font=mono]".. - "tabheader[0,0;tab;"..I("Commands,Help")..";2;;true]".. + "tabheader[0,0;tab;"..S("Commands,Help")..";2;;true]".. "table[0.1,0;8.6,4;command;"..sCmnds..";"..pos.."]".. - "textarea[0.3,4.5;9,3.5;help;Help:;"..text.."]".. - "button[3,7.5;3,1;copy;"..I("Copy Cmnd").."]" + "textarea[0.3,4.5;9,3.5;help;"..S("Help")..":;"..text.."]".. + "button[3,7.5;3,1;copy;"..S("Copy Cmnd").."]" end local function add_arrow(text, line_num) @@ -94,7 +91,7 @@ local function append_line(pos, meta, line) local text = meta:get_string("signs_bot_cmnd").."\n"..line meta:set_string("signs_bot_cmnd", text) meta:set_int("err_code", 1) -- zero means OK - meta:set_string("err_msg", "please check the added line(s)") + meta:set_string("err_msg", S("please check the added line(s)")) end local function check_and_store(pos, meta, fields) @@ -106,7 +103,7 @@ local function check_and_store(pos, meta, fields) end minetest.register_node("signs_bot:sign_cmnd", { - description = I('Sign "command"'), + description = S('Sign "command"'), drawtype = "nodebox", inventory_image = "signs_bot_sign_cmnd.png", node_box = { @@ -134,8 +131,8 @@ minetest.register_node("signs_bot:sign_cmnd", { nmeta:set_string("err_msg", imeta:get_string("err_msg")) nmeta:set_int("err_code", imeta:get_int("err_code")) else - nmeta:set_string("sign_name", I('Sign "command"')) - nmeta:set_string("signs_bot_cmnd", I("-- enter or copy commands from help page")) + nmeta:set_string("sign_name", S('Sign "command"')) + nmeta:set_string("signs_bot_cmnd", S("-- enter or copy commands from help page")) nmeta:set_int("err_code", 0) end nmeta:set_string("infotext", nmeta:get_string("sign_name")) @@ -175,6 +172,7 @@ minetest.register_node("signs_bot:sign_cmnd", { after_dig_node = lib.after_dig_sign_node, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, is_ground_content = false, drop = "", @@ -215,18 +213,18 @@ local function place_sign(base_pos, robot_pos, param2, slot) lib.place_sign(pos1, sign, param2) return signs_bot.DONE else - return signs_bot.ERROR, I("Error: Signs inventory empty") + return signs_bot.ERROR, S("Error: Signs inventory empty") end end end - return signs_bot.ERROR, I("Error: Position protected or occupied") + return signs_bot.ERROR, S("Error: Position protected or occupied") end signs_bot.register_botcommand("place_sign", { mod = "sign", params = "", num_param = 1, - description = I("Place a sign in front of the robot\ntaken from the signs inventory\n".. + description = S("Place a sign in front of the robot\ntaken from the signs inventory\n".. " is the inventory slot (1..6)"), check = function(slot) slot = tonumber(slot) or 1 @@ -247,18 +245,18 @@ local function place_sign_behind(base_pos, robot_pos, param2, slot) lib.place_sign(pos1, sign, param2) return signs_bot.DONE else - return signs_bot.ERROR, I("Error: Signs inventory empty") + return signs_bot.ERROR, S("Error: Signs inventory empty") end end end - return signs_bot.ERROR, I("Error: Position protected or occupied") + return signs_bot.ERROR, S("Error: Position protected or occupied") end signs_bot.register_botcommand("place_sign_behind", { mod = "sign", params = "", num_param = 1, - description = I("Place a sign behind the robot\ntaken from the signs inventory\n".. + description = S("Place a sign behind the robot\ntaken from the signs inventory\n".. " is the inventory slot (1..6)"), check = function(slot) slot = tonumber(slot) or 1 @@ -277,7 +275,7 @@ local function dig_sign(base_pos, robot_pos, param2, slot) local err_code = meta:get_int("err_code") local name = meta:get_string("sign_name") if cmnd == "" then - return signs_bot.ERROR, I("Error: No sign available") + return signs_bot.ERROR, S("Error: No sign available") end if lib.not_protected(base_pos, pos1) then local node = tubelib2.get_node_lvm(pos1) @@ -289,18 +287,18 @@ local function dig_sign(base_pos, robot_pos, param2, slot) minetest.remove_node(pos1) if not put_inv_sign(base_pos, slot, sign) then signs_bot.lib.drop_items(robot_pos, sign) - return signs_bot.ERROR, I("Error: Signs inventory slot is occupied") + return signs_bot.ERROR, S("Error: Signs inventory slot is occupied") end return signs_bot.DONE end - return signs_bot.ERROR, I("Error: Position is protected") + return signs_bot.ERROR, S("Error: Position is protected") end signs_bot.register_botcommand("dig_sign", { mod = "sign", params = "", num_param = 1, - description = I("Dig the sign in front of the robot\n".. + description = S("Dig the sign in front of the robot\n".. "and add it to the signs inventory.\n".. " is the inventory slot (1..6)"), check = function(slot) @@ -317,23 +315,26 @@ local function trash_sign(base_pos, robot_pos, param2, slot) local pos1 = lib.dest_pos(robot_pos, param2, {0}) local cmnd = M(pos1):get_string("signs_bot_cmnd") if cmnd == "" then - return signs_bot.ERROR, I("Error: No sign available") + return signs_bot.ERROR, S("Error: No sign available") end if lib.not_protected(base_pos, pos1) then local node = tubelib2.get_node_lvm(pos1) local sign = ItemStack("signs_bot:sign_cmnd") minetest.remove_node(pos1) - signs_bot.bot_inv_put_item(base_pos, slot, sign) + local leftover = signs_bot.bot_inv_put_item(base_pos, slot, sign) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(robot_pos, leftover) + end return signs_bot.DONE end - return signs_bot.ERROR, I("Error: Position is protected") + return signs_bot.ERROR, S("Error: Position is protected") end signs_bot.register_botcommand("trash_sign", { mod = "sign", params = "", num_param = 1, - description = I("Dig the sign in front of the robot\n".. + description = S("Dig the sign in front of the robot\n".. "and add the cleared sign to\nthe item iventory.\n".. " is the inventory slot (1..8)"), check = function(slot) @@ -358,14 +359,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "sign_cmnd", { - name = I("Sign 'command'"), + name = S("Sign 'command'"), data = { item = "signs_bot:sign_cmnd", text = table.concat({ - I("The 'command' sign can be programmed by the player."), - I("Place the sign in front of you and use the node menu to program your sequence of bot commands."), - I("The menu has an edit field for your commands and a help page with all available commands."), - I("The help page has a copy button to simplify the programming."), + S("The 'command' sign can be programmed by the player."), + S("Place the sign in front of you and use the node menu to program your sequence of bot commands."), + S("The menu has an edit field for your commands and a help page with all available commands."), + S("The help page has a copy button to simplify the programming."), }, "\n") }, }) diff --git a/signs_bot/commands.lua b/signs_bot/commands.lua index e009a8d..6347273 100644 --- a/signs_bot/commands.lua +++ b/signs_bot/commands.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,12 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. +-- Load support for I18n. +local S = signs_bot.S + local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") local ci = dofile(MP.."/interpreter.lua") local lib = signs_bot.lib @@ -73,7 +72,7 @@ end function signs_bot.get_commands() local tbl = {} for _,mod in ipairs(SortedMods) do - tbl[#tbl+1] = mod.." "..I("commands:") + tbl[#tbl+1] = mod.." "..S("commands:") for _,cmnd in ipairs(SortedKeys[mod]) do local item = tCommands[cmnd] tbl[#tbl+1] = " "..item.name.." "..item.params @@ -90,7 +89,7 @@ function signs_bot.get_help_text(cmnd) return item.description end end - return I("unknown command") + return S("unknown command") end function signs_bot.check_commands(pos, text) @@ -154,12 +153,16 @@ local function activate_sensor(pos, param2) end end -local function bot_error(base_pos, mem, err) +local function bot_error(base_pos, mem, err, cmd) minetest.sound_play('signs_bot_error', {pos = base_pos}) minetest.sound_play('signs_bot_error', {pos = mem.robot_pos}) - print(err) - signs_bot.infotext(base_pos, err) - mem.error = true + if cmd then + signs_bot.infotext(base_pos, err .. ":\n'" .. cmd .. "'") + mem.error = err .. ": '" .. cmd .. "'" + else + signs_bot.infotext(base_pos, err) + mem.error = err + end return false end @@ -176,9 +179,9 @@ local function power_consumption(mem, cmnd) end function signs_bot.run_next_command(base_pos, mem) - local res, err = ci.run_script(base_pos, mem) + local res, err, cmd = ci.run_script(base_pos, mem) if res == ci.ERROR then - return bot_error(base_pos, mem, err) + return bot_error(base_pos, mem, err, cmd) elseif res == ci.EXIT then signs_bot.stop_robot(base_pos, mem) return false @@ -198,31 +201,31 @@ end signs_bot.register_botcommand("repeat", { mod = "core", params = "", - description = I("start of a 'repeat..end' block"), + description = S("start of a 'repeat..end' block"), }) signs_bot.register_botcommand("end", { mod = "core", params = "", - description = I("end command of a 'repeat..end' block"), + description = S("end command of a 'repeat..end' block"), }) signs_bot.register_botcommand("call", { mod = "core", params = "

Y~O_F7t4wJGg$rj*>LEH2svQ7>G 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 diff --git a/techage/manuals/toc_DE.md b/techage/manuals/toc_DE.md index 55e2323..cbc8c7a 100644 --- a/techage/manuals/toc_DE.md +++ b/techage/manuals/toc_DE.md @@ -30,6 +30,7 @@ - [TA2 Stromgenerator / TA2 Power Generator](./manual_ta2_DE.md#ta2-stromgenerator--ta2-power-generator) - [Items schieben und sortieren](./manual_ta2_DE.md#items-schieben-und-sortieren) - [Röhren / TechAge Tube](./manual_ta2_DE.md#röhren--techage-tube) + - [Röhren Konzentrator / Tube Concentrator](./manual_ta2_DE.md#röhren-konzentrator--tube-concentrator) - [TA2 Schieber / Pusher](./manual_ta2_DE.md#ta2-schieber--pusher) - [TA2 Verteiler / Distributor](./manual_ta2_DE.md#ta2-verteiler--distributor) - [Kieswaschanlage](./manual_ta2_DE.md#kieswaschanlage) @@ -119,7 +120,7 @@ - [TA3 Kiessieb / Gravel Sieve](./manual_ta3_DE.md#ta3-kiessieb--gravel-sieve) - [TA3 Kieswaschanlage / Gravel Rinser](./manual_ta3_DE.md#ta3-kieswaschanlage--gravel-rinser) - [TA3 Mühle / Grinder](./manual_ta3_DE.md#ta3-mühle--grinder) - - [TA3 Flüssigkeitensammler / Liquid Sampler](./manual_ta3_DE.md#ta3-flüssigkeitensammler--liquid-sampler) + - [TA3 Injektor / Injector](./manual_ta3_DE.md#ta3-injektor--injector) - [Werkzeuge](./manual_ta3_DE.md#werkzeuge) - [Techage Info Tool](./manual_ta3_DE.md#techage-info-tool) - [TechAge Programmer](./manual_ta3_DE.md#techage-programmer) @@ -181,7 +182,8 @@ - [TA4 Tank / TA4 Tank](./manual_ta4_DE.md#ta4-tank--ta4-tank) - [TA4 Pumpe / TA4 Pump](./manual_ta4_DE.md#ta4-pumpe--ta4-pump) - [TA4 Ofenheizung / furnace heater](./manual_ta4_DE.md#ta4-ofenheizung--furnace-heater) - - [TA4 Wasserpumpe / Water Pump](./manual_ta4_DE.md#ta4-wasserpumpe--water-pump) + - [TA4 Wasserpumpe / Water Pump (veraltet)](./manual_ta4_DE.md#ta4-wasserpumpe--water-pump-(veraltet)) + - [TA4 Wassereinlass / TA4 Water Inlet](./manual_ta4_DE.md#ta4-wassereinlass--ta4-water-inlet) - [TA4 Röhren / TA4 Tube](./manual_ta4_DE.md#ta4-röhren--ta4-tube) - [TA4 Schieber / Pusher](./manual_ta4_DE.md#ta4-schieber--pusher) - [TA4 Kiste / TA4 Chest](./manual_ta4_DE.md#ta4-kiste--ta4-chest) @@ -193,4 +195,5 @@ - [TA4 Steinbrecher / Quarry](./manual_ta4_DE.md#ta4-steinbrecher--quarry) - [TA4 Elektronikfabrik / Electronic Fab](./manual_ta4_DE.md#ta4-elektronikfabrik--electronic-fab) - [TA4 Injektor / Injector](./manual_ta4_DE.md#ta4-injektor--injector) - - [TA4 Recycler](./manual_ta4_DE.md#ta4-recycler) \ No newline at end of file + - [TA4 Recycler](./manual_ta4_DE.md#ta4-recycler) + - [TA4 Laser](./manual_ta4_DE.md#ta4-laser) \ No newline at end of file diff --git a/techage/manuals/toc_EN.md b/techage/manuals/toc_EN.md index 655e62d..d3f880f 100644 --- a/techage/manuals/toc_EN.md +++ b/techage/manuals/toc_EN.md @@ -30,6 +30,7 @@ - [TA2 Power Generator](./manual_ta2_EN.md#ta2-power-generator) - [Push and sort items](./manual_ta2_EN.md#push-and-sort-items) - [TechAge Tube](./manual_ta2_EN.md#techage-tube) + - [Tube Concentrator](./manual_ta2_EN.md#tube-concentrator) - [TA2 Pusher](./manual_ta2_EN.md#ta2-pusher) - [TA2 Distributor](./manual_ta2_EN.md#ta2-distributor) - [Gravel washer](./manual_ta2_EN.md#gravel-washer) @@ -118,7 +119,7 @@ - [TA3 Gravel Sieve](./manual_ta3_EN.md#ta3-gravel-sieve) - [TA3 Gravel Rinser](./manual_ta3_EN.md#ta3-gravel-rinser) - [TA3 Grinder](./manual_ta3_EN.md#ta3-grinder) - - [TA3 Liquid Sampler](./manual_ta3_EN.md#ta3-liquid-sampler) + - [TA3 Injector](./manual_ta3_EN.md#ta3-injector) - [Tools](./manual_ta3_EN.md#tools) - [Techage Info Tool](./manual_ta3_EN.md#techage-info-tool) - [TechAge Programmer](./manual_ta3_EN.md#techage-programmer) @@ -180,7 +181,8 @@ - [TA4 Tank](./manual_ta4_EN.md#ta4-tank) - [TA4 Pump](./manual_ta4_EN.md#ta4-pump) - [TA4 Furnace Heater](./manual_ta4_EN.md#ta4-furnace-heater) - - [TA4 water Pump](./manual_ta4_EN.md#ta4-water-pump) + - [TA4 Water Pump (deprecated)](./manual_ta4_EN.md#ta4-water-pump-(deprecated)) + - [TA4 Water Inlet](./manual_ta4_EN.md#ta4-water-inlet) - [TA4 Tube](./manual_ta4_EN.md#ta4-tube) - [TA4 Pusher](./manual_ta4_EN.md#ta4-pusher) - [TA4 Chest](./manual_ta4_EN.md#ta4-chest) @@ -192,4 +194,5 @@ - [TA4 Quarry](./manual_ta4_EN.md#ta4-quarry) - [TA4 Electronic Fab](./manual_ta4_EN.md#ta4-electronic-fab) - [TA4 Injector](./manual_ta4_EN.md#ta4-injector) - - [TA4 recycler](./manual_ta4_EN.md#ta4-recycler) \ No newline at end of file + - [TA4 Recycler](./manual_ta4_EN.md#ta4-recycler) + - [TA4 Laser](./manual_ta4_EN.md#ta4-laser) \ No newline at end of file diff --git a/techage/mod.conf b/techage/mod.conf index 517295f..ea1e24d 100644 --- a/techage/mod.conf +++ b/techage/mod.conf @@ -1,4 +1,4 @@ name = techage depends = default,doors,flowers,tubelib2,basic_materials,bucket,stairs,screwdriver,minecart,lcdlib,safer_lua -optional_depends = unified_inventory,wielded_light,unifieddyes,moreores,ethereal,mesecon,digtron +optional_depends = unified_inventory,wielded_light,unifieddyes,moreores,ethereal,mesecon,digtron,bakedclay description = Techage, go through 4 tech ages in search of wealth and power! diff --git a/techage/oil/explore.lua b/techage/oil/explore.lua index b854257..c4d4180 100644 --- a/techage/oil/explore.lua +++ b/techage/oil/explore.lua @@ -171,7 +171,7 @@ end local function marker(player_name, pos) local posC = {x = center(pos.x), y = pos.y, z = center(pos.z)} local pos1 = {x = posC.x - 2, y = posC.y - 2, z = posC.z - 2} - local pos2 = {x = posC.x + 2, y = posC.y + 5, z = posC.z + 2} + local pos2 = {x = posC.x + 2, y = posC.y + 7, z = posC.z + 2} techage.switch_region(player_name, pos1, pos2) end diff --git a/techage/oil/gasflare.lua b/techage/oil/gasflare.lua index 7df4fad..b8e0b0b 100644 --- a/techage/oil/gasflare.lua +++ b/techage/oil/gasflare.lua @@ -74,7 +74,7 @@ for idx,ratio in ipairs(lRatio) do end end, - use_texture_alpha = true, + use_texture_alpha = techage.BLEND, inventory_image = "techage_flame.png", paramtype = "light", light_source = 13, diff --git a/techage/oil/pumpjack.lua b/techage/oil/pumpjack.lua index 81200ab..eb70012 100644 --- a/techage/oil/pumpjack.lua +++ b/techage/oil/pumpjack.lua @@ -174,7 +174,17 @@ tiles.act = { local tubing = { on_recv_message = function(pos, src, topic, payload) - return CRD(pos).State:on_receive_message(pos, topic, payload) + if topic == "load" then + local storage_pos = M(pos):get_string("storage_pos") + if storage_pos ~= "" then + local amount, capa = techage.explore.get_oil_amount(P(storage_pos)) + if amount and capa and capa > 0 then + return techage.power.percent(capa or 0, amount or 0), amount or 0 + end + end + else + return CRD(pos).State:on_receive_message(pos, topic, payload) + end end, on_node_load = function(pos, node) CRD(pos).State:on_node_load(pos) diff --git a/techage/oil/tower.lua b/techage/oil/tower.lua index 9a0991e..cfade41 100644 --- a/techage/oil/tower.lua +++ b/techage/oil/tower.lua @@ -38,6 +38,7 @@ minetest.register_node("techage:oiltower1", { diggable = false, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {not_in_creative_inventory = 1}, is_ground_content = false, @@ -70,6 +71,7 @@ minetest.register_node("techage:oiltower2", { diggable = false, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {not_in_creative_inventory = 1}, is_ground_content = false, @@ -102,6 +104,7 @@ minetest.register_node("techage:oiltower3", { diggable = false, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {not_in_creative_inventory = 1}, is_ground_content = false, @@ -134,6 +137,7 @@ minetest.register_node("techage:oiltower4", { diggable = false, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {not_in_creative_inventory = 1}, is_ground_content = false, @@ -156,6 +160,7 @@ minetest.register_node("techage:oiltower5", { diggable = false, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {not_in_creative_inventory = 1}, is_ground_content = false, @@ -169,6 +174,7 @@ minetest.register_node("techage:oil_drillbit", { wield_image = "techage_oil_drillbit_inv.png", visual_scale = 1, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, groups = {cracky = 1}, is_ground_content = false, @@ -182,6 +188,7 @@ minetest.register_node("techage:oil_drillbit2", { wield_image = "techage_oil_drillbit_inv.png", visual_scale = 1, paramtype = "light", + use_texture_alpha = techage.CLIP, drop = "", diggable = false, sunlight_propagates = true, diff --git a/techage/power/drive_axle.lua b/techage/power/drive_axle.lua index ba41084..a1a0105 100644 --- a/techage/power/drive_axle.lua +++ b/techage/power/drive_axle.lua @@ -67,6 +67,7 @@ minetest.register_node("techage:axle", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {crumbly = 3, cracky = 3, snappy = 3}, @@ -160,6 +161,7 @@ minetest.register_node("techage:axle_on", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, diggable = false, diff --git a/techage/power/electric_cable.lua b/techage/power/electric_cable.lua index 472f3b2..3799a62 100644 --- a/techage/power/electric_cable.lua +++ b/techage/power/electric_cable.lua @@ -117,6 +117,7 @@ minetest.register_node("techage:electric_cableS", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, techage_trowel = 1}, @@ -153,6 +154,7 @@ minetest.register_node("techage:electric_cableA", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, diff --git a/techage/power/junction.lua b/techage/power/junction.lua index 9da7fdb..e14c3ef 100644 --- a/techage/power/junction.lua +++ b/techage/power/junction.lua @@ -52,6 +52,7 @@ end -- 'node' is the node definition with tiles, callback functions, and so on -- 'index' number for the inventory node (default 0) function techage.register_junction(name, size, boxes, tlib2, node, index) + local names = {} for idx = 0,63 do local ndef = table.copy(node) if idx == (index or 0) then @@ -65,6 +66,7 @@ function techage.register_junction(name, size, boxes, tlib2, node, index) ndef.paramtype2 = "facedir" ndef.on_rotate = screwdriver.disallow ndef.paramtype = "light" + ndef.use_texture_alpha = techage.CLIP ndef.sunlight_propagates = true ndef.is_ground_content = false ndef.drop = name..(index or "0") @@ -72,21 +74,41 @@ function techage.register_junction(name, size, boxes, tlib2, node, index) tlib2:add_secondary_node_names({name..idx}) -- for the case that 'tlib2.force_to_use_tubes' is set tlib2:add_special_node_names({name..idx}) + names[#names + 1] = name..idx end + return names end -function techage.junction_type(pos, network) +local SideToDir = {B=1, R=2, F=3, L=4} +local function dir_to_dir2(dir, param2) + if param2 == 0 then + return dir + elseif param2 == 1 then + return ({4,1,2,3,5,6})[dir] + elseif param2 == 2 then + return ({3,4,1,2,5,6})[dir] + elseif param2 == 3 then + return ({2,3,4,1,5,6})[dir] + end + return dir +end + +function techage.junction_type(pos, network, default_side, param2) local val = 0 + if default_side then + val = setbit(val, bit(SideToDir[default_side])) + end for dir = 1,6 do + local dir2 = dir_to_dir2(dir, param2) if network.force_to_use_tubes then if network:friendly_primary_node(pos, dir) then - val = setbit(val, bit(dir)) + val = setbit(val, bit(dir2)) elseif network:is_special_node(pos, dir) then - val = setbit(val, bit(dir)) + val = setbit(val, bit(dir2)) end else if network:connected(pos, dir) then - val = setbit(val, bit(dir)) + val = setbit(val, bit(dir2)) end end end diff --git a/techage/power/laser.lua b/techage/power/laser.lua new file mode 100644 index 0000000..7a0b359 --- /dev/null +++ b/techage/power/laser.lua @@ -0,0 +1,146 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2021 Joachim Stolberg + + GPL v3 + See LICENSE.txt for more information + + TA4 Laser beam emitter and receiver + +]]-- + +-- for lazy programmers +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos +local M = minetest.get_meta +local S = techage.S + +local Cable = techage.ElectricCable +local power = techage.power +local networks = techage.networks + +minetest.register_node("techage:ta4_laser_emitter", { + description = S("TA4 Laser Beam Emitter"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta4.png^techage_frame_ta4_top.png", + "techage_filling_ta4.png^techage_frame_ta4.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser_hole.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_hole_electric.png", + }, + + after_place_node = function(pos, placer) + local tube_dir = networks.side_to_outdir(pos, "F") + Cable:prepare_pairing(pos, tube_dir, "") + Cable:after_place_node(pos, {tube_dir}) + + local res, pos1, pos2 = techage.renew_laser(pos, true) + if pos1 then + local node = techage.get_node_lvm(pos2) + if node.name == "techage:ta4_laser_receiver" then + Cable:pairing(pos2, "laser") + Cable:pairing(pos, "laser") + else + minetest.chat_send_player(placer:get_player_name(), + S("Valid destination positions:") .. " " .. + P2S(pos1) .. " " .. S("to") .. " " .. P2S(pos2)) + end + else + minetest.chat_send_player(placer:get_player_name(), S("Laser beam error!")) + end + minetest.get_node_timer(pos):start(2) + end, + + on_timer = function(pos, elapsed) + local res, pos1, pos2 = techage.renew_laser(pos) + if pos1 then + local node = techage.get_node_lvm(pos2) + if node.name == "techage:ta4_laser_receiver" then + Cable:pairing(pos2, "laser") + Cable:pairing(pos, "laser") + else + local metadata = M(pos):to_table() + Cable:stop_pairing(pos, metadata, "") + local tube_dir = tonumber(metadata.fields.tube_dir or 0) + Cable:after_dig_node(pos, {tube_dir}) + end + elseif not res then + techage.del_laser(pos) + local metadata = M(pos):to_table() + Cable:stop_pairing(pos, metadata, "") + local tube_dir = tonumber(metadata.fields.tube_dir or 0) + Cable:after_dig_node(pos, {tube_dir}) + end + return true + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + techage.del_laser(pos) + Cable:stop_pairing(pos, oldmetadata, "") + local tube_dir = tonumber(oldmetadata.fields.tube_dir or 0) + Cable:after_dig_node(pos, {tube_dir}) + end, + + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("techage:ta4_laser_receiver", { + description = S("TA4 Laser Beam Receiver"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta4.png^techage_frame_ta4_top.png", + "techage_filling_ta4.png^techage_frame_ta4.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_laser_hole.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_hole_electric.png", + }, + + after_place_node = function(pos, placer) + local tube_dir = networks.side_to_outdir(pos, "F") + Cable:prepare_pairing(pos, tube_dir, "") + Cable:after_place_node(pos, {tube_dir}) + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + Cable:stop_pairing(pos, oldmetadata, "") + local tube_dir = tonumber(oldmetadata.fields.tube_dir or 0) + Cable:after_dig_node(pos, {tube_dir}) + end, + + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + +Cable:add_secondary_node_names({"techage:ta4_laser_emitter", "techage:ta4_laser_receiver"}) +Cable:set_valid_sides("techage:ta4_laser_emitter", {"F"}) +Cable:set_valid_sides("techage:ta4_laser_receiver", {"F"}) + +minetest.register_craft({ + output = "techage:ta4_laser_emitter", + recipe = { + {"techage:ta4_carbon_fiber", "dye:blue", "techage:ta4_carbon_fiber"}, + {"techage:electric_cableS", "basic_materials:energy_crystal_simple", "techage:ta4_leds"}, + {"default:steel_ingot", "techage:ta4_wlanchip", "default:steel_ingot"}, + }, +}) + +minetest.register_craft({ + output = "techage:ta4_laser_receiver", + recipe = { + {"techage:ta4_carbon_fiber", "dye:blue", "techage:ta4_carbon_fiber"}, + {"techage:electric_cableS", "basic_materials:gold_wire", "default:obsidian_glass"}, + {"default:steel_ingot", "techage:ta4_wlanchip", "default:steel_ingot"}, + }, +}) + diff --git a/techage/power/power_line.lua b/techage/power/power_line.lua index 02c6d32..1b63162 100644 --- a/techage/power/power_line.lua +++ b/techage/power/power_line.lua @@ -64,6 +64,7 @@ minetest.register_node("techage:power_line", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, drop = "techage:power_lineS", @@ -105,6 +106,7 @@ minetest.register_node("techage:power_lineS", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, drop = "techage:power_lineS", @@ -168,6 +170,7 @@ minetest.register_node("techage:power_lineA", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, drop = "techage:power_lineS", @@ -222,6 +225,7 @@ minetest.register_node("techage:power_pole2", { on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky=2, crumbly=2, choppy=2}, @@ -267,6 +271,7 @@ minetest.register_node("techage:power_pole", { on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky=2, crumbly=2, choppy=2}, @@ -317,6 +322,7 @@ minetest.register_node("techage:power_pole_conn", { drop = "techage:power_pole", on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky=2, crumbly=2, choppy=2, not_in_creative_inventory = 1}, @@ -344,6 +350,7 @@ minetest.register_node("techage:power_pole3", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky=2, crumbly=2, choppy=2}, diff --git a/techage/power/power_terminal.lua b/techage/power/power_terminal.lua index a20852c..50b24b6 100644 --- a/techage/power/power_terminal.lua +++ b/techage/power/power_terminal.lua @@ -209,6 +209,7 @@ minetest.register_node("techage:power_terminal", { paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, on_rotate = screwdriver.disallow, sunlight_propagates = true, is_ground_content = false, diff --git a/techage/power/power_terminal2.lua b/techage/power/power_terminal2.lua index 2c5d691..cbd142d 100644 --- a/techage/power/power_terminal2.lua +++ b/techage/power/power_terminal2.lua @@ -28,6 +28,16 @@ local STOPPED = techage.power.STOPPED local NOPOWER = techage.power.NOPOWER local RUNNING = techage.power.RUNNING +local HELP = [[Commands +help print this text +cls clear screen +gen1 print all cat. 1 generators +gen2 print all cat. 2 generators +con2 print all cat. 2 consumers +num print number of network blocks +pow print provided and needed power]] + + local Generators = { S("Power station"), S("Tiny generator"), @@ -35,7 +45,8 @@ local Generators = { S("Wind turbine"), S("Accu Box"), S("Energy storage"), - S("Fuel cell"), + S("Fuel cell cat. 1"), + S("Fuel cell cat. 2"), S("Electrolyzer"), S("TA2 Generator"), } @@ -43,7 +54,8 @@ local Generators = { local Storage = { [S("Accu Box")] = true, [S("Energy storage")] = true, - [S("Fuel cell")] = true, + [S("Fuel cell cat. 1")] = true, + [S("Fuel cell cat. 2")] = true, [S("Electrolyzer")] = true, } @@ -54,8 +66,9 @@ local GeneratorPerformances = { 70, -- S("Wind turbine") 10, -- S("Accu Box") 60, -- S("Energy storage") - 25, -- S("Fuel cell") - 30, -- S("Electrolyzer") + 33, -- S("Fuel cell cat. 1") + 34, -- S("Fuel cell cat. 2") + 35, -- S("Electrolyzer") 24, -- S("TA2 Generator") } @@ -65,10 +78,16 @@ local GeneratorPerformances = { local Gentypes = table.concat(Generators, ",") local Gentype2Idx = {} local Gentype2Maxvalue = {} +local Gentype = {} for idx,name in ipairs(Generators) do Gentype2Idx[name] = idx Gentype2Maxvalue[name] = GeneratorPerformances[idx] + Gentype[GeneratorPerformances[idx]] = name +end + +local function short_node_name(nominal) + return Gentype[nominal or 1] or "unknown" end local function generator_data(gen_tbl, nominal) @@ -94,6 +113,40 @@ local function generator_data(gen_tbl, nominal) return pow_max, pow_curr, num_nodes end +local function get_generator_data(gen) + local nvm = techage.get_nvm(gen.pos) + local pow_max = 0 + local pow_curr = 0 + if nvm.ele1 and nvm.ele1.gstate and nvm.ele1.galive and nvm.ele1.given then + if nvm.ele1.gstate == RUNNING then + if nvm.ele1.curr_power and nvm.ele1.curr_power > 0 then + pow_max = nvm.ele1.curr_power + else + pow_max = gen.nominal + end + if nvm.ele1.galive > 0 and nvm.ele1.given > 0 then + pow_curr = nvm.ele1.given + end + end + end + return pow_curr, pow_max +end + +local function get_consumer_data(gen) + local nvm = techage.get_nvm(gen.pos) + local pow_max = 0 + local pow_curr = 0 + if nvm.ele1 and nvm.ele1.cstate and nvm.ele1.calive and nvm.ele1.taken then + if nvm.ele1.cstate == RUNNING then + pow_max = gen.nominal + if nvm.ele1.calive > 0 and nvm.ele1.taken > 0 then + pow_curr = nvm.ele1.taken + end + end + end + return pow_curr, pow_max +end + local function consumer_data(gen_tbl, nominal) local pow_max = 0 local pow_curr = 0 @@ -125,7 +178,7 @@ local function storage_load(gen_tbl, nominal) if gen.nominal == nominal then local ndef = techage.NodeDef[techage.get_node_lvm(gen.pos).name] if ndef and ndef.on_recv_message then - local resp = ndef.on_recv_message(gen.pos, "0", "load") + local resp, _ = ndef.on_recv_message(gen.pos, "0", "load") if type(resp) == "number" then load_curr = load_curr + resp num = num + 1 @@ -152,11 +205,16 @@ local function calc_network_data_type(pos, nvm, gentype) pow_max2, pow_curr2, num_nodes2 = consumer_data(netw.con2, nominal) pow_stored1 = storage_load(netw.con2, Gentype2Maxvalue[gentype]).." %" pow_stored2 = pow_stored1 - elseif gentype == S("Fuel cell") then + elseif gentype == S("Fuel cell cat. 2") then pow_max1, pow_curr1, num_nodes1 = generator_data(netw.gen2, nominal) pow_max2, pow_curr2, num_nodes2 = 0, 0, 0 pow_stored1 = storage_load(netw.gen2, Gentype2Maxvalue[gentype]).." %" pow_stored2 = "-" + elseif gentype == S("Fuel cell cat. 1") then + pow_max1, pow_curr1, num_nodes1 = generator_data(netw.gen1, nominal) + pow_max2, pow_curr2, num_nodes2 = 0, 0, 0 + pow_stored1 = storage_load(netw.gen1, Gentype2Maxvalue[gentype]).." %" + pow_stored2 = "-" elseif gentype == S("Electrolyzer") then pow_max1, pow_curr1, num_nodes1 = 0, 0, 0 pow_max2, pow_curr2, num_nodes2 = consumer_data(netw.con2, nominal) @@ -231,7 +289,7 @@ local function formspec_type(pos, nvm) "button[0,2.4;5,1;set;"..S("Store").."]" end -local function formspec(pos, nvm) +local function formspec1(pos, nvm) local gentype = nvm.gentype or S("Power station") local netw, gen1, gen2 = calc_network_data_type(pos, nvm, gentype) local _, sum1, sum2 = calc_network_data_total(pos, nvm) @@ -239,49 +297,181 @@ local function formspec(pos, nvm) local star = netw.prop == 1 and "*" or "" local state = get_state(netw) - return "size[9,7]".. + return "size[11,9]".. + "tabheader[0,0;tab;status,console;1;;true]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "box[0,-0.1;8.8,0.5;#c6e8ff]".. - "label[3.5,-0.1;"..minetest.colorize( "#000000", S("Network Data")).."]".. - "label[8.5,-0.1;"..minetest.colorize( "#000000", star).."]".. + "box[0,-0.1;10.8,0.5;#c6e8ff]".. + "label[4.5,-0.1;"..minetest.colorize( "#000000", S("Network Data")).."]".. + "label[10.5,-0.1;"..minetest.colorize( "#000000", star).."]".. "style_type[button;bgcolor=#395c74]".. "button[0,0.7;3,1;config;"..S("Type").."]".. - "box[0,1.6;8.8,0.4;#c6e8ff]".. - "box[0,2.15;8.8,0.4;#395c74]".. - "box[0,2.65;8.8,0.4;#395c74]".. - "box[0,3.15;8.8,0.4;#395c74]".. - "box[0,3.65;8.8,0.4;#395c74]".. + "box[0,1.6;10.8,0.4;#c6e8ff]".. + "box[0,2.15;10.8,0.4;#395c74]".. + "box[0,2.65;10.8,0.4;#395c74]".. + "box[0,3.15;10.8,0.4;#395c74]".. + "box[0,3.65;10.8,0.4;#395c74]".. "label[0.1,1.55;"..minetest.colorize( "#000000", gentype).."]".. - "label[3.7,1.55;"..minetest.colorize( "#000000", S("Output")).."]".. - "label[6.2,1.55;"..minetest.colorize( "#000000", S("Intake")).."]".. + "label[5.7,1.55;"..minetest.colorize( "#000000", S("Output")).."]".. + "label[8.2,1.55;"..minetest.colorize( "#000000", S("Intake")).."]".. "label[0.1,2.1;"..S("Number blocks:").."]".. "label[0.1,2.6;"..S("Maximum power:").."]".. "label[0.1,3.1;"..S("Current power:").."]".. "label[0.1,3.6;"..S("Energy stored:").."]".. - column(3.7, 2.1, gen1).. - column(6.2, 2.1, gen2).. + column(5.7, 2.1, gen1).. + column(8.2, 2.1, gen2).. --"box[0,5.3;8.8,0.4;#c6e8ff]".. - "box[0,4.5;8.8,0.4;#c6e8ff]".. - "box[0,5.05;8.8,0.4;#395c74]".. - "box[0,5.55;8.8,0.4;#395c74]".. - "box[0,6.05;8.8,0.4;#395c74]".. + "box[0,4.5;10.8,0.4;#c6e8ff]".. + "box[0,5.05;10.8,0.4;#395c74]".. + "box[0,5.55;10.8,0.4;#395c74]".. + "box[0,6.05;10.8,0.4;#395c74]".. "label[0.1,4.45;"..minetest.colorize( "#000000", S("Power grid total")).."]".. - "label[3.7,4.45;"..minetest.colorize( "#000000", S("Generators")).."]".. - "label[6.2,4.45;"..minetest.colorize( "#000000", S("Storage systems")).."]".. + "label[5.7,4.45;"..minetest.colorize( "#000000", S("Generators")).."]".. + "label[8.2,4.45;"..minetest.colorize( "#000000", S("Storage systems")).."]".. "label[0.1,5.0;"..S("Number blocks:").."]".. "label[0.1,5.5;"..S("Maximum power:").."]".. "label[0.1,6.0;"..S("Current power:").."]".. - column(3.7, 5.0, sum1).. - column(6.2, 5.0, sum2).. - "box[0,6.75;8.8,0.4;#000000]".. - "label[0.1,6.7;"..state.."]" + column(5.7, 5.0, sum1).. + column(8.2, 5.0, sum2).. + "box[0,7.75;10.8,0.4;#000000]".. + "label[0.1,7.7;"..state.."]" end +local function formspec2(pos, mem) + local meta = M(pos) + local output = meta:get_string("output") + local command = mem.cmnd or "help" + output = minetest.formspec_escape(output) + output = output:gsub("\n", ",") + + return "size[11,9]".. + "tabheader[0,0;tab;status,console;2;;true]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "box[0,-0.1;10.8,0.5;#c6e8ff]".. + "label[4.5,-0.1;"..minetest.colorize( "#000000", S("Network Data")).."]".. + "style_type[table,field;font=mono]".. + "table[0,0.5;10.8,7.8;output;"..output..";200]".. + "field[0.4,8.7;8.6,1;cmnd;;"..command.."]" .. + "field_close_on_enter[cmnd;false]".. + "button[8.9,8.4;2,1;enter;"..S("Enter").."]" +end + +local function generators(pos, gen_tbl) + local tbl = {} + for _, item in ipairs(gen_tbl) do + if item and item.pos then + local node = techage.get_node_lvm(item.pos) + local ndef = minetest.registered_nodes[node.name] + local name = short_node_name(item.nominal) + local spos = P2S(item.pos) + local pow_curr, pow_max = get_generator_data(item, ndef) + if Storage[name] then + local load_percent = 0 + local tdef = techage.NodeDef[node.name] + if tdef and tdef.on_recv_message then + load_percent = tdef.on_recv_message(item.pos, "0", "load") or 0 + end + local s = string.format("%-16s %s = %u/%u ku (%u %%)", + spos, name, pow_curr, pow_max, load_percent) + tbl[#tbl + 1] = s + else + local s = string.format("%-16s %s = %u/%u ku", spos, name, pow_curr, pow_max) + tbl[#tbl + 1] = s + end + end + end + return table.concat(tbl, "\n") +end + +local function consumers(pos, gen_tbl) + local tbl = {} + for _, item in ipairs(gen_tbl) do + if item and item.pos then + local node = techage.get_node_lvm(item.pos) + local ndef = minetest.registered_nodes[node.name] + local name = short_node_name(item.nominal) + local spos = P2S(item.pos) + local pow_curr, pow_max = get_consumer_data(item, ndef) + if Storage[name] then + local load_percent = 0 + local tdef = techage.NodeDef[node.name] + if tdef and tdef.on_recv_message then + load_percent = tdef.on_recv_message(item.pos, "0", "load") or 0 + end + local s = string.format("%-16s %s = %u/%u ku (%u %%)", + spos, name, pow_curr, pow_max, load_percent) + tbl[#tbl + 1] = s + else + local s = string.format("%-16s %s = %u/%u ku", spos, name, pow_curr, pow_max) + tbl[#tbl + 1] = s + end + end + end + return table.concat(tbl, "\n") +end + +local function number_nodes(pos, netw) + return + "num. generators cat. 1: " .. #(netw.gen1 or {}) .. "\n" .. + "num. generators cat. 2: " .. #(netw.gen2 or {}) .. "\n" .. + "num. consumers cat. 1: " .. #(netw.con1 or {}) .. "\n" .. + "num. consumers cat. 2: " .. #(netw.con2 or {}) +end + +local function power_network(pos, netw) + return + "pow. generators cat. 1: " .. (netw.available1 or 0) .. " ku\n" .. + "pow. generators cat. 2: " .. (netw.available2 or 0) .. " ku\n" .. + "pow. consumers cat. 1: " .. (netw.needed1 or 0) .. " ku\n" .. + "pow. consumers cat. 2: " .. (netw.needed2 or 0) .. " ku" +end + + +local function output(pos, command, text) + local meta = M(pos) + text = meta:get_string("output") .. "\n$ " .. command .. "\n" .. (text or "") + text = text:sub(-2000,-1) + meta:set_string("output", text) +end + +local function command(pos, nvm, command) + local meta = M(pos) + + if command then + command = command:sub(1,80) + command = string.trim(command) + + if command == "cls" then + meta:set_string("output", "") + elseif command == "help" then + output(pos, command, HELP) + elseif command == "gen1" then + local netw = techage.networks.has_network("ele1", nvm.ele1 and nvm.ele1.netID) or {} + output(pos, command, generators(pos, netw.gen1 or {})) + elseif command == "gen2" then + local netw = techage.networks.has_network("ele1", nvm.ele1 and nvm.ele1.netID) or {} + output(pos, command, generators(pos, netw.gen2 or {})) + elseif command == "con2" then + local netw = techage.networks.has_network("ele1", nvm.ele1 and nvm.ele1.netID) or {} + output(pos, command, consumers(pos, netw.con2 or {})) + elseif command == "num" then + local netw = techage.networks.has_network("ele1", nvm.ele1 and nvm.ele1.netID) or {} + output(pos, command, number_nodes(pos, netw)) + elseif command == "pow" then + local netw = techage.networks.has_network("ele1", nvm.ele1 and nvm.ele1.netID) or {} + output(pos, command, power_network(pos, netw)) + elseif command ~= "" then + output(pos, command, "") + end + end +end + minetest.register_node("techage:ta3_power_terminal", { description = S("TA3 Power Terminal"), inventory_image = "techage_power_terminal_front.png", @@ -305,7 +495,7 @@ minetest.register_node("techage:ta3_power_terminal", { local nvm = techage.get_nvm(pos) M(pos):set_int("outdir", networks.side_to_outdir(pos, "B")) Cable:after_place_node(pos) - M(pos):set_string("formspec", formspec(pos, nvm)) + M(pos):set_string("formspec", formspec1(pos, nvm)) end, after_dig_node = function(pos) Cable:after_dig_node(pos) @@ -315,15 +505,18 @@ minetest.register_node("techage:ta3_power_terminal", { power.update_network(pos, outdir, tlib2) end, on_rightclick = function(pos, node, clicker) - techage.set_activeformspec(pos, clicker) - minetest.get_node_timer(pos):start(CYCLE_TIME) - local nvm = techage.get_nvm(pos) - M(pos):set_string("formspec", formspec(pos, nvm)) + local mem = techage.get_mem(pos) + if mem.active_formspec == 2 then + M(pos):set_string("formspec", formspec2(pos, mem)) + else + local nvm = techage.get_nvm(pos) + M(pos):set_string("formspec", formspec1(pos, nvm)) + end end, on_timer = function(pos, elapsed) local nvm = techage.get_nvm(pos) if techage.is_activeformspec(pos) then - M(pos):set_string("formspec", formspec(pos, nvm)) + M(pos):set_string("formspec", formspec1(pos, nvm)) end return true end, @@ -333,15 +526,31 @@ minetest.register_node("techage:ta3_power_terminal", { return end local nvm = techage.get_nvm(pos) + local mem = techage.get_mem(pos) - if fields.config then + if fields.key_enter_field or fields.enter then + command(pos, nvm, fields.cmnd) + mem.cmnd = "" + M(pos):set_string("formspec", formspec2(pos, mem)) + mem.cmnd = fields.cmnd + elseif fields.config then techage.reset_activeformspec(pos, player) M(pos):set_string("formspec", formspec_type(pos, nvm)) elseif fields.set then nvm.gentype = fields.gentype nvm.gentype_idx = Gentype2Idx[fields.gentype] or 1 techage.set_activeformspec(pos, player) - M(pos):set_string("formspec", formspec(pos, nvm)) + M(pos):set_string("formspec", formspec1(pos, nvm)) + elseif fields.tab == "1" then + M(pos):set_string("formspec", formspec1(pos, nvm)) + techage.set_activeformspec(pos, player) + mem.active_formspec = 1 + elseif fields.tab == "2" then + M(pos):set_string("formspec", formspec2(pos, mem)) + techage.reset_activeformspec(pos, player) + mem.active_formspec = 2 + elseif fields.key_up and mem.cmnd then + M(pos):set_string("formspec", formspec2(pos, mem)) end end, @@ -354,6 +563,7 @@ minetest.register_node("techage:ta3_power_terminal", { paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = techage.CLIP, on_rotate = screwdriver.disallow, sunlight_propagates = true, is_ground_content = false, diff --git a/techage/power/powerswitch.lua b/techage/power/powerswitch.lua index e1cd5ce..0b55363 100644 --- a/techage/power/powerswitch.lua +++ b/techage/power/powerswitch.lua @@ -114,6 +114,7 @@ minetest.register_node("techage:powerswitch", { on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, paramtype2 = "wallmounted", groups = {choppy=2, cracky=2, crumbly=2}, @@ -145,6 +146,7 @@ minetest.register_node("techage:powerswitch_on", { drop = "techage:powerswitch", on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, paramtype2 = "wallmounted", groups = {choppy=2, cracky=2, crumbly=2, not_in_creative_inventory = 1}, @@ -184,6 +186,7 @@ minetest.register_node("techage:powerswitchsmall", { on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, paramtype2 = "wallmounted", groups = {choppy=2, cracky=2, crumbly=2}, @@ -215,6 +218,7 @@ minetest.register_node("techage:powerswitchsmall_on", { drop = "techage:powerswitchsmall", on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, paramtype2 = "wallmounted", groups = {choppy=2, cracky=2, crumbly=2, not_in_creative_inventory = 1}, diff --git a/techage/power/powerswitchbox.lua b/techage/power/powerswitchbox.lua index f29f0e0..1d63318 100644 --- a/techage/power/powerswitchbox.lua +++ b/techage/power/powerswitchbox.lua @@ -54,6 +54,7 @@ minetest.register_node("techage:powerswitch_box", { end, paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, on_rotate = screwdriver.disallow, -- important! paramtype2 = "facedir", diff --git a/techage/power/steam_pipe.lua b/techage/power/steam_pipe.lua index d834ead..e667255 100644 --- a/techage/power/steam_pipe.lua +++ b/techage/power/steam_pipe.lua @@ -63,6 +63,7 @@ minetest.register_node("techage:steam_pipeS", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {crumbly=3, cracky=3, snappy=3}, @@ -95,6 +96,7 @@ minetest.register_node("techage:steam_pipeA", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {crumbly=3, cracky=3, snappy=3, not_in_creative_inventory=1}, diff --git a/techage/power/ta4_cable.lua b/techage/power/ta4_cable.lua index 1e169bc..cbd2760 100644 --- a/techage/power/ta4_cable.lua +++ b/techage/power/ta4_cable.lua @@ -27,10 +27,16 @@ local Cable = tubelib2.Tube:new({ max_tube_length = ELE2_MAX_CABLE_LENGHT, show_infotext = false, tube_type = "ele2", - primary_node_names = {"techage:ta4_power_cableS", "techage:ta4_power_cableA"}, + primary_node_names = {"techage:ta4_power_cableS", "techage:ta4_power_cableA", + "techage:ta4_cable_wall_entry"}, secondary_node_names = {}, after_place_tube = function(pos, param2, tube_type, num_tubes) - minetest.swap_node(pos, {name = "techage:ta4_power_cable"..tube_type, param2 = param2}) + local name = minetest.get_node(pos).name + if name == "techage:ta4_cable_wall_entry" then + minetest.swap_node(pos, {name = "techage:ta4_cable_wall_entry", param2 = param2}) + else + minetest.swap_node(pos, {name = "techage:ta4_power_cable"..tube_type, param2 = param2}) + end end, }) @@ -76,6 +82,7 @@ minetest.register_node("techage:ta4_power_cableS", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3}, @@ -109,6 +116,7 @@ minetest.register_node("techage:ta4_power_cableA", { }, on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, not_in_creative_inventory = 1}, @@ -139,7 +147,9 @@ minetest.register_node("techage:ta4_power_box", { connect_back = {{-1/16, -1/16, 0, 1/16, 1/16, 1/2}}, connect_front = {{-1/16, -1/16, -1/2, 1/16, 1/16, 0}}, }, - connects_to = {"techage:ta4_power_cableA", "techage:ta4_power_cableS"}, + connects_to = {"techage:ta4_power_cableA", "techage:ta4_power_cableS", + "techage:ta4_solar_inverter", "techage:ta4_solar_carrier", + "techage:ta4_solar_carrierB", "techage:ta4_cable_wall_entry"}, after_place_node = function(pos, placer, itemstack, pointed_thing) Cable:after_place_node(pos) @@ -159,6 +169,7 @@ minetest.register_node("techage:ta4_power_box", { on_rotate = screwdriver.disallow, -- important! paramtype = "light", + use_texture_alpha = techage.CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky=2, crumbly=2, choppy=2}, diff --git a/techage/power/ta4_cable_wall_entry.lua b/techage/power/ta4_cable_wall_entry.lua new file mode 100644 index 0000000..d83d3b7 --- /dev/null +++ b/techage/power/ta4_cable_wall_entry.lua @@ -0,0 +1,57 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2020 Joachim Stolberg + + AGPL v3 + See LICENSE.txt for more information + + TA4 cable wall entry + +]]-- + +local S = techage.S + +local TA4_Cable = techage.TA4_Cable + +minetest.register_node("techage:ta4_cable_wall_entry", { + description = S("TA4 Cable Wall Entry"), + tiles = { + -- up, down, right, left, back, front + "basic_materials_concrete_block.png", + "basic_materials_concrete_block.png", + "basic_materials_concrete_block.png", + "basic_materials_concrete_block.png", + "basic_materials_concrete_block.png^techage_ta4_cable_hole.png", + "basic_materials_concrete_block.png^techage_ta4_cable_hole.png", + }, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + if not TA4_Cable:after_place_tube(pos, placer, pointed_thing) then + minetest.remove_node(pos) + return true + end + return false + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + TA4_Cable:after_dig_tube(pos, oldnode) + end, + + paramtype2 = "facedir", -- important! + on_rotate = screwdriver.disallow, -- important! + groups = {crumbly = 2, cracky = 2, snappy = 2}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_craft({ + output = "techage:ta4_cable_wall_entry", + recipe = { + {"", "techage:ta4_power_cableS", ""}, + {"", "basic_materials:concrete_block", ""}, + {"", "",""}, + }, +}) diff --git a/techage/solar/inverter.lua b/techage/solar/inverter.lua index 9b877f8..9930e31 100644 --- a/techage/solar/inverter.lua +++ b/techage/solar/inverter.lua @@ -119,6 +119,7 @@ local function node_timer(pos, elapsed) if techage.is_activeformspec(pos) then M(pos):set_string("formspec", formspec(State, pos, nvm)) end + State:trigger_state(pos, nvm) return true end @@ -187,6 +188,7 @@ minetest.register_node("techage:ta4_solar_inverter", { sides = {R = 1}, ntype = "gen1", nominal = PWR_PERF, + regenerative = true, }, ele2 = { sides = {L = 1}, diff --git a/techage/solar/minicell.lua b/techage/solar/minicell.lua index 0f6c10b..bd3713f 100644 --- a/techage/solar/minicell.lua +++ b/techage/solar/minicell.lua @@ -109,6 +109,7 @@ minetest.register_node("techage:ta4_solar_minicell", { paramtype2 = "facedir", groups = {cracky=2, crumbly=2, choppy=2}, is_ground_content = false, + use_texture_alpha = techage.CLIP, after_place_node = after_place_node, after_dig_node = after_dig_node, diff --git a/techage/solar/solarcell.lua b/techage/solar/solarcell.lua index ebb00f8..e303f90 100644 --- a/techage/solar/solarcell.lua +++ b/techage/solar/solarcell.lua @@ -133,6 +133,7 @@ minetest.register_node("techage:ta4_solar_module", { return S("power").." = "..power..", "..S("light").." = "..light end, paramtype = "light", + use_texture_alpha = techage.CLIP, paramtype2 = "facedir", on_rotate = screwdriver.disallow, groups = {cracky=2, crumbly=2, choppy=2}, @@ -172,6 +173,7 @@ minetest.register_node("techage:ta4_solar_carrier", { networks = net_def1, paramtype = "light", + use_texture_alpha = techage.CLIP, paramtype2 = "facedir", on_rotate = screwdriver.disallow, groups = {cracky=2, crumbly=2, choppy=2}, @@ -211,6 +213,7 @@ minetest.register_node("techage:ta4_solar_carrierB", { networks = net_def2, paramtype = "light", + use_texture_alpha = techage.CLIP, paramtype2 = "facedir", on_rotate = screwdriver.disallow, groups = {cracky=2, crumbly=2, choppy=2}, @@ -239,6 +242,7 @@ minetest.register_node("techage:ta4_solar_carrierT", { }, paramtype = "light", + use_texture_alpha = techage.CLIP, paramtype2 = "facedir", groups = {cracky=2, crumbly=2, choppy=2}, is_ground_content = false, diff --git a/techage/ta3_power/akkubox.lua b/techage/ta3_power/akkubox.lua index f5efc7a..3d7b662 100644 --- a/techage/ta3_power/akkubox.lua +++ b/techage/ta3_power/akkubox.lua @@ -167,7 +167,7 @@ minetest.register_node("techage:ta3_akku", { description = S("TA3 Accu Box"), tiles = { -- up, down, right, left, back, front - "techage_filling_ta3.png^techage_frame_ta3_top.png", + "techage_filling_ta3.png^techage_frame_ta3_top.png^techage_appl_arrow.png", "techage_filling_ta3.png^techage_frame_ta3.png", "techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_hole_electric.png", "techage_filling_ta3.png^techage_frame_ta3.png^techage_appl_source.png", diff --git a/techage/textures/shrink.py b/techage/textures/shrink.py index 45b41a4..0cf29b8 100644 --- a/techage/textures/shrink.py +++ b/techage/textures/shrink.py @@ -5,7 +5,7 @@ print(">>> Convert") for filename in os.listdir("./"): if fnmatch.fnmatch(filename, "*.png"): print(filename) - os.system("pngquant --skip-if-larger --quality=60-80 --strip --output ./%s.new ./%s" % (filename, filename)) + os.system("pngquant --skip-if-larger --quality=80 --strip --output ./%s.new ./%s" % (filename, filename)) print("\n>>> Copy") for filename in os.listdir("./"): diff --git a/techage/textures/shrink.sh b/techage/textures/shrink.sh new file mode 100755 index 0000000..36036c5 --- /dev/null +++ b/techage/textures/shrink.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force \ No newline at end of file diff --git a/techage/textures/techage_aluminum_inv.png b/techage/textures/techage_aluminum_inv.png index 1127c253dfce652aff0fcd87da5e0f9027105673..26f5e620ab1cecfb2ddff9ba152915d158ce7daf 100644 GIT binary patch delta 1172 zcmV;F1Z(@t3CanOMt_Qrm04Y10s;b-o1=Jth;4FrrKzniG&Mx;mZSgx1W-vtK~zY` zeN?-W+c*%M6Cbf3h}QT&w%t>v~|3Ed9=%}`cZ70yVgeOwhTdGoyP)<5=pBrM=w2xRrH)zAI9 zkZ}DFk>H9F34hSw)*H4FJUPVNLKzFbJMP~th`-1&t`325@(v~h;zvSi4m}=K+wtQY zkPy6h|FGX5ICyNd?JhUM#|-AlSUv+hrE|v1?2fms+uTIP;5b0tkvb7z_xCgTvz0mL zl7a%TP-lg6LbuI~MV7u9pa6j1_0|#ctelakkoU4gN`KQ?qrgz&)`#DN;Qha5@q6`mtrG#<@72#MO#nkviNl@=IEsF9bi@)Mnuu z7NTUM;WQeY(_8jnDp-kKu;FAJV!!8(;SR)-KZBjmSa3;ZrbRFNn8jELK znx0P9qzGo5z&!a0D~vl5TxCPA@GxuP8=VtMJ|)87p(?J#VHZ)%Jk{7TNzwfL*wN7uG7bto8GGlOYV{E+@)RRtp?F#mEh>@j_11 zur#!y3zOCqqGCgpLcHP;jKU;0Via=vO0m&U3P11m9}nv@96^VF$b$-1SN4~58(hr3 zIBvMZ;jh9^^4#*FYKw{BC4i*tLROq11Ahcp*Wa=VLJB%JPGBD*<8{W7fF{1^*64~B z{bH!CrwiF|WH``eSagsOg>2>g@cf0W>%`I%f(oHR0@t((m8|F$q5Hc)16|)4^uUE# zI+PGbpwEBF!|`qo)Qk(h0WNi62D4~tk_7tvPL7Xs=8}*4yuETdlutpTMDp1c*MFn= zq>NOa%9G1a0M@?XeiP(hLFo3XzsS!DO?;Ef`UX@+q%N=sN`O4-yT?A8(C1UWO*FSO zCKrLIE1aYt(S(s&uK5!x><-%e+1mh4yG9m9W;XjpZ$1B#UwD6rrKzniG&OP`|8xKV1W`#uK~zY` zeN{no+b9?XQP`_6E1hNPC|6`f^qRJA_bOQ{_6i}8YsZuSKl_2?IO(By#*^`T-}~MN zJc438x@clF@a@7={IEFZD8?tZqS18Wsny%^j_37}FvRw)lz)FD=o`WD_v;6XIOpPVtN!VOl=c}8vp-x*w|f({Mu&;ly{r>~@BVp}Z>8FM zpA;zPj@Mb?n9!|Q>!{PWMj*g>*bUB0`AR(_ppa{IB7cDbuR&m{p!ELNM2xlin0sV} zO}#ex4ZVLpyBYLK|S*tB<^CcLnOozxp~O9&I#@&9;S<=EKXE2t5wdI z-mWF@p+mih^L-raWFi8ky+sF!u3o^UcsYnMhH{9EMRvS}2PFX>s``A#0`rDOVx#T$ z?+EprG=IXbwQ-CyF-jAU-w}KeU6{D*6G0C!s8(Ldcgb3GGBJZOGLX!}aC1$RAmGp$QU+`ha9N5rMm&|zpbQ?Qw{GJm7CuE}@`k9eL0SmaCVNn`^KYVgFf zhUWaiwXf+^!-p?Tv{7|5cxtf6t3t{}fL#Pa^#D_7O`u2GOx2`(=HM*YOzAkjd}a>z zxMG7^97_?*xJ2U;7U(eVr1hn<1BK13acEFZsQjJ?Q{bhzNDos)Jet&F%c%Uy(OsPc zmVXDh?+onYfz4{RyDQGoIe>|3Du4h}cnb&CQD0qE{QWcswCUB{zGKF}>BVrVi+~66L<+Fl8ZH2?QJAKQ?nUaOUUQ4h)Y_Wea2pkC=31ynp>E*VXFugn)uAfnbm-ZAnUX0qE}0Dy!Ca20R$d ziXJ2|!{GBTd4G5utzvTlZ-7rzu!$8&u_OULzsSS08VxZsI&QBVvelC%Ad!6b;(u~r z-zg(BDAM}$1i(I&XQ*eE{B1GnF6bxuLBYfWDc1kICBtb7XaW+P9MI$QkWHXtn@b76(+v;OXk; Jvd$@?2>_7_8|eT5 literal 0 HcmV?d00001 diff --git a/techage/textures/techage_appl_chest_front_ta4.png b/techage/textures/techage_appl_chest_front_ta4.png index 6e2322eba646f6c2c6a9e4c1a15a5519dd5eaa84..bbb24005626e7059c100c5b902455726365b51f2 100644 GIT binary patch delta 26 icmbQi)XX%&kwY=SC&bmgz-MBd97{>Xn!z-|QI@m7BeIx*fm;}a85w5Hkzin8Pz>-1arMb8oaiaX@_a=?;>7G30Q{c} Af&c&j diff --git a/techage/textures/techage_appl_cooler4.png b/techage/textures/techage_appl_cooler4.png index 42583353188a8036c4cb9ea7f7090f45207601b7..952cf552cb80324984158407ba80bc89df65fef9 100644 GIT binary patch delta 550 zcmV+>0@?kr1@HurK@K5MOjJbx008!OcFD)bk&PIC%-|5Q0005)Nkl7xV z5XU`$NZ9%c;SKTx3`d-HV!((pF_={C#9)Pv3_yV?LtC{>3_vTMvUXg>PT*3CI`kyx zoYE)A`iN6|R0G|Bt>;V_Z!Ro^q3rgT)mG8M?t6X94_ZW+PLsu zhoqDbRowx`hdXC0d4>2E<3j+Adb0e(4a$dq+X32!c!lzjk`301*?eTq7x4tG#Hve? z55_|`5)f=KKAf(EYsaE7#rW9qmb$HRJ{&Qy*X|S_`3juo<2UxVeBgG>M|HK*3*N^1 zM-5`8BqBun$1B8r;&S?c@u4J-QZQVW@KK|J4sUTj+|LCMHo3aw`Ec1CjHWc#rSg1# z?C#*6%ter({o_Ll$Xhp)9P`l`rKomAtE7MYS3caLqth?vgQ$(4kka$TY9846vZX9- zyhnUwp&b~II)@g<$GT5CGk|f_KFvp6ZhC-lK4eRTkG%j`I*_1zxM}m*i)Q)xA}I>W o&llZ>NGTsX9{$Kjaw#8QPtm-_CX>@2HM@dakWG-a~ z00009a7bBm000XU000XU0RWnu7ytkOAy7QtF{wEX{$bE z?YN4az@-#*=t<7U<*)O__w$*YRg4^M%YMc28k*;h=Xq-Qa#)IBR6MJ1N}(=Bac?Q| zuR27>QqXdYeE6QPJ%34(kOnoGspvYFj#jLrXdXAZ1cpuDv+Zm z(Ak(gc(=(ITNJbSI#<6Cti-ud>j0fxXcWAx`2JbzZ&&=HqR40RO&(53;rYeF8*ei_ zzNmXRoX&@|dF8zgSuG!`x)Y2Kcg<3Hh4>QVLja9Nvi#F6%72GD0NRH*Mfu3d25ZG^ zJ__f9xPxA1)uqY@&QQX&W6_*qeC&8n-Cl4$95J!i?hqg43LNI+H}oZeu3D9No9OgA-rw5XudOPmk)BjLfeRF^Uz zF1v!+l9swunSYPn6{E?6BR6f1{Mct(sTn|9{A^-pY07*qoM6N<$f+vI> Az5oCK diff --git a/techage/textures/techage_appl_filler.png b/techage/textures/techage_appl_filler.png index 64e340da91c38feb5e06717c95299db7e713dfb6..92205d666e898b7aca56a4a179c0ccb4c6f6d867 100644 GIT binary patch delta 358 zcmV-s0h#`=1AzmOB!5v*OjJcja7+LL2s}JI009C300RIA4_jGT009F400RI30RRC6 z009FdBO?F+0tN;K000620R&1(Nn2Z6Ha9l_004AxaZ5@{K0Q56OiXBJW{HZ3VBC~J z0000KbW%=J03-6h&I+F5*j0o>{Gaw}x4re_(MgQ_0002XNq?gPC^<(V&i@PkO@pY93irv!Rt&IJ8UKPzWX)STBh_~Hk70HOF2)8QpHVE_OC07*qoM6N<$ Eg4nZ=-v9sr delta 406 zcmZo<-oQLTr9LgdC&bmgz$ZE~(#FPyfsu)UftitsnSq@j$Y5Y(W?*1s5L9MhWC0>& zWgvr@ftj6+jg5(!2P6nm10)%|JUyeMqs`3C85kLX+|r^VZ!a%9TU%crpQ3_-jP$h5 zu8!?pJ*$CoQYArt!3^_0?LYUN>qh(!*DYO?&D;h^-y(%{az{6nVo4_rAO9{W=_zh#5L`a>dVd5Z+M`8c*1UMenre_TZH z7wfl${(^}S33AUA9a;V*e%q6(|Knr%!as+sKCm#Hs*``?6ja_LbyD`&ioHklU2^-I ypC!$=D%J_nlKK`J*zPCYI}{=t%e; k*fL{+Sc$XY8d)(0h7aW|%iOJhy<-3ZPgg&ebxsLQ04J3nKmY&$ delta 101 zcmdnQc${&9qZwy`M`SSr1Gg{;GcwGYBf-GHAQ9jb;_8!Gm|s?L^~#9_^QQ%cN1UzK zdG}CMyHNYgk*5bICh4fyNcbKwnK?nM#My9-tQZ5sLj{)UlZx||fchCcUHx3vIVCg! E0Ex;X4FCWD diff --git a/techage/textures/techage_appl_laser.png b/techage/textures/techage_appl_laser.png new file mode 100644 index 0000000000000000000000000000000000000000..70f706243ee2cd73024e35d326779a6498d9251c GIT binary patch literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!%mREuT-^(NfJ_KrW@Lpi3DEiL zR?G(~5iAMv3uZ7msK)*MYU-5NE#mnhE^Fos-b&ibz`!`))5S3);_%yP(bHNKcw8#? z2X6j<)~~6|+rOX7V*a(|$6Hj)rx<;Ead9!*$_ejO3mCrn?YY7bch^IKLG;*Tbp{>B z(+^qL`j0C;yZFO}dBTk&CSvlnjtvTD44k?`>I77;p1VJ{M{&;g$ZyBfpNN;wx~mad z`^Kih=t}QpxAH|j5|j2flw>dplsfO#_{qJnC|iD;u&Q4{V&Nj68S1M)9oc&Pa;2c- zr%4Bz-ne+mPM$33$T=n6Y|0|$g@I46^l&o0o&3hpbNw?Gy-hcZPgogPCabIF1~+ZJ z@uYTARKrKZjQQV>Mb-2yJNAf)EuH_+iv`;*?#nwadWctIdBq;KO>MVZ0ye66>{Gcg zPvqMzJ;6tpKDpe|+Mx1p?}K!^Fe`zSR}UOd>|cLF@X*w4MG2eC9{ASzPBdLm^ZQ1N z$jwLU9tomFQ{=6yerZO`j$BeVbj literal 0 HcmV?d00001 diff --git a/techage/textures/techage_appl_pumpjack.png b/techage/textures/techage_appl_pumpjack.png index cafabd2f3be101cc78d46e34c16e6407400caad9..d6a34db839ed3894c47876f088697ea1e1c6fd86 100644 GIT binary patch delta 130 zcmZ3^xP)AC^RgX2v6on-!Hhc@i=eyalw*33Ra; h@B%@Cf?$AR9%H1Je{R~Vk}RO944$rjF6*2UngFJpC!YWS delta 133 zcmZ3&xSVli&0|SGvr;B5V#`)v|M`oTB z4vs~PY{@1E7PPP#uo`Tckf`B%M&a-@7B)6Bg%l5lL;>cOgE<`@OfFJO__WRmbg>!m l0)dO@fq+Iuht?ge40rE@s?PUv^aL8r;OXk;vd$@?2>|COD5wAc diff --git a/techage/textures/techage_appl_recycler.png b/techage/textures/techage_appl_recycler.png index 9be40e7a795c5274d04e62a81903c1454777ae8f..ddfa0cc33bb20bbd1ca4b9226398315a83d1a5da 100644 GIT binary patch literal 565 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!Yyx~jT-^(NtjPtR)=gXhG)K53 z$S;_|<#JceQMOqt@?u{I7<{=et+qqvUX&vP1LIUr7srr@!*9cGZ)#THnNrQ*I_3ZW z>x;v@7i>LJc5e3XM;5atoKADTes@RVKF91ij^ zM7yq;dhI~d($?mzs}~>DUA4Wuy8iyt8LV;)Llb3TF zHmy}P+!D%|X4$W=`(}qm#;-)VB(75mJNB9wEdX&xHa8K0nM(k4te#cKrCg$C8-t5~77~b8AYj(OiUBn^&_L_j) ztXb>QGuPhs*tgOpO+Mjx9D~3$zx#31r>*)^@zCg%JNv~l^SnD55BQrGn@zpvgd7n7UN^*|)oH%OG1&JTI zuDJZhx#+OKGs8wYHBTHS7K7y*L2K(lV!-^aGyJOTXAz?IhaR~x|0C+YRJ7C8d?wtmpj1V zlP(*wBl&3xg#z$?M&FbL25y0#HMh6cK29HiGEHtfGjvk^lez24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2jv0;5(NYrN2BBb000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-} z000HGNqkVi97DVpU52urvmQ#-jCIpcrX+l?vs${?R#dZLW8_ zdwb=u`%NyFo&C+s&Ub&W`OTe}Fku4y87cI0xPJt=1h@pa1h@pa1h@pa1h@qBR}ARc zkdu>x5aNHk;P&=*Dk>^^Cm=O7)iDB!ii%Q#!Qgb@SzvG!(I8L<2%sHU9J(Q;bd-QI zXU;^|0H;t|S}KCU;0E9YlU?ouHUU=y`vEV|sFZpIsBxNzgc6{X0$5^U@h7zWV&I0b z_J6y8J^K6m%gG7e2E1-T{xx7hkHv?hTq$+sWkJBMUAq8QSm^Qw%FfOPz~k|h13#GTdkA=M(xger6$PY} z%{rNx0sIonp-CxEgf0pT3p;_?W~>ZY=44Xjs1uY@BBk6YgczgEp(TRYK45LnMt_gT zvlsX+#uU4Ok1uH^sFb=6h!ucOYrB3|hp!pHApHjO!{4Bek zxM3Jgz?`tf0}KaB?WV;kHXmpZP+&rIq4pWJux;Bmlu|bWTY%5>Vk6->;D6CRA)uzF z2H;g7)r9zPV189ql`R4q8ylwrKk9-?ER-pwh9;bV>gww2EoN>n)z;RIw5bFKO_*8; zw*d2+o0}7E0&{doGlL_*RlssPHky%<@w2WKm`+7DZ{F-lGy%7olxqVX03Oz1E#2xw zlP6C`2oV5|nd~yGv$OMwM1K=dV^VGz&?u#>(((G79UI8Y%j*DgEOZ&0=tO7>kRM~B zgTR_lhNi!p%!WK*w3M;|V91am0G|WffhSE2aXir)a2BWl4n&cerMJ0u5UTcc+hdrl z{u~oRtnZ7>&=}x%V3Cf`f9O2l09FCDb`u_8r5>jn($muq6T8s5D1R#}t0SQ%Af-ep z@cMhDlpjQBpAOss98yZ%9^w8Mz+T{<2=@*E8+<+=iS_{i ze*tB{T;N{)v4UYhHv$5IzyMt$19)10BGm(yMj*fd=G)m7+A;w>Q6XIi8cvkcFbv}j zdOsJqxOoGfiBM1Y1b_IlPYWXB$B#d2qAp+-*8(q_l=W$!6SDOSK!M$wfNcUMPMip^ z#zK-3&GD_Wkn#d#g*n={G$DOo5|dJ%)}{~-Mr*aUf>FA#auO(sry>-0O;8B&k%?Mf zG~93FIfB4Q?f6c{^ZaoWpo<*&aSTz0^^@Pjz)&GXdA!f>NPkt>>-B!B(^b0>Wv0b$ z>wu|#zyEwv5TFk@vgs;*8a9U54-}h}T>-3j>TJNVvq354iOR~#*$oX1%XO+eCW`n^ z5wgn#db2Z1H<#ELdI`vYwMoJxPO}J s=Wq#d32+H;32+H;32+H;3Fx=@4|DdhOS+e0_W%F@07*qoM6N<$f{z`g$u2r3Ldv96b$#Pkj+i00000NkvXXu0mjfEiGkK delta 226 zcmV<803H8@0)_&REPs!Wkf5KL(WiC9kzUH3YuB`e&CShBm{;Zi0004WQchC9W;ag|HxEI05hw2Cf*A*y{ z(e5qe4mQ9o+*)EGT?+Nl_VH8}7SIGUC%^>(YmVt5avWQc1H9bDiCx0_^*s^_2FZKG ck6(VS7sSRd^=vapod5s;07*qoM6N<$f>pL_5&!@I diff --git a/techage/textures/techage_appl_sieve4_top.png b/techage/textures/techage_appl_sieve4_top.png index edcf1263147b6ad3f2b98b80be14d01634219fdb..10ec29e29b646861c296f8205364c7d6c1963a39 100644 GIT binary patch delta 861 zcmV-j1ET!H2d)Q@K^_%QOjJcja7;->KxA4@fq#BiPDFEQT%VtxPB?e>k#r${BDnX^ zsQ32k)?N&nz4h$5K<ow5YK5%fO;$X_wu)Tm}3z|Nj2?T>TnsfFD%f1$ozy-mOJyk8bJh=UY9d>*CF9Sy#jeXs1zO`l6L_+ znQUg+vr4X+W>6tXJ0x%}?--^vaUWd5;2e-IwHQ~NgLe2FT%tJVz|!ti^9<@7&g3;a zm&-6XWK70?W^oQsxuJ8p49=n5_4huwguyu+bnJsmWbu>OM0<-XgM(OqZ-dL!A^!9F zdi^b0=^QGk4lb8ra1fLRCeem0)r;XK{oNNmtsjiXH$`zT+~ws_<&)a%%chEEdTQuM zc(DKD17<7jM;4dueNmXgk~f7BL}?=Q6rE4^3#N$1jr^8O>Uf^bv>P!_VWO@TQ(Jf% zkh@k+46`qm56*Pv4ds}Bt%P1%%Uoxp>SykfXc3Xf;vu1X1X7Ax6c8D#G1my{91Mt> zz&X3`95QuvE-~v4jCCI)u|<}_;pxQ=F0*`q)G`h(b1an*P8(ci8yp9)%KOUTpawj+ z%#lKN$~?GShQYxn6$h8N;vA}(3@$N2&cSEZ?EVhw9Kgj=U;1Q!Dz&M=;1a}~gX03_ zIWWu#A^X@OM)|#EJ$!Es=c;#qN)Elmo)H7H&n2P4guLPyNge8=+Re*2T)Yy@tvs?X zhQoNTdh*v|X}L;JeD9D!Kb<~!z3hfh#iA%d3Yt>4O5*%p38 zwLqt9Syr91Ube^AtyCz_e2W+rR?dA*X_-^_fG2K+H+_`wK$p6&>W@|Ec;s@wts>54 zW@-3ibg8a$U%uPx_Vd(IihhNM)6lf~S0hF#tYrn;`75$Cq}O;k73!JRkN?ssznodi nU*F$*{Nk|wALCz&@sH0R1V8@yX2+)100000NkvXXu0mjf0FSeC delta 882 zcmV-&1C9Ky2g3)DK{E+(NLh0L01FcU01FcV0GgZ_0000LP)t-sOlfgsT25C^L`g+J zb7@?Ge}12zpW4%~F-|tg0?nuiUj_rT)Dlw3=`z&S@6ZoHn;w*RA!HC(;WDVfBD639 zx61&?B6Y(}8eRrL1;G79>l#5(5_%>)Wc`dQK$W>}M{Rl;xGjKMe@Og;nHP?aRK`kqJJ;oFsikLQ3*@0wc*%D*QiDpnFteaMKF7Ft^xk4LU!r&YXF0~d{ zoC7!g99*I}=Kxr4l+z6A9QNupJD1BaI3%kAe`awGnxU9;xeU&s-1zr4xP-wuth8=} zOJwm=L6BRCD}w_{e=CE_)Per<`da=iTIn3JG7m17VQ@ghqJq(egxQOMg6-WG-leTz z>o-MlFWd0tQSplMKsIg3hF+)?;H{}b}ljN4h(o3BXLBQ!Qtt}4lc8N)moAb zE^{oIOm`VvW*Z!b0^|G2;J~6VxXh6aT!z8HC)Ea*xZ)hL2o5eWLC(QvRpj;# z>KxR?Qd`<&e~P&<>fjQ@oP*<1hB+_*)in9onnn4oBwl@Q#pbFve@a%q#O@KplFua> z>x6vT7)c)LquRsESY14m&aGUNFNVW-uX^>@Vky4-sov($38#)neMuPL-m}jTwf+jF zxpI}7w=?C~x$mB-KkIk*Z48go^w=uZGSecL`nv8>+xgf-aCJVhtuGC`j;a{@4)?3 z+2AX(G}PC4Ia%{w&yWAo>A##=%U|E$d;H>X{vYFCit&%nA9dsaKiI7P-2eap07*qo IM6N<$f~#b-VgLXD diff --git a/techage/textures/techage_appl_switch_inv.png b/techage/textures/techage_appl_switch_inv.png index 153eae26e166a45ab00580a5c95b7fa213faee9f..74374933bcc5bf5782b4e097597e375fc8ff5ed4 100644 GIT binary patch delta 188 zcmV;t07L)Z0oVbMB!3}LOjJexRV)AIe&$mq|K3u)q&>Ss9smFU#Xm!&g+2fC$xJ~P zW8sC|0001TNklY%E$snp$rV6 z%18oA0SpYRN(ccXh=4L&KnWtCgj2u>CZKGbOgjO>?!a_9hBfDa!2uLN_P((J0|T=$ qwFS(}%ZqUdxPU=NM@I{o;ROJwr&xz9Ss9smFU#Xm!&g+2dQDgX1y zOhFe9i-HOO004GLL_t(2&tq69hyp|ygp|NQEeS%azyy-iNEW~_0|sCM3c;pK$N~zX z#>SycNCFJO#>S=$2mu9%025q*0V2SFQ$PVGz@(H!I|0J(z&vz1hUb960Te*?zLK)B xv5^wR0y8I}2&k8r7vU0c0fUZ?jutS(8vuJaN}2>CxLN=J002ovPDHLkV1mnfOE~}l diff --git a/techage/textures/techage_ash_side.png b/techage/textures/techage_ash_side.png index 5dc53614bde853aa41baacb457a290791852c377..f382e26ce644ce4677dc92299529b16b5decc64b 100644 GIT binary patch delta 136 zcmdnUID>J5L_HHT0|P_ST=7ppiYLG)#MQmP=j+$6FJHd=_wQd@(@#;LIAckWUoeBi zvm0qZj+Lj2V+hCfIZyJXq^^?y!H5d5h+ of1#@1Bc*0%fo5UmLbP0l)%^o08nT-wg3PC delta 161 zcmV;S0ABx?0kHv)7=Hu<0002(-QrRJ000kAOjJcja7_OG{^{xI^Yioe_V$<}$%p^| z00DGTPE!Ct=GbNc002-)L_t&t*QLe@4Zt7}MA1oufmv1nmjD(Jz)}C2iiiH>B_D~x zqGw}8j%I@pGMT5M+*6}L@>>4Yh!};du%m^ZIX4mJ5f{8e#Ts~kdj0AH!zc+Ri(Fq< P00000NkvXXu0mjfpw~nA diff --git a/techage/textures/techage_axle4.png b/techage/textures/techage_axle4.png index 2e2b7164fd4d4788f7df59787cbc0ea5e7fb3fab..e7998ec966b5fd84279147fca866cef55b367a8b 100644 GIT binary patch delta 300 zcmV+{0n`5P0_*~iNq?%WrvLx|k&cOmfO~atZD(R%L`hC8Haw)9mb}5dIeubV00001 zbW%=J06^y0W&i*H+(|@1R7l6|mAeiCArM997YK#ArGK!Q^|kHJYHh;s0}9s0XKO69 zH)}#^{81q^J{n6Cx#bO9ZkQ8*?wjCeCGVLOnUq50;x01@u74Rh=Y$ZA)_Ud~V1+og z%uF;Dol60h39n&8elrWU28ask5B9s$el;gAZy@$sJFlf84ZK|xNk&cOmfO`M{0B2%fL`hDhoR%y$JiNiYYJe`U00001 zbW%=J06^y0W&i*H-$_J4R7l6|l|2dqF%X643L_y0ND9~O+*#J2PIk}}cz~e9+8-3l zMUd25h^=1Kgf#v%mV$ZZBYAmbJ`$L|34T`ko@%7@S)et*TyoXh?=JiGoV~n3)NSv)R)U2$P;fozT)j?9pM-$8 z*ve>_kzfl3(a0D&XCV;%b+cipPN917yO;cd!yoc7 z2*W-<@e!w=_$Wwxe91?AP@edRM&cv=FNu#AJpe(+^VLguxaa@?002ovPDHLkV1ldC Bir@eM diff --git a/techage/textures/techage_basalt_glass2.png b/techage/textures/techage_basalt_glass2.png index f22db4a53507e7359120cd1438666ce2095b5a3c..e7a7b9c7c6de4b86182c238e7ecf90df3fac22e2 100644 GIT binary patch delta 349 zcmV-j0iyn^1C;}iL4O!fOjJex|NnJ$b^iYT|NsC0|Ns8}{%UG!>FMc|Kx9q;000Pd zQchEBzI9$vgUNY623GL^009R{L_t(2&rOlRio-ArL=`Q2kGtSr9YOB}BF9bn8gLrmR2$lj^*8lzPJX+X_yp(}CMx~SGsS|p>6>bt`{4J6Bt zsz;Yz>g1C<09Ni5fK1h~9l+Sz94~;4iSI{i2_AeOSXSHP?Et=0Y#$GGn%kv67#wr8 vFs>c+aFWdX{{1vEP*@|rWr<-~V~Ft&k403I&3QM100000NkvXXu0mjf@jakf delta 371 zcmV-(0gV2Y1FHj&L4OHwNLh0L01FcU01FcV0GgZ_0000OP)t-s|NsB~{{H{}|NsC0 z{{H@Tb#-cLYU%0ef=@@}00008bW%=JY;{pygTBdm&ClI&00039Nkl~0ZflL4#44^HM zs^Sc6;0~ZMMm+&>c+d@C0vOF@;3D)94VcNmPw)iDh|~e5STn@b{e$S8IBVJIvESu>7QYbXlZMKDq-H@NHljE RnOXn<002ovPDHLkV1iD9sJ{RJ diff --git a/techage/textures/techage_ceilinglamp.png b/techage/textures/techage_ceilinglamp.png index f8656fcb8b665aa5b698503b66f664324c6db0ad..7545650fc4edc1a2fddfc4e67d46e7d15a07595e 100644 GIT binary patch delta 185 zcmV;q07n1M0@VSKB!3T3OjJcja7@yfJo^0kyoWr)nLHtKq__Y800DGTPE!Ct=GbNc z004AJL_t(2&xMh}4FoX=1Kq+1D%cK?(f~^TJzZ|^b0Lx7$H)*9OJW3Ml0oQOt91i{ zVJ~?8>IUrDDik|Fz5}ZxE~?_#K?QRMc`B&JsLy6Ia#2`otTJ3XT4R@Mv-#6JcN{VF n$*r&_Z`KLFunlwES38kDeIPz5DP`nC00000NkvXXu0mjf!vIV$ delta 308 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 delta 1083 zcmV-B1jPHS2ha$RB!9qAOjJb(1_LDx3m6m%4ScXOMdeWs%UlU~ zaBYiiX@++&ux&5mVH5aa0MT#~!gdAVZxW`J8JmxW>xUWMlo|J^0QA8CJyXW37P0000P zbVXQnLvL+uWq)pT07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b0{%%vK~y-)HBt$avN{lq z7$FJa2-mtn;;I`C1#fm$1pfcOdc%J8rV6T{JCm92Mn}$<%T=lyt#?6iZ69rM)EXhw za5avv8d2NMvN)7N$UF-At~;oWmIxWzj=EuN5x2{-V1K?t9Fef#GW5!}6KNQRWKmSM z3fbyWm$6HTWr^LA7gfxtC$ND<5<+(ysrK2b*tal-9#Xiz?-f58@r4XDU1;EhM^StPHC-PrNoIz-ZJ*sOHZTxilDVH9F1Kn2 z!@;m1qv3wk-SFH9ffy9^xSDj&r)?$}TITA~-+yzk4b-GGpyjLYYs#rPpU(^f5xh|4 zR}TK7rFvZU0k7M~EuJy^Z8Hzg2oY|a?qm}@P}z6Sy&dp~|Lr`E;H9}-2^Xe!BoL0V zQYrO!iI`Mbn6|x>%wEbr)in)JV$J6s0|(_q&?aLp$Jn7dg=y6oW`M<$j{U1(?J*+Gc&H@=u#XGw;V!%l*(e}%RQKIg8iUos@#O_ zFg(i8!UnozoXv^+dB1=DCuH?`+YaMc=8zmzFmnd+IsKeIg)#Y$XS_V`i|Ty-+DQi^ zX3h;vypNC2cl193Rn>QrZ}&&Y6hT@-0Dmnvu9a<(iC{Q9*my*EQfo*l`fY@8T~_zk zi2fts#I>ee3gOUSiC)k1*9XhN+}3s>83f`A56tVx>b&_zuc3d|@$uI655EKftI z0XLlQ^YeW|*M1l}m6wm#E#r-MI#{SP(!Txa`mAsPSx002ovPDHLkV1kdX B-cbMm diff --git a/techage/textures/techage_concrete.png b/techage/textures/techage_concrete.png index 94eff2ea165b7f1049a3965dbd6abb63a6e1d132..2bead6bab23268469d55a9360687d6c0b15e14f4 100644 GIT binary patch delta 175 zcmV;g08syw0>1%}7=Hl+00009YVQh&Uq0E z0z};-+aUuH0h9ue7=Ho-0000E6q08E000S4OjJdoqM@9goR^uGytvQM0002SNklLP8Xyp(h0e$q>v%pbw~tac+wnWAZ%71o!vjI;m^tex4!27=NbvkI(>k)G0pvD zOfwveDRSUDtzilz%$ta2rzG1IEs39=8TCatEGx=+VZU1Moz`C;bSpOb@2! zwuA{1Ji#MTd;=tIo_8MtSNFcpKk>u3(DpkI)-lbk?LXH9bbrf4`BNcK=oj{B*KYCg zH4-alViD8vtIT9`3FWU*jWD(-M>3SLGO*>a(M{tM_>*k+P2T4?z6@ALM;;QZQ{y=r z3;J>3d&w0L{|tl`cWC!IWLZl}(@pKx1fZqse%3{RSV^N=q##I7y=_1#h6izjzM zLI%FC;nZ*d9#&-(Tjdnx)U0bTmpLSPI1J|t5STe2rQJg)=MFt}iyep!pH4R8%NHJ1 v{o$Syaxk8}Z=S<|_~?pH#i!!)FMR$0(#ZSu1_TwI00000NkvXXu0mjfMYWr? delta 411 zcmV;M0c8H{0;vO#7=Hu<0001(NOmaz000kAOjJdyt*f`Wwz9LarKhBxpq<;&L`?ty z0aZyvK~zY`?NaTPxSt^(FAxxtpD+R5hW7>& z-xG7fjD-zT>np&>iJ?1e24>-BU=DAeVaV-y;dQ-pSfSpQg@1k$V`5$^P}Ll@Vc@#> zie#}{g27S|v{ zZ#a_$f|p0d_-L4P4oOjJcja7?eYx7F0hm!PA~%)rFNyKi=Qpr4jXTu;q^EbieI zqjE3y00001bW%=J06^y0W&i*HqDe$SR4C7#kxLH3FbqVkita%b`)XvfuW|uYC3{-+ z1S~3UP`ChB;0RokBATc8|*b8-?%K+Y&PUk>(I} zoIzots-M8K?{6ajceXHB;AARpS`fTE7zPgj0f|L$++Pv6+3|P>(1|68}c8L4OHwNLh0L01FcU01FcV0GgZ_0000XP)t-sOlfh|)X0~hqp!8M z#KgPJ%)oDUc%Yw_N?cFPe=P3d6^F+ZY5)KL0d!JMQvg8b*k%9#0HaAnK~yNuosmHf zf-n$8hXnTgWQGHj3mEhO62e}R_5f~(H$X4o6+D91v~2{Tvwtx9moNW!I`fI5GT=yS zWny<*pd5@-5=lTVC^w*h2J1+-k2)-%F;VqT;69F*H-Pl6F=wDMwR9~AULH)72LOZE zAvms&2<+mxJp<_35!lHmf*8U4D8vqW?4Ms(>xZikWl}=|aPdzP=dQ8$S;`T_fy%I68dIWekPXm+-?R6_jtNEhG?8m zPHqi=%xE{0ih zIZP|s?%dPWxODN6K!c8g#tWqrU5irRv${7js5I^tmyi(j%6q?e_OxRM6ga|*${A-Q z30gLb@ie9LE%-3={2a@cRgcchH0ILKnsH&O#ET1Voctb~3?6@cP6RPk%>+7(!PC{x JWt~$(69B5~X!`&F delta 282 zcmbQrw32CpL_G&H0|SH0lCEPw$}+$w#MQmP=T*MW|NsA2Y}$H1P7R2FGThwUyzHzH zHjtdYVDYl`n}KS8j0bUQ`;Q!3yJefu+^i2krTis9e!&c?W~R?2C4RqrcIDLzT4C4eLX1KG%zgg?|4%^Q!3NX&rq53Tj=UXJR+zl^nl_3000VfQchC<|Ns9ky8ji2TGH43 z3IG5CJ4r-AR9M5+m)o|jAP9y7n^q`z=>6Yyqt4c9--TT*FMk{3`$0gUW%-Neq*r(d zM6dARbDG9|;}g9x9|F+!hWCNi-!Ew5-QaHl0A^zVyb9cTEGB_|OFX06_s+qc zXOn>I0`3YNsDN0m5V7+P052ZJ>pv6fBbH`Kw$> zFq9nCeI|F4}T;ymP%eGvNCn~CxvXvvfmEZ6=?A8Gm| z=k^X{q8o1%wdQNBS_3@6ugW{FxoS6K+ReO%AAPDhmw(_Vd3H6&OmqC+?5p{j&ncd? ze7M^`we=l#J~D7t``GLs_Vg8omVb@EBU_CX>@2HM@dakWG-a~ z00009a7bBm000XU000XU0RWnu7ytkOKTu3mMNDaNz{t?c)7wT$O-4#hMoLZ8+u}w_ zO-4#hMoLY#zQy0<={Gw)pr@^fj+7)QDROswTVG=e4GXTdynpca`WK#(NB{r;3UpFV zQvm<}|1P@!6^B~V*Zm3r00KTqL_t(o!_}9|mh2!1MT4YFl?XnP|NqvDTItT61v6@g zS!*8<5XiFp#dF{zoCxs|PCn&n+&4bs6Z1rX+Y{asuD@S`iFbp)1OS+g0q`ntoq+=t5X%)}=)406Cm+P*0g`h=K~D%xAxD!y=`O%f z=pK~SB!F4y^q@-s-WS;3$RT6)7XdINq4Ampm*t`$@pG47-?+E<`J-Tf76k{*pW8g* zu2tMZ$hl}JSR~{9SGl6>R8my;88ME7x7}YpDBw#iHGg}?xUB2eOyB3A z>}rY$=Xec&@M^xMV~R&DAMW-KZG9g*9~n5Q{m|@x?CC2EE&m#Shq&6qS9X1{8UROO zqI_HWKz|=U@-f9@OP>SZX1T+cJ$y+*ci&I)MR}*?pMZ8dugzaTpFd(DCJmi-R-qJIGZk{fALTJA32g7z}V?jO8@`>07*qoM6N<$f}bH}tN;K2 diff --git a/techage/textures/techage_filling4_ta4.png b/techage/textures/techage_filling4_ta4.png index 7f03588890b4f79c1a22d57b517cce74ed65afff..48987fe3c99126bc5ebc9ffca63a0ceca3a4fcb7 100644 GIT binary patch delta 158 zcmV;P0Ac_B0qFsdL4OZWOjJeO-P+^j;>^s<0002&?(081;Rr107syxAXq>IA|QyO1)_mL zfeE1ns6zm%g*0oK(5z*|YONB;Ot3tX8E}B^4uQe#4w9o1Bazh=6b!)X&8{uGoB#j- M07*qoM6N<$g1A;Yo&W#< delta 173 zcmaFK_>XadV?AeqM`SSr1Gg{;GcwGYBf-GHz#rff;`;dMqc`tf-?@A1%$YMlv2Oz2 zY(T2Q)5S5w<9Kp`BQwvrV;ToqG#J^~*xVcy7P7D;FsiDuv9TJ6D+k_%Z^G;^+MHOZcOaZTs}zO1!|Cww;8Op+{Uc*nPF;!YWn Ys7K-lGLlUf0WD+jboFyt=akR{06a%JQ~&?~ diff --git a/techage/textures/techage_filling8_ta4.png b/techage/textures/techage_filling8_ta4.png index e43a955a25e1fb50a06846b65cc85319be0ec787..b9d631199d571464882f4308f196f345ed5b3b15 100644 GIT binary patch delta 186 zcmZo0|UdGcdzYj9_;|K+C5zyLn02po#e>XpdjGt?*E{H zonyD-T!jz+(zE7t24zp&-SJ<%$$y{nkBqlY-dRTbna;>;)=>#`IP#_>J7JNN)`U%| z!l$?vG3IH7EjSr3n%T10$n08q^4-TbLf&2Z{Ho9Rul1qr_YIBzOf{E%!2YK}{sVK} k0sbG1_Ha(W{Ev;yAthH=eB;xW0lJC7)78&qol`;+0A=Y`lK=n! delta 224 zcmcc3*vK?Nv;OhZM{nM}zH|51nKNe?7#O|@c(Va1&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=m`j+O)q0BI#XCTuR!dq8yWeR4P<)!KIAzamT*tZ9ea3&Sm*wwI{-K?3b%Fnvqx}o^zYFBQG}j?G_8*ejXG-bU_t=DG Q16{=6>FVdQ&MBb@00i4t-2eap diff --git a/techage/textures/techage_filling_metal.png b/techage/textures/techage_filling_metal.png index 2d7021e439d65e366e3e4ded8c766414c1a1ace5..43f0734e7559e1bf12d1651c0360c78a43877053 100644 GIT binary patch delta 218 zcmV<0044vZ0+RxeL4OZWOjJd0hkbvTjd+iQT6AlVrkM|s{rUg^0HR4mK~yNuWslJf z12G6gt#pA9bpXNyl`=u4>_1a;Kp=PheR|ji3}F;`&-o>4uMZXwl%4=;T!7jFV;fTq zG0-3m((ur#p!f-y;h{4OL?}gQ?vOOD9o>z>VOYxwg`6#6G*jZ?hGoQ#8W|iXVl8z< z%hdP7fG_2HOLuUzxPh2%0GwcK;L{D1BV*jYZU0O2M1gF8oSvccaiT3EkLNVw2h;5~ U$979(lK=n!07*qoM6N<$f>^O%Q~&?~ delta 239 zcmV0f7mH z-y)yZ`s<4Y7-b}Y8W(7Nfw>J)gAH`BgETy}Dky%^%4s&*jv5&hCu}WsMvLhCVZfKty=6E!THHWPHvmd7H}L5O%8@Z|-}e8d pd7^+fKu*t4d7Nkq^W! diff --git a/techage/textures/techage_firebox.png b/techage/textures/techage_firebox.png index 7bf2040119456a68f82ae1b2eab45e048d35730a..6267f3d6a94c45da91c98c257f3d7c9d0bf25614 100644 GIT binary patch delta 555 zcmV+`0@VG$1>ppcL4Q0@OjJc}SyOyjO*=3%sHdUA!@iDdTeh~aXiQ2^K|qL6LebUF zdQ3?^K|*?MYMFj+l3r7MXjqq&k6$%8T3THj7mWx200F~EL_t(I%WabjmaHHQL<4~k z`6$T#_xdWJJ$+_gsT`F{C_o_=!}7g ztoyjG;2e>2{0&3^xvxK0XNRaFxJxH8SK0qQV7s`kJdpVnUzoQLxX!DO9;$7=%c=#t)=MWE0( zz;G3EfSmwuA%A3R>^;j8bxm8IXEj4+L~gxJr3`8xAS}a*X7Wv&NCIFJ>O=FsA_%VR z%{pfYNmbG9}{ZaXV&WXjN9h+RCe9Q?pa@P!9cwDW9? zk+u3dtG~65!$cB~s}#g>F=2%ZfXNS_d@P`oOFA%Ajen@Vc-_Fw)?Tz$Fx30CMojSn zNj9)Dih4i=Am0p0wtVw48H9qBqwQsDvXg^2La!#RnF{W5jQjRmoK~-8cmc((a-6pH z-XMNAU<2jY0NdadMV<|KBcQinYh{HuAg6Te%D;Z6I)74~zf%3*Q@86o_UB~%J$1Xj tUHlxq-z2AQ*EdSLzrdeUXZ5|(_YYWV9=)J-%E|x$002ovPDHLkV1jDC1zrFE delta 651 zcmV;60(AZ11i%H5L4O@-R9JLFZ*6U5Zgc_CX>@2HM@dakWG-a~ z00009a7bBm000XU000XU0RWnu7ytkONKi~vMNdIMsHdUO)XqLZLc+toa93A+Sxt^> zTWCy5h*3gmTupjyYI;mbnSO4PUQ@NUu6$@%F)=cil#fR+G=H|WvR^ehT3TJ|?CF^+ zhNA!g0oX}IK~y-)b(4#-q#z7L6W#&}B69!#Pqzb(JGGA%tr>F+O(3+tQZ0GEej4w+ zb57wu`Q1<9Z3^xXZAWn;-zV^B_a<5^;q5q#bJjY?-+%`|D@At3*$}MdT6a2r1Bvod zGC2p#ft-oBaDP#X+oNKb3&gBsltL$+I<{nO5{*$R6l0Cu)kratCZYfUu0+EuAXXTN zM3p4&c$at%aBlV|1zQ0yFi8OL=qNQM$q-Fl0K=70hI=<1w^5}SQ%osJy!L`g!qUY< ziy<*mZn{M|H%c#HqQu4Z;5#lN6ab5$%cS$vNPs!m)S60hDm6t4S__ej8>)*JSPA~&#uRDa6XQ#*Pd)T`lWv>HcE24tla0;? ztZ)J_`2xh-1Zs4F8a^c<^p!7Kz|HbJ)(RS(=Uk({?hg?90u&0|aUMF1G9JubnV;U- z{J^B?WPjv1%3h{aR&kK0+m8m#nKDM++I?RTCgU)pv%);%S%4koq)G5+0~V0C1+aXN zEa%yu4fte0G-GSKEakfKP$Aqj>rdaMR&S}*SE>Kmb?bcl`gGRcUANA+^Ka|@*>&rD l+bFH~pRSYh?IK$57i6;|hUbS{I{*Lx07*qoLPz`n}}1}TjB?1*FRS=C)fLm^qM>UvqDs_INakg~!+ z*C<2&-aIFqI#eCfEb!BPvVVS$GpNsYg`W&-_;`EXca;Nv<)9ZYtBPUK!}`bJ_(#=ZxHzf|69D= z?P$MBc0w_i!r9#hc2OE<=NpJ62!VUkF3CQpT_yXHcJ;sZ>%a)BaE%z7R&BZAteA+O z6%Jp{hV^SvpMUGJmWd=tM=eV>))m#HqM9ubyQ1Phio&0wPWppPFc89dRt}~;^!w*M zJ9sAao_fwaJ2>|w&JtAq>8gL5cFxbQdbcVE@^RL?P0i|2vcIc0X$WnXsLm+#Ha zB3rfQS$WEcbxVT+1Ga0Pp1CxaTQi*6o!P^V%+&AA9Dg!0l`LLiQI+Ow4>z*@!W=vk zhu9AXjnh@KUlKlM&$veG6a-j7{{&=4`-KsLt;+G#!_BO>TzlP#MFp;+8;Xm{d?@_>@nGK zv;&XX@kbUsW=-Je)xIc>_OC+wJ?*3P5AdU`WHa;xsdxu1O&`jh-tg`YzS*^ozm9j1 ci})VC00)YjUHU{ZtpET307*qoM6N<$g0*ZX_y7O^ delta 616 zcmV-u0+;=S1)c?vL4OHwNLh0L01FcU01FcV0GgZ_0000UP)t-s{m3@|;6nbv?+1{Vx={cC0trb(K~y-)rIfL68Zi`ypMi$e?;Ly?gZ0cY zo?Gd%9u|ljTkCY!vFXqm0!Wz=qH{9j@69uC>QHq^**@8_zkmDvY#+4CLl&xe3!y!< z^h^;3fmSs^$S9a6hE&Q_VZ8&yTqu+qkm!Vxw-HdFl*m({F-Hfegmw-kNF@r1A_5jU z4}wzYP!tR(9M38q5j?BU;1ntdzbVkuf$hU+XUGPWS##Kd>DAtv!4AkU5Q9X?c8c}^ z+hw!^+vWe-AAbZ#=!*w%E-&PUH=Je_g=uEt`*Pl%iu&5tEmagDerjEFV_i{sEGpjs zx~?eqkD~BTQMaR+CY%w&Sl6>j0;AD5F|#*@>T zrln4{vH5hSKj*ReJI*eOl+xr%v`>cCmjVwy)T}h<|{e zbwj&(V4#NIpaCbY${ufc_Xf{)rQEN}8{|cN58nYdj+~^1z#5zY0000pP$klnf?49t7#aZs+W z0D{iF0tO@tWL#i7Xb#ZsBY~hi1XRLg5Wx&0n|_O&0SZxMf&olDtAoiK5C^=^r<4QK zF`&XhZca>MfLHlw#=(4u&wD~ZzMSFIPI_CX>@2HM@dakWG-a~ z00009a7bBm000XU000XU0RWnu7ytkO9#BkFMNDaNLqkJ!baafHriz@VlB2I~ikePy zhiHF~E?8~?r0c5y000DZQchC009|EL_t(Y$A7)nF%p6>5Cu>>Z1Dzq z2X7FD3y_f$NSgv(N*c$M7LHz53?xLvfNJ|!HW{sU?pJ;E{|g{ zX4y`O$GYB0000OV delta 195 zcmV;!06hPO0*3;SOn+gBql>D)C7WxZ00002bW%=J06bazvA+NS0E6bD%#Be)cQijavifZ*q3`@mPHjT=9ZK*j$e(^Knz0``Us z)II_FOluyHbV3G-|M4SG96f;pkwLcN8X4%5Tw+Ndn`8>f4E2QfMG1ANOjeG@k xCsRW$zFNB_vRS)c*B(4Ds&EtlXE*9pnJ<_jl2@n;E++s0002ovPDHLkV1iP1P=5db diff --git a/techage/textures/techage_frame14_ta2.png b/techage/textures/techage_frame14_ta2.png index 356da5916f9828751b7ba2127f9328ae42a72767..b7a119a454dd0246bd193bfd0f7cc9600161ef1a 100644 GIT binary patch delta 370 zcmdnMyqLMcMVO?T7kf&z?Qo*V~gA6*U1U#at5P7tEmOSZ=hy znt_2)+0(@_B;xSfONM3520UyDU)gf=FT@?}JDHbwW8y;Vm9u3h9p{^pEpGJW=Dpp2 zrT#lsO|qNREwNhq?Ox5;3$s>5?z-yN>l1Wl*^HI@etxdcFe{wByKj};N|CccM%HV$ zTs?E;`~1LG2d}-CHplfSo-SNaz3RBd3iemeEMABu$+Ew5Iuohjduht&Wy+=AG9kv5 z;_TM~X0rKc$*w-B9=vDi=LtSLgD3j^JX>|cF8Y4!-yHaXVr+w(#5hXnq pyRHY!c0{4a@ijC4Ce8WzgIQiuCa=BqT^J~6Jzf1=);T3K0RTI;tls#9o^~at)d$zB)r-oJN6;Oh?B*-sVk-_nh z(oB8^21XT67srr@!*4GcmNgsjuqA9&&&a=UUHe4YjkgkIi7(_{rL{lV$o%w{uhEm6 z_jdo4`tMjZ$!^l*iqvT7rt4}^-tD2ke9x{u6SRI`+{#@o^=V&r&$v6sJ-m0x(M=g? z$#X-e&1`#Uyi(%i>LN_g4CMT|U4U(wiCJdu4s*1^pvtyk|ORbUWNyqdDEz{nd06 zi!~xXQX2kN{4-+gt_7V~#^QQ*ito;#x&A+Ot5z(GxPRm}=VgbeUs~@APt49+vRUq+ z82cynuEXrMueWxqe3yTjbK%~-qif{$zx{Rg?A`Bo{_4E@cssfHWA*9X*8bn`cWtoR u)X_Qdz3T@?d7{uqeY-zzlVX1SVD3mfc2sI|tOGCr89ZJ6T-G@yGywo_h_a>t diff --git a/techage/textures/techage_frame4_ta2.png b/techage/textures/techage_frame4_ta2.png index 52f3a95b8ff0217dc00b5640d066892fdd624aff..98bb0e6b056bcdceca65f36b7fb439bdf0bc4cb9 100644 GIT binary patch delta 329 zcmV-P0k;0E1D6AkK_nYcOjJbx002jDOw+4*$eCu(qH))>hK`Ml&CSifi&~_xUCWVr z8h^2FgFp}j(8(VpzW~_o?*LP?dyebl4ybJ`rv@9PPEf*U$4R7U&wVR zlvSDcWT;RnHl1xw@mRUhZI61}VSV*^ug{Lo?MF|$3s#nmHEiOP6)8?Iy+d44Ke1TJ7=2~>iNgf@k* zBzVSd2%^7Ydyw$_0!o9Y8pIOf877GCs=Mgx;Z}o$%?YLqawtRrXoWebp`BsNC|1Th zPHe>V({0t9^jKwM``P_=Yx3)@TRu}h=?&300000NkvXXu0mjfo?)CL delta 372 zcmV-)0gL{Z1FQp(L4OHwNLh0L01FcU01FcV0GgZ_0000RP)t-s0000?X>re@amblw z*R+Pyt9i}M&5n(Xzl&O#8#;dg0007XQchDM0MQ|f7ytkP3`s;mR7l6|(!FkjKoA7r z$r~iEuzSR=IS#0gJD`SO_r?g<_(!Q7CE>B-BvQ2JJ{?;0hl+jU!Y$3Dt1_^a#fiD2Hk#a0FHo>KLw&;0UWeh<-qN zmhk)vj)SKfOl=a{87hcwi@WIC{#Jv8%?T+f!D)F|?HJ?GR>*I?PlvX5mS%RZKUEc^I}eS87PPczVF SZ#Yf>0000hK`Ml&CSifi&~_xUCWVr z8h^2FgFp}j(8(VpzW~_o?*LP?dyebl4ybJ`rv@9PPEf*U$4R7U&wVR zlvSDcWT;RnHl1xw@mRUhZI61}VSV*^ug{Lo?MF|$3s#nmHEiOP6)8?Iy+d44Ke1TJ7=2~>iNgf@k* zBzVSd2%^7Ydyw$_0!o9Y8pIOf877GCs=Mgx;Z}o$%?YLqawtRrXoWebp`BsNC|1Th zPHe>V({0t9^jKwM``P_=Yx3)@TRu}h=?&300000NkvXXu0mjfo?)CL delta 372 zcmV-)0gL{Z1FQp(L4OHwNLh0L01FcU01FcV0GgZ_0000RP)t-s0000?X>re@amblw z*R+Pyt9i}M&5n(Xzl&O#8#;dg0007XQchDM0MQ|f7ytkP3`s;mR7l6|(!FkjKoA7r z$r~iEuzSR=IS#0gJD`SO_r?g<_(!Q7CE>B-BvQ2JJ{?;0hl+jU!Y$3Dt1_^a#fiD2Hk#a0FHo>KLw&;0UWeh<-qN zmhk)vj)SKfOl=a{87hcwi@WIC{#Jv8%?T+f!D)F|?HJ?GR>*I?PlvX5mS%RZKUEc^I}eS87PPczVF SZ#Yf>0000Ww delta 68 zcmbQuyn%Uwqa|m7M`SSr1Gg{;GcwGYBf-GHARXWn;tHgFG7Ev^<*OI(-@p6n&CBB_ Uj_uvQXT!$zJ+_Rg8&k~~0iXgH)&Kwi diff --git a/techage/textures/techage_frame_ta3.png b/techage/textures/techage_frame_ta3.png index bc2bb4914597017d31fffd597c844091be822a41..e744bc55e64c9062fe6236983c082b89d9a58bbc 100644 GIT binary patch delta 30 lcmeBU{>e1Kky|>zC&ZP3fx*4NXJVo#i@(Vx{*6^4i~xj;2ulC} delta 68 zcmey#)WAd U$M){uvti@<9$UuLjj1Ax0I#nY?*IS* diff --git a/techage/textures/techage_frame_ta3_top.png b/techage/textures/techage_frame_ta3_top.png index bc2bb4914597017d31fffd597c844091be822a41..e744bc55e64c9062fe6236983c082b89d9a58bbc 100644 GIT binary patch delta 30 lcmeBU{>e1Kky|>zC&ZP3fx*4NXJVo#i@(Vx{*6^4i~xj;2ulC} delta 68 zcmey#)WAd U$M){uvti@<9$UuLjj1Ax0I#nY?*IS* diff --git a/techage/textures/techage_furnace_ceramic.png b/techage/textures/techage_furnace_ceramic.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0a82bfd222c90cad369a2a6ca26330649a96af GIT binary patch literal 643 zcmV-}0(||6P)KI)m6XQ7zdt)VWM5u0zlyW~0004WQchCS}TuO}%6laV02Z`bZHq%E-EwVWiY_yEP5`Z0vl`W@Zr*4Q^IiKY8sq zw&&sDe=}<>w`AL1#s-hZ_%?DF7AIg*er5sb1&%@p83D$ueS0flKRYBcz>V(!YZf4d z;AYF%17txSvsD_l0o1{}C_vj)@JeGgJ3#O&z--~mCSVv_F#KKMhza}wAO-pN3U59o zfJ#8OINd5df2;sVc>lc%@qC_nQTTa1ZM=nk5ogNq*X1hU!*(TyMbn#OlpVlQrLdH8 z2S_!|yHgy}9Ip#y9M6u4a=G!shnz@Y{3F%bB=9-1};CIN0zP1_p7*MOv^tb}2l zm6i-i3e=cDQg5TIa;^GE;lGFL&t zNt{v~(jnC-jI)UX5r8QS@sfZnVIrWk8fQAgRIYM3YhsYa`{2cYk5HchOOc9|f2|ob ztllX*8b72$+6}{G@(wWO_HYy+#Af9uV*rz{DLs(T@z^jr9bQhXVlxoqq?}$e456ZKCL)hR;qN z6EUGcHvk%414tl(0y0Bu=CF}!Xq&FC4OtohED+t0wz+~Cj--*GjTzcNEyV&3f&vXF z2vIRi>XOI84j$vD_P)fvcf?_CX>@2HM@dakWG-a~ z00009a7bBm000XU000XU0RWnu7ytkO7*I@9MaIj`pQNX#t+2en!?CuwiH?z#nw+`4 zzx66Cs{jB1*-1n}R4C6?kh=}TFbqTqut=@INN+W62~ZOlT7STW(#L=`fU`dk{``r$ zd%io82Dm+9CP3DwlDX7*asUK?C^`oqB9!knJ0OaHfI$E;A%2-fk4Zr^))Pn`(F_id zfPG|6sjpxYMHe%?RzRJo6K2(20Rvh<6B^M1c0=#(@R4Tdi>+=ANg5P*AcvtXadmdX z)+|!2F{yS?=Tg}N0g3?&7%1n+0QJ_O;JE4nah2?FfH1TRjK1Un{tRt}J+5;7KEdW% r{>OfA=nk%i{u$t*w~I^PGuP<{|Lki1$}pSc00000NkvXXu0mjfvjC3} diff --git a/techage/textures/techage_gate.png b/techage/textures/techage_gate.png index 4d424f7dc2b521ce4ef66da2a414fc3b235fd129..ac51f19d56730490ad7552e99f0e42f9a5ae765c 100644 GIT binary patch delta 399 zcmV;A0dW5O1E~X$B!3%FOjJe0fkxV*V9=FSZ$%H6UnhP{61Z|bjg5`X&CO6e^TPlD z0ZBVK6@_M8{x?8oiSO9h`wg5SS?=9}yZPx#00>D%PDHLkV1nipumS)8 delta 482 zcmV<80UiFS1N;M!B!47OOjJd|eni)yU&n??eoPY1l2dO*58b3;)0$bAUnjV7JJWmM`jgsYT%!O+c_#fuosz>)z2)8C^^JxE@(zzYM z_m*I;jFrbv&L^4C|8#TZ02`CIC>YB78QBW6HU(+b8KH! z$-%tdRSOD4tbZ={#Ho$Ngf4C821+(nN`y%&JxUmPssOCrSO^Lz$YxO?6ZMRm%R6ad z|B5ODBY>FO+gCI}+@VJT7S-wv$GzY^t}rr&j_mhrIS?Ek5S3IVw8nDi6O8P;k!REz zqbK0z@DLUKi>5^O{FHHh_-(C{=L8rAFzur>ru>$Gp<7=vy^+-BxxOZ{KI?TyHI$XU z0!Q|5qiCAvdan`c1n9P}ru06np{IJYfu`i3(f|Me07*qoM6N<$f=+GbbpQYW diff --git a/techage/textures/techage_inv_button_error.png b/techage/textures/techage_inv_button_error.png index e4b0d1d6566ecda89bd2604a4b73293eb8c20b23..c01e36a0eeaeee953d064e8e5da7c37251997f84 100644 GIT binary patch delta 297 zcmV+^0oMNg0@MPK7zqRe0001iRAR4C7NkxL4~Fc5~vkOP<&e63=b3&>E>rKJ}r zE?kQzDDK>P0_h37sd>!QrqH2e%9qJMF96UMz<jO1PCsI4N(<=V~tXP<|jjhngU5HqJ?F!mMo5+?1;Kd)DV4HluLh;$s-;N zeA<2LPq73?BgdnAVvWXdK&i+ep8R!IV vui3O>ahYM&+95h@{9XYu?iN%%3egom|G6ooA*vE700000NkvXXu0mjffDnus delta 338 zcmV-Y0j>Vj0{sGz7zqdi0000)q>9FoAs~MzP)t-s|NsBT$H)I5AkEFq%gf8b!NLDH zIRDYn|EH(_Cnx{z?*D&(|5{p}Nne}*008t!L_t(I%Y~C$62l+}ML{5%$Ntw_AhrRV zY51@ZPw}Dv#6%=$DtR_67$u*o710*0Ac#ibi@qNGcdJa`yL}@ffFzQ`d)tk z3_u670H_9xz~R6a0L&Vo0_;`%$-wvm5a%Oqkxv;aFax7h!B~#CY<~oxiu5wBmPInR ztTNrCWr#pe5)?2EnBU?^fR!oX%c+kQLUbTz*Z|HDdOxHpTD(WQhXpu$Y8YHPDw}TB z40}AG;R2K@)~2oCV1)#Swi`^spbM(1&1X0k}X}V=! k#OD|0u!hNhMpzyB0P8mi%!tXeApigX07*qoM6N<$g8Z1A#{d8T diff --git a/techage/textures/techage_inv_button_standby.png b/techage/textures/techage_inv_button_standby.png index a13b2015f2a5ca0192ebde88e417adcda5315d90..8e239899318cc3a0a704f09f12c9a663507593cb 100644 GIT binary patch delta 316 zcmV-C0mJ^11Lp#e7zqRe0001iRAt+U%%_R{x0F&xM=^N}JobO2 zHv>76b^@dg*p0erI`jlLjW8ioF@qJRo>ewS<#Rw8%JK*>)nN9ZjH%OCRXrn&)s#{q z#yUfdW$Pxth0+Iue7zqdi0000)q>9FoAs~MzP)t-s6@IzR&CUP+|H{hB!^6Wiki*#O z^Rm?Foyg)}r_Xk@*1_HE@Amq8UPurC009X}L_t(I%Z*e~7K9)OL_ss{{^wmr)U5Qq zoey)@iGdL!$Idxntom>h>`!u{D|4gVd$vW4FwXkR2K**O*`iADn~;u<3<7@$(RY6o zr#Xo135RC2kT^4NDS;5-OE8w6pPD42%oRZc6f(lHOyr>twRSCG0z|ya0TooE%<7#$ z%V9(~Q$?r(p%Ea#-C}bmK%meaL3lDK`d9^G#bIU2BYO(NX&^f>ra6h5^AI>`O~@(vtM^mEOcK{6iYJTnB#fpFKCPR!ca#e32`W+ik(MD~ zGYIVtRuTRgoENyypKZ`E!ZwE@V@~ diff --git a/techage/textures/techage_laser.png b/techage/textures/techage_laser.png new file mode 100644 index 0000000000000000000000000000000000000000..43e14f69030a1f0e8c5a2dddc86cba3f2478b93e GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|uI{WK(m38v=YM-;L#7H) zkhvttFSw(lgEe#db|6>W)5S4_<9c$!0dA+phD1>T2Mz;qeFg@5dG?bP0l+XkK6eAtz literal 0 HcmV?d00001 diff --git a/techage/textures/techage_lighter_burn.png b/techage/textures/techage_lighter_burn.png index 52c435bbc93e0ccca05576bb5d0f564da8219dc5..2950f59be10a71ae28895269c3379ebc561928d5 100644 GIT binary patch delta 769 zcmV+c1OEJ+2bl(tB!5&;OjJc}Lm^u@8=z=2y-y>dc0^-DCr~yZds8icP$G_HIplgI z^s)rQHy79{4PG)D<4qQ}iBowv9Ijt2K`I#ak|dHyA?`B+Wh@u@-2k08821nY!Feqt zTD&6w00MwXL_t(I%NeoV-4>O6It&)4V`m-9zBjgB6ilU zS8Al>AnOPKUw>=I-~g(qS)Y8KXQ4@koC{xxKE_Yy&CJFaKHv9kdW-$9V8t7oV@k1X zU>VP`Qkq}q$3Y;0ndAs%&hDK9i|_(ah=O55EK|Y}-2TPbdhpI1o++Z-)3MbM8UkoEr7D?Tfj?(l;%W`?a71fT}RvR2q_Ro8>PQ!0ok zi)4^M$&ylQy25}9@|F>-0xvJYf2N3`v)!o;0Mw=9I!jjo9R8EY*@+``jFGDnOANJB z0{|)bT7N}_pqTpncq#lr&Y*Cw3T7BAqdh?$Tz@t}AQ0nx=3KG#_tFsdOz|?OLwwEj~Me7!X0SHsQ(U_UPdlw)6d2Wbauy0ROs!FxDJNCI-csa_fw#C zyU;Mk_|$=4|DnaG)B!+@PTc`e;m9X00R{d8-Uug8Cr#aQ00000NkvXXu0mjfAl+$( delta 901 zcmV;01A6?K2Al_wB!6H~OjJcdDi~!f7hWQ!TwuBbr_}<4qQhWjU^2Euw8Pp>{;Uc`fR4A>4u}wuw{q zk|g)12JpHA`P~3X=njbh0004EOGiWihy@);00009a7bBm0Dk}r6951U69E94oEQKA z00(qQO+^Rd1rZY|CO~N+Q|WOw+WN zK9Bbuv+4b!c7HY5S}FbNMOw22@4ehpzQ>uS3jl6HI;Vc;7;XEVB5iY!w)!m@7kd}f zIyo1A_j@00zTc&dyRu}xn%G)wg-VH$V>{20>zog3>Q6}~QgzfAdn0FKjIO2-!m^}# z6~}Bz=ro?}6klYe10V{avGyHQQBy2o+cxjWb&|4tC4Y3SzZBMeZ@u@n&GVeo%J#p4 z<=AaFp6Bo@unosGo}0eTuL~_$+;KRe%%-E1U{U7rJl?p{(PLxcauvD$&NJ&0WxwKX z&in9-#@0?~2)w0KVw#o-0EXTorrGmVd&gKJWW(TE&Z2W394)bSS{^6UgH9F$OrB$dpp8b;n!0Rxn-Sgnv+gtrG;G z48=lH*lm?gwMD5^VOAE&a115={p6ek445FVuf;0YOXLziQ@GQX-#b?T)Rc~~d947r zU}JaE5`K}6^K3$~HT!WtPX+)fK&uWFifW|k^hV!|FNj)BwwFlPD}mzh4k39^Psu=L{{S(|!1 z+kT=xBFF<$RsFXS-YWk-M>c9bpX7ggWR8U>#u~;mk~X%cdLChAgBi%rbH>jLid=)= za#~hgKxfsMM1|pePmDvSuTC^#TGz!N_njvbp|Y#_IDYW!KeSlFXaH`cDCy{oDp%Mr b1g^9{)EqjT`<*KP00000NkvXXu0mjf#?6y8kYu1vvpekbukI$$FNvEU&p- zpWl1&IbRw`lb9$YgkH$Yh}~q|yo^xv33?fc2B2jb>Jve!3`fZj{wR)69d%h5I#FpD z3>s!*H5w-a$5LdL(MiGO8>w{RF4tJw0dfBK_w1EVh=rF6l02><{907*qo IM6N<$f;xFu`2YX_ delta 253 zcmVBB_z)l}QHJ$-E}eC-*Q00001bW%=J z06^y0W&i*HvPnciR4C82k3=RGXF!}L!*)OsfYuxdvy;KH>+QiX zSY(J!hSNbrhYv=yL}Zu%IecjWm`s7GMG#O0PX-DK%sC(?OjR0@?kR1+xT@7=Hu<0001(NOmaz001yhOjJcja7;@!A37=*0RjPALMCZVEP`V= zk8M45S2CWUouGS1DSJG5JLs)Gyri6RDhHt*MDun4dk#5a7YWAZ+q+y z>>d$e_QH;kcK-PJ&t?yiGHdO63qTNkQ9AxJQW_yOaMUg-f9lHTYk6{^x{Bhh+kYysm{q^P_* zKD55&BPCBbfDc)503S5f4Gw{E2-78x`S>3kMp%ydNI6as0q}8h`PH_VHlnh%I|>3m z)-?zCs9!C7aASp!TJnqW!6hRpmkd5=mZnLw)ctYJuYZLP?leprJ)iz{>#5}aiKjog zr7o8XW7&Vz(??Pc20lIyHVZfe4p#O@4OaM&cYIuF+ISg0#w1sP4^9p~*Z?2fvS2M5 z+xUa0`?NO(d~7^9$w2FT`u!76|M;F#?kF1!zWdl89~9N6s7T8D6nu!Ld001*kOjJcja7>SFJ#|+ypnFGKLMCZVEP`V= zOEw(u2SlLU1mJ_4HJr^`zI85b~3h(Gi3aLJM7ROaur?e1(vb5Ms>J zkrIamr?9r06@Mj!I1p;6r1#T){7LUf|G+e-6BmOJw(a=rN@WG|A5s#-6w6h}{ehOt zrfoc#jt#Ev9OZ!U1?dNs=kTf@WNSI-hfC-y{?HHB_VLGlDC2n=|Jn~Npda3K^)UW1 za`A_L`1kl1LKlDNhe7;@HF%>R=2nOV=!d!LKuTcJFn`UXG)jY1Aq4b;VswCh=nvWS z1LvrwA9~Y%`+?~P&a|K;XIkioI*X(-ZR2)-aPVT&51c2#Q3FBR_w#E<_#B({3=9m6B|(0{ z3=Yq3qyag`o-U3d9M_W*9N6>%W^ENYRiIzqU=VbunMGmE6$6dtv%IT3EE%1QB@eN% gbciMhq&ToLFi5g)PyM-IKTtb^r>mdKI;Vst0Kp$Di2wiq delta 133 zcmbQsxQKCrN}Y6oPl&5~flrWyq_vu0uBXt0yM^yw)|@>R_w#Fqxf0Js_VYIx7#J8! zg8YIR9G=}s19D6~T^vI=t|uoru;~TN+A4CYK)<}fAm|XYsKS~n1{%$0c~^N@Fgh7a l9b^&V;4*QLl;v5$z##6xHrGXzD+Fi&gQu&X%Q~loCID(tD;NL( diff --git a/techage/textures/techage_oil_drill.png b/techage/textures/techage_oil_drill.png index 7d05c22036fd5519d799fec17ce01f1b415c4e12..95a9aa6334532bf0fc8f2b902dd961efa6d83623 100644 GIT binary patch delta 293 zcmV+=0owkJ1Kk3UL4OrcOjJcja7<`uXc!+rbaZqfC`<(mEjT`2PSxCI00001bW%=J z06^y0W&i*H*-1n}R4C6~k=+f#Fbsqpu|N*P06g&ClVJeL1e6uJ{|wj;AuX*Gr=RXz z|A?BY5ikS&Fd^bqr737sF^E6QI$t9~MYRr4;FiJxFqxR3c7IUdpDIN=aJT1>kR%AR z|3%`GAiQC5YYmXu(;^212tl)WIIi<01kDnrbxy(8BEr)%Tlj$&c>aRSAqzB{6YGHJ zpjqC)eW$?ffh{OpXVwkK!Z1J?rMIM*vN`m^{Fd#}Vi-+Biw`*Ya r9NaF7uHfG)IUcqj2SsE1I=24-n>I1BDiixh00000NkvXXu0mjf7ZQEf delta 341 zcmV-b0jmDp0*wQZL4OHwNLh0L01FcU01FcV0GgZ_0000OP)t-sOlfg+baZNJY8W3t zV`F0>C`<(mEjT`2P7oyf00001bW%=J06^y0W&i*H@kvBMR4C6?kwH$yFbqV8DhH^o zz&bV&2SAARZ&VI|$_3z`q4)oG(ln)6IDPVr91ny@h*gO+7JniFtDRMZNtB5fP=S5- zef#vP3uH+HOvNNq96)CRK~o3QYQKUY9hh3|pri&|EY^dG8^||Q2|`W|g1c7V^%I5H(@ zi7LM&VUezCk%&_>38NYnkOBuvRDgV@!@Ql0Id!=4TB!3K0OjJcja7=l5d1`8EaBy&#Zsz&UZix zG|lRE*QYb~KEdU( z0!l2v%z_e@wzs)%XnRCcUGikZ8Jb+dWuVBJEbJ_P)?oRH7NO!RCe(kE0DQtCG-?s* nYgw#)>d!uP*tvLr(Qmu~cO+)&t5||)00000NkvXXu0mjfa;;ta diff --git a/techage/textures/techage_powder_inv.png b/techage/textures/techage_powder_inv.png index 976a790f2dd539ee286f790f455e193e00e81c49..4ec099af244a0567ecde001e258be2a238055465 100644 GIT binary patch delta 710 zcmV;%0y+J=2HpjbB!47OOjJcja7^p#>*M3&m6ertc6MxRY<_-zgoK2QjEtR~onm5Q zl$4YMx7bnu000DZQchC<4J5`mq;u*300L!6L_t(Y$L&--Z`42#O;T~9Ny>pd-h(gLUPWw+)#w%E^fVt;y#Iu|9>2(91xQGpam(@Y%x#o zy?L{{8yk=H&(`EG{oVG<+Wux!uczn~^>8gkkqwh<9YNc)NxBB`v~6pMR%>kDWKC-M zA65dM)y;W#>dTcBMP8p0J3Kj9(cdbPMu{LsSV7ULk6KmgxW_&(2RykM)Cv;G+5U<> z<-<6_h;krHHGeu$o9mch$gygcF0!t36?Z{{b&@Qh*iAD%7NfB1a{}?Z#T3u#Hn>Jm zzE=@~m)i>tye8EN944_u38Y-KDC>N1jM77kI~@3eiI;giJOYS3=A)1X_7*}YLY1?ybq4RgFmw>#;s3DEJK5q%aH*Jkasq> zCWCPjV5<63OB1-<^&Q7>`?tuXuCEgfsRSBA)k8B&+o=TqHArV)Ckc%MMO+dvLqrLR zV<`pxM1Nc~z)%xN|l9uaKQ)85Yava58mDaUSE9d23q^lm)MUZ zOInT)TvUDH&F6e`oDN1j(y1>Afgt9VfRY;{NT>dY sXM5)^>-I`iVn%I@b9+91_y1%40JNV7d6Dfa)c^nh07*qoM6N<$f(gAyhX4Qo delta 788 zcmV+v1MB?W1-k~2BsMKjOjJcja7^p#>*M3&m6er#etvj(cyn`egoK2QjErt>Zk?T- zX=!Pcl$2#(Wxk*GpR7l6|R6TFgP!w%Dp@^YfD;6ds#112aztUC} zLWv#YCX=62H-yQKU(yr_DUMys6LQjql8T`~6bw`25Uh5pRAbCm2ZgFks8Wd9-+


MiJg9 zmMzliGjUy)vvYuykR(nw&_nKCUkIiE^&Ejv0u9J;4Pbvz1S%9&SMq5|SfP+L&?*FP z_X4)Gj~E4WG)~43_)s|M%S*k0inc(lLvekLH2fmKm_~@~RnV$`HCBSr_)?u|)yO1> z)#x4**Q(|*BnY2+Pqq?%VHuPg^vv&BnH3L@vob2Q=dKG0#uZ^Oo*Xih)A~CQWK<# zw2zgTA922RD-NE2iLMGl0>Cm)e>py&TeSxq7kk%Tw5fS&4w3icA`7Ud(Fz(8V|-$p zo7>$J3h0`M=OQ32iYzJ`fZSLlxlO;Gnq?oK0babiw@IK>JY~>nau(oZF~&1|eI6j- z&Kcn5o2L;1LL9|WdRdmXF{50vYo((!i*)C_^-#^^iV{>)ag=2_B402{z%O>%rDnW4 z#0}5$k~{5ifwT^y2_)9pIov3peyd)cnw&b%+WTOi+Pk*h>Fl1p7vKH=SlTAZB7 zMMXto1Bzn+006Z~L_t(2&tqU91275-3JL(RAc$cA5>Qf75)e{SQUoy|0%~bMfwV** zV;e*u38-QlNJY{nuz(s!OEyqIO$kXLX%j+V_ih)UfQlnfztdI)Rj>LzyMhbOn?vo0D`nSjrVL%?f?J)07*qo IM6N<$f&o)mX#fBK delta 319 zcmV-F0l@yA0@?zQB!4YXOjJdbm5`U0l&`O=T3K0RVq)0X*j;&IYHDebPfxnKxzNzi zxK2(+y;_`{$VEj(jhfu30002!Nkl>Dg2;dhLkg*SqK=2G0AW}Nhv)MPmU*a2*<5BT zrK4?R`4{lUjyNYJgTogR!ZsvA#RJK998V;mcfDRHNl13r`|Ss$$**MaD^Gw{UM@^4 R)-(VB002ovPDHLkV1oSXnSuZS diff --git a/techage/textures/techage_power_terminal_side.png b/techage/textures/techage_power_terminal_side.png index ada1b1a2aa9cc7ea1793e687df990bd41411a9e2..61024047f1a9c2ec0bda0f358032f5f81d846e1b 100644 GIT binary patch delta 90 zcmV-g0Hyz^0j2?vDLH6uR*#U2ySuxVm6d{kfQmw$!TD1pdx6IV=fjB0I=*N;-nkq)c^nh07*qoM6N<$f=F2*$p8QV delta 92 zcmV-i0Hgn=0jL3xDLRjki)d|DySuxVm6d{kfca&;kpKVyR7pfZR4C755JUqE0t{fl y2%~8vpacetFq%O?#evxfB%s1zkzy_vo*V!&c@`woP#k{%0000E}2#lFMG%smMbizqFTBP`O@)cXAT zrl+dl<>GZ)SeS%%>Q7d(Dl(lYG%gz>zj}ntEk3fToJ>-!eYOgH-0004WQchC< zK<3zH00071Nkl$?p5V=}H)ToPXMjNRGRGh-XsuHKHYO zMXCQ9lqG%3e`8^P^ez99gAK1AK8;&gyXEcTV+vv1V9w08k5?ErxYpjb&Ax#6S%9V!z;f9YYOBrIZ%?r3eEJNCr>k z+aktUZcy-+>3@8R05HI>zf;U-4OamHq zAV>qe*FG(9J#?|c3Hp0|(xMFr8)zbvXan(d5z_eq&{;l0oo^FZ`64A=FhS14LXc%0@2bXI6GaW@3Che=^DnJv}s9zk1hCaa%h~U$!V?4=$o7^ zma5UYhNJXV{uqRMERA>COh%~z2k%#UhaLNV@=wiSz+k5u)^$1jg_A}KAWf5LK~B2! zdW=^k=aMIu{)i`svhUOFVAKcd74QfYrM_{<>T}Jcf9l8ZA2Dbr*zoSvxBvhE07*qo IM6N<$f(W-)`~Uy| delta 856 zcmV-e1E>7%1@8usB!5~^OjJc=LvlR{5-SuvRSgg@9~X`xA(134eLDM`cxq2djJ3c0d!JMQvg8b*k%9#00Cl4M??UK1szBL0Dk}pa7bBm000XU000XU z0RWnu7ytkO2XskIMF-&u2n-bpK(eH#0000PbVXQnLvL+uWo~o;Lvm$dbY)~9cWHEJ zAV*0}P*;Ht7XSbPX-PyuR9M69m)(-1AP|KyDBFTN;-Co#zW-}(H{#FK-ZaVO?zu5Z z)pux4H&_wkzke#_QJ?akSolky@^3l#_`LX3Z{dAw+k@XyjP(YKCDyLqug_R*aID?7 zt$zmO7d{))#jn3{8(4p=h#-=#_*TE=U+Q>NI0MYzs`*^TO8iR5z!}$;R5=T`7 zl5=iNH-39nG$3i%WlxK3TlfYJPU*s@?G-`=-~US6*MDBaQ9ulVSprfjQR#_3>B9RE zSVN>i0eolPd#pi$&wL<$;VqBrml_02u9wkHmW(4=U^iVFT40 z=@T9T;ukIt&3<+^9EJh=m;>~j0QAmd26*&0rxhq815Hqf%m9?i7OJt59{5$(1*mcj zWTUcRFn{{xYXZz*q0z=nKmi7Bfalt~1@wb-o;g8(uaBCf4$KDX!{GFQ9(kymKseXV z9e`e(*b(&*3qO6Mq01gfGb+!CM=U?*SW#LntkOgbEL; zgUQfMj67q4?h)ob;?s2cN&CkuRS51D)ae49!3oM&vJ`xS0^R@YxHQsn-(&Ya z;x!C2>LED+-nO8M$sl1I$3{x2Yv1H#F&PO>lX2j_%5Q^UhAH!n)T0)n!lCPWe1{G5 z_fgJghYEukYv}uFPzR2xDF8K&v=7ydvhcEw7tUH|N0NMtM+Z00!$OaK4?0d!JM zQvg8b*k%9#0mDf|K~zY`z17Q4gD@Ni@XiDAxMnWS8|j7ZNGi!Z8sQIE;HP-w;*3X^ zDA{d~kZ^U0F8sH)P#$GvV5WAXd+^!%?E{L?e-$l2bEM7DJby?l{vr4QI3vV(CE><+ zBjIiml841OB$o?9bOA^cAjKaL+I zS_N(fqV3er0Q&}4fU20k2DIl8)BwWwAO%Y`z_3pN>fx;#P`o;t0b|lHfd#cvf-Iy8 zNFkL#gglR9HGi1Kt7-<@b^ttfuvhj>Gw3p~*~#?qB#?)+u?-&o8+P7v6d`TOz}+MgL+7>0k8~y3fRNrtrsBL%OMXy9`Yse)gu`tUqQ=k z>Zl9In_xWkU|#SgdE&nRJ&OW6bJ0L41e$eSBzu{YpB2BJJ>6GrWq_5xa?$l=n}|7y4VGs{|!5@JBpAlW#ImS z0dNG!LTXPC=S>Iz8HnK`sD;d$$U!}%-T+tzGX?CS^VSOx?d6aMAP@PHnCg*?lCPj; zHZ9ZzY`DB$%h>&8k z%8@L9Bdy6|!IZslq*a|~&<{Y@k!)S|trt$~rFZP=wooU~8#UfQ2B^y|sPQHyg~ZBI zhV)MccxYhohbE5JV^;M?fW3YN_$gR30R15_Fs=dBhH_hOD6Lyk`=)mKzWK-H8)Jua VkVdo)unYhI002ovPDHLkV1ij>_J;rf diff --git a/techage/textures/techage_reactor_filler_plan.png b/techage/textures/techage_reactor_filler_plan.png index f60cfac1987a07450dd537d681dfde9d6190b029..2fb7a6e0d3b78d1e8c4edcdcd0e57084b774c297 100644 GIT binary patch delta 294 zcmV+>0one61Kt9VL4PVxOjJcja7^Fg=aZPB)7Rd|%hR*9x{ZvC>g({kzs8@XujRi% z_0?1W004}1Cg+|(C;$Ke0d!JMQvg8b*k%9#0L4i}K~yNurH-);gD?<9ZwFkYwnX6) zg(9tmqNGM4@&chs2a+d1N==a=U<1=I$@;%VZjN3)JYxy z8cOPhjBEy1YxFCC#%hDvO2V8`c+j{}5e!Bf5<&G+%P3ab&Ve#)pmB)|r9y?Lh17XB s8*HhxH2UV&z#k17?PnmQzk>hZKi2z=9>A8!{r~^~07*qoM6N<$f||aCWdHyG delta 329 zcmV-P0k;0$0)YdNL4OHwNLh0L01FcU01FcV0GgZ_0000jP)t-sOlfhmwYuu-@ROLK zjf{-P%hS`>-k+tfyT8WY;^+0%RO!P+0001tbSB%jJ$$48_5c6?0d!JMQvg8b*k%9# z0MJQ9K~yNurH(NUf-n?@dj*0fotwbkFmyIybkTv>17Kvv3xBYAffCQqCLRD1Ca>WI z7(9cI@>(dXe>#2n-Uo!hO#kiSb;Rw1^T1eP7|F6uYeUu>wdq4J=vM$bvjst;iEtFb zF2@}lcpr27{sG%)_*q`_YsH@Qw69pJVnKujHvp2gr3YY8$x<|`30$mEuK*aUhO#HE znvKGNbdCydC_-#V0MUakQ8-DO8N|K;#yK!3^(PrGBrBWAP|Hbz(R*eMXkfISfROGh b@E_R^=Uau)5L2$K00000NkvXXu0mjfT11f* diff --git a/techage/textures/techage_reactor_side.png b/techage/textures/techage_reactor_side.png index 21ddebfab80d7c01af1956b72bed66a5ea9856ba..9ac7a5333c50ea63561378aca13aefdb199f7481 100644 GIT binary patch delta 3138 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 delta 3203 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 diff --git a/techage/textures/techage_server2_back.png b/techage/textures/techage_server2_back.png new file mode 100644 index 0000000000000000000000000000000000000000..c91583d0c4f3474cfa682f16c22b100a23e617e8 GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv#Q>iWS0J60lXLs=)BpefZ`-t~ zva&KfIJmE`Z_b=KKX<3h4i3Iy7<3Y-XqBgnV~EE2)`=T=4=V_``sXw(eQl_n(yCJs z^kO2n_&eo2j+>gcpN&;GAus-u_2}o%*AHH-Yu=N|V9m+o&*`Y-b|8EL$D|XxE4)5% zT}!>>Vy(vdXj|$v7wcyV;fEIoS}!nJDVS_taw+SN)sDtI1JUWL1Fh$A%_`j9vZ(p# zQ;S`STw$ztH+UMKOKhvnZu?(0r>Dx&KtjI5^8@GC>4p0L4{q>U$lhd>_x~&7(%z_> UinHumfv#roboFyt=akR{0ELxsApigX literal 0 HcmV?d00001 diff --git a/techage/textures/techage_server2_front.png b/techage/textures/techage_server2_front.png new file mode 100644 index 0000000000000000000000000000000000000000..973849e58587d6d7287a367ca82389a5e1782fa1 GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!VDxk7i7!@Qi=gSA+GKPKH1sXEmLP-zkVGk zcKh+u%F4>VzP_-4fCUQ{guDs4TcY zphSz@k$|U0yY2F)Yrj2aej>Nxt&W#@ Q7SNRpp00i_>zopr0EVJ$4FCWD literal 0 HcmV?d00001 diff --git a/techage/textures/techage_server2_side.png b/techage/textures/techage_server2_side.png new file mode 100644 index 0000000000000000000000000000000000000000..0887f4b438f75dafccf000bf1a15722c12644f7e GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*UHMuf`WpqtSla8=Kufy zCnqQS`v-UfWhZ#LIEHAP@11y&lf{sO)l}3)`@(gnC=XWl+x6Phb8Z-WScH9gd|*MP z&|(LD^A6|LQWH0HGp*TScgSUL|M!rcyLs32F<(*6*!s8Y77ND%$pxxQ_k?({O*@`< zpzlB?i=vq`V zFr8%a6PTvNIptAWA) Rc|Z#oJYD@<);T3K0RZjfJ7WL< literal 0 HcmV?d00001 diff --git a/techage/textures/techage_smoke.png b/techage/textures/techage_smoke.png index 58a1648337f6e212aa8999c5fa0bf50d483b06bf..344982cdcbf545432fe133a8eee66b7164e94573 100644 GIT binary patch delta 193 zcmV;y06zbK0{;P!Mt{%G(noMi$jHpm(bUMu%gM>i&d$=p!No}IM$G^K00?waPE(cu zGk;}Hs1@3qm^%Of0C7n~K~xCWEzPkC!axv3(F+UqqlDC}e!$`j_Ax8gk%;Y7_L9Qi zcUFAOoMyO~^&g92{i5shZEP16bPr4gUd(i78b;Plz_~Rfi8>Pp#lgph;`q$5N~!!% v2xL~_X(F?W-&8L~Cuex%NbGBCM*Y7p-&z#3%|Aa|00000NkvXXu0mjf>_S~h delta 194 zcmV;z06qWz0e}LKMt{-K)X&e-M{rEY$jiye%*e>h&d$=p!No^jG_U{w00?waPE&uD z0A)@ys1-@L%xeGu0CGu0K~xCWEzPkC!axv3(YuJ&`x6RS@8p=u?{x* zeP_kj%xQ*;S-)Bg>t{~)k8zwy;uaW^^wvbTQ^Uxz54bK389TJ)K^*zmkX%0TERfR= wNrB3OJP)cI;y0JG(c#)$Yb0K!HKYFD7l`2$Kb4pR9{>OV07*qoM6N<$g263XL;wH) diff --git a/techage/textures/techage_solar_cell_mini_side.png b/techage/textures/techage_solar_cell_mini_side.png index 43eef9ea574974105e8c8b5db526e6e746bf2fc3..5cd0274c608f968c5257eb5092b639b050d1a9d6 100644 GIT binary patch delta 96 zcmb;|o*)s;#LU3Jz$Y>(9Z2y6_=LDdMMbS&yCy9)rM0f0p$UZB!3%FOjJcja7^WsI_afb;h$IBjyTeXHNJZ??6h{EaXvY^5b6K` z00DGTPE!Ct=GbNc004MNL_t(2&tqU1cA!Yf*hmQk+Mxo9fr1c_1QQ5x6ciM34M7M5 zinuxoiV6xQLj?>SLFz?95CTvmkOTtJ1suhVjg*az;MNK{3PPbc%urMiO+Z9Y7*#+J zLqLR}fGC=wj%cpF0d!JMQvg8b*k%9#0D?(GK~yNuV_+C|ph>CNNC^aHKm`;71tA~_CJ^E%C@A6@ zf)EH4adi|F6%^{_vM00001bW%=J z06^y0W&i*HLPJ5FNy$kKML7I#F{*&0ps@&=fT0k60Yyv!L3Awy v-C>BSLWD%WI1+RRhCnC~par24GzbBRO$s`FK-uB|0000 zBm_-`NT4v98G%A*);bEI8R|&L9f4>9ijIP)?l5$8M6*`V5hmT~gxrA<9_2uQ8iYzp b8W0u$MbQqF%aSSO00000NkvXXu0mjf^+Z8# diff --git a/techage/textures/techage_ta3b.png b/techage/textures/techage_ta3b.png index 4fcb2b028acaf89a548bcbe63507377210e799de..24cf11e940d4abec6b911e77c2a4f0e5b0069c37 100644 GIT binary patch delta 3773 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 nZ}1)(S1w0e_s3CBU{d@KKv_I delta 3797 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 KT-G@yGywo*=^O3< literal 0 HcmV?d00001 diff --git a/techage/textures/techage_trowel.png b/techage/textures/techage_trowel.png index 87b90bdd87b22221e492e44fc6077dc2e87f5077..11d7941b0a3a11a1b6aee5c44fb8e954e53976cb 100644 GIT binary patch delta 261 zcmZ3PdzP=dQ8$S;`T_fy%I68dIWekPXm+-?R6_jtNEhG?8m zPHqi=%xE{0ih zIZP|s?%dPWxODN6K!c8g#tWqrU5irRv${7js5I^tmyi(j%6q?e_OxRM6ga|*${A-Q z30gLb@ie9LE%-3={2a@cRgcchH0ILKnsH&O#ET1Voctb~3?6@cP6RPk%>+7(!PC{x JWt~$(69B5~X!`&F delta 282 zcmbQrw32CpL_G&H0|SH0lCEPw$}+$w#MQmP=T*MW|NsA2Y}$H1P7R2FGThwUyzHzH zHjtdYVDYl`n}KS8j0bUQ`;Q!3yJefu+^i2krTis9e!&c?W~R?2C4RqrcIDLzT4C4eLX1KG%zgg?|4%^Q!3NX&rq53Tj=UXJR+Uf&sDbe+Y7)aF5)o!#jyMDw$B`GnE(6-V zI!scS>>i+UFql*ixgo?fUfglxpeZE+a6l&4EYP^aq=vglr3U)T%j&2s3s{>D+Tv1e zd;mOy4~}s1=B%})+hx$!{zOZ+H0l2;xbl!E?;~8^*hlDTQ(JTc3`Lr@@WD|ewXi$# qcd458bXc|(fS3=<9`XYRnjL?V99=qeas6}v0000Ix$HpIQZyD2ZQ%g)Nh#>20%uIcOOIzO_Vo}44D0!07-00DGTPE!Ct z=GbNc009_DL_t(2&t=jvOT;h~2k`e5(m=)Wnx%(R5)ivLn`2ZE9g5hqZZ0a0Culo* z1!+Hk3WpBonpGF6ql@@qd}%po+BcN&h!h!9sU8RB#68`6*C0@00001^@s5 z0{r}$l9acsvqEEit^fc40d!JMQvg8b*k%9#0G>%iK~yNuV_?7zlz;$*ua=gUge1bG zC@7XDD5QW;ASff2Xqbjjq9`aMC}=1sgb+v+6iiD?LkK7vgLi=uTtL-FS4rv8ksv$* zj)FiSf+XPLBI4-cf=`8`p`oxa)dkRP@i1(*5N<&da0dyLAPa!Bpa@j2TD2XYfRYDD zpal`8Od1`#cXu=?AjOMDd3lo(zL;Z}FoBbkL;(Z<0G69HMYIVWY5)KL07*qoM6N<$ Eg4bwcl>h($ delta 233 zcmV1)~K@lxbOjJcja7@t4$IZyV6%h`RPa%IL6j*@(0007XQchC<0Rfc# zt^fc4qDe$SR4C75V89NPfB=QBUS3{=B*LU@Y+i0`q<~OhY-?WZTn<;lsBCO&Z0u}o zgb*k;HZCtOM+m66fPoWSKrL2JN$JtmB%A^aYQe@pV2UIV8fqFG8j4Rvu(Pv?3DpJA zZHaWA;%QAX6-gihB+!a10MddY(6f8@348)dksyJoh+t>ZoO$NV%t;DJ@uJz$F-Zwu j%rPujz{N$P00IC254u8t5_5Is00000NkvXXu0mjfg=@3642jIX+?0000AbW%=J0RR90?yh5IF*xR3Q=W-S0AMe$3-%(BUI01@Hd5Ob zvGyA9ZrE=Jh<_=A?Zxa=^EbvIvj!vWLFP4-j5`243g8nU%R|1xp%JKHv#iEUw8aBw zzdQ>C;7-8Ln<#|_HS`{{WGtgzSiRQ3?E<#1cN4Wisam}UmdF@St3ir3(*|IoAh$9_ zy>F`reJZ;RuoBpuqcq|JU?b&R0CMjV(3%LeA^}M+@P7axdBK(*rfh-6UIInWqNYP=J-D5Tv-eWE z0Y?DR1Ak`k=P%a8t4!qo_`is!Y2<}z_W>MRmN4m5e+&edcoz> z1HwxakFCd)N80A|ekQ)PJ2xgaW&K@1SJsD@^IuII-P*poxw?D1b6ZsP1;dQLVA$gY z<9<#21{3$!JUbB0YhE?c4goG^;Vz&I%~U}xT&IBUlZRXatZm!w0=r<9SAg@t;kpBd zb2Ht6!zFup;Mk5#H;z2_4_!ZhZX7uJ&hs~)w}0P^KZ7Ym8W2h43IG5A07*qoM6N<$ Ef&n0O)c^nh delta 1001 zcmVm+*@-i|qCMG7u$IZ;p+659c#md<;WBJe1;x#oj zo12@?&(djWX#fEM{{H>~0tgBU3W|z~Mn*p;+fpaVe%g7Wf)AxL9{jT*px zu#x%@W9)l@(0^03q33A(UI8S8VMEXx1Q8oWxj<4XQ=TCaDZ?2+gf~@xAW%w~Oa?7@ zhD2!w21@}$S>Dn5lt3A?12e<+iP4`Cyc!1r5yhBG?M3W<^{;bQW-Y(UfB|HgS6dvn z5JaLY1SIpi63Ft9Tm146Kmg!sSzQtl1Q1bgXF**g2!Bt49}+QN6ERmJN}^!|p2aBM zjpE%HHU39%jNqo}_C#RNpk%GBh}wIl8b7M!ogVx|5WTnZXz&it^(tcRL|L1Y#YRth z7@RbU3xRfi7z_yH*^@vr!kLKVNA~2U7il3#z8QTkJs4JkPeo+!>|i{(ffs_tGszg_ zlH_M5*nc?jalaE$;-c_?0k}pwJrnTYt(A6iX`EXKSTYRV?~kLA926V9Z}64yZ1yEY z8qe&%5>YAnGJ@^|#U+|X+G;M7!ZMssMT|L>iR3I|&Z!NNa4k17eigD}AxO0bZ#%JV z8cE%A4_+ilS(-FW`>oYDrL^$W;7GtpCe>hd0DtBWKU2<*;4p+nTstHdLx>BUpYKHZqHqt@FdL@`wcIQgOXIg)G zEo{>I)#d!Ph*!6^BH}kT*Y3M#q+?RW0YhCsVCdz5@%9m?64AUACnCNZ8!MDTUd_3!J(3#D({{&2nh>&^HL Xg282_q-XZK00000NkvXXu0mjfeB8!S diff --git a/techage/tools/trowel.lua b/techage/tools/trowel.lua index a44e590..6e895fe 100644 --- a/techage/tools/trowel.lua +++ b/techage/tools/trowel.lua @@ -89,7 +89,7 @@ minetest.register_tool("techage:trowel", { description = S("TechAge Trowel"), inventory_image = "techage_trowel.png", wield_image = "techage_trowel.png", - use_texture_alpha = true, + use_texture_alpha = techage.CLIP, groups = {cracky=1}, on_use = replace_node, on_place = replace_node, diff --git a/techage/wind_turbine/rotor.lua b/techage/wind_turbine/rotor.lua index 83f6aa7..60cdaf6 100644 --- a/techage/wind_turbine/rotor.lua +++ b/techage/wind_turbine/rotor.lua @@ -29,8 +29,6 @@ local power = techage.power local Rotors = {} -local MAX_NUM_FOREIGN_NODES = 50 - local Face2Dir = {[0]= {x=0, y=0, z=1}, {x=1, y=0, z=0}, @@ -48,44 +46,51 @@ local function pos_and_yaw(pos, param2) return pos, {x=0, y=yaw, z=0} end -local function add_rotor(pos, nvm, player_name) - nvm.error = false - - if not techage.valid_place_for_windturbine(pos, nil, 1) then +local function check_rotor(pos, nvm) + local resp, err = techage.valid_place_for_windturbine(pos, nil, 1) + if not resp then + M(pos):set_string("infotext", S("TA4 Wind Turbine")..": "..err) nvm.error = true - M(pos):set_string("infotext", S("TA4 Wind Turbine")..": "..S("Not suitable position!")) - return + return false end - local hash = minetest.hash_node_position(pos) - if not Rotors[hash] then - local node = minetest.get_node(pos) - local npos, yaw = pos_and_yaw(pos, node.param2) - local obj = minetest.add_entity(npos, "techage:rotor_ent") - obj:set_animation({x = 0, y = 119}, 0, 0, true) - obj:set_rotation(yaw) - Rotors[hash] = obj - end - - local own_num = M(pos):get_string("node_number") or "" - M(pos):set_string("infotext", S("TA4 Wind Turbine").." "..own_num) -end - -local function start_rotor(pos, nvm) local npos = techage.get_pos(pos, "F") local node = techage.get_node_lvm(npos) if node.name ~= "techage:ta4_wind_turbine_nacelle" then M(pos):set_string("infotext", S("TA4 Wind Turbine").." "..S("Nacelle is missing")) nvm.error = true - return + return false end - nvm.providing = true - nvm.delivered = 0 - power.generator_start(pos, Cable, CYCLE_TIME, 5) - local hash = minetest.hash_node_position(pos) - if Rotors[hash] then - Rotors[hash]:set_animation_frame_speed(50) + local own_num = M(pos):get_string("node_number") or "" + M(pos):set_string("infotext", S("TA4 Wind Turbine").." "..own_num) + nvm.error = false + return true +end + +local function add_rotor(pos, nvm) + if check_rotor(pos, nvm) then + local hash = minetest.hash_node_position(pos) + if not Rotors[hash] then + local node = minetest.get_node(pos) + local npos, yaw = pos_and_yaw(pos, node.param2) + local obj = minetest.add_entity(npos, "techage:rotor_ent") + obj:set_animation({x = 0, y = 119}, 0, 0, true) + obj:set_rotation(yaw) + Rotors[hash] = obj + end + end +end + +local function start_rotor(pos, nvm) + if not nvm.error then + nvm.providing = true + nvm.delivered = 0 + power.generator_start(pos, Cable, CYCLE_TIME, 5) + local hash = minetest.hash_node_position(pos) + if Rotors[hash] then + Rotors[hash]:set_animation_frame_speed(50) + end end end @@ -126,14 +131,22 @@ local function after_place_node(pos, placer) local own_num = techage.add_node(pos, "techage:ta4_wind_turbine") meta:set_string("node_number", own_num) meta:set_string("owner", placer:get_player_name()) - meta:set_string("infotext", S("TA4 Wind Turbine").." "..own_num) nvm.providing = false nvm.running = true - add_rotor(pos, nvm, placer:get_player_name()) + add_rotor(pos, nvm) minetest.get_node_timer(pos):start(CYCLE_TIME) Cable:after_place_node(pos) end +local function on_punch(pos, node, puncher, pointed_thing) + if minetest.is_protected(pos, puncher:get_player_name()) then + return + end + + local nvm = techage.get_nvm(pos) + add_rotor(pos, nvm) +end + local function after_dig_node(pos, oldnode, oldmetadata) local hash = minetest.hash_node_position(pos) if Rotors[hash] and Rotors[hash]:get_luaentity() then @@ -167,12 +180,14 @@ minetest.register_node("techage:ta4_wind_turbine", { sides = {D = 1}, ntype = "gen1", nominal = PWR_PERF, + regenerative = true, }, }, after_place_node = after_place_node, after_dig_node = after_dig_node, tubelib2_on_update2 = tubelib2_on_update2, on_timer = node_timer, + on_punch = on_punch, paramtype2 = "facedir", groups = {cracky=2, crumbly=2, choppy=2}, is_ground_content = false, diff --git a/techage/wind_turbine/signallamp.lua b/techage/wind_turbine/signallamp.lua index 8bd29d3..a1be480 100644 --- a/techage/wind_turbine/signallamp.lua +++ b/techage/wind_turbine/signallamp.lua @@ -52,6 +52,7 @@ minetest.register_node("techage:rotor_signal_lamp_off", { end, paramtype = "light", + use_texture_alpha = techage.CLIP, paramtype2 = "glasslikeliquidlevel", sunlight_propagates = true, sounds = default.node_sound_glass_defaults(), @@ -77,6 +78,7 @@ minetest.register_node("techage:rotor_signal_lamp_on", { end, paramtype = "light", + use_texture_alpha = techage.CLIP, light_source = 8, paramtype2 = "glasslikeliquidlevel", sunlight_propagates = true, diff --git a/techpack_stairway/COPYING.txt b/techpack_stairway/COPYING.txt index a0c8d14..ac8785d 100644 --- a/techpack_stairway/COPYING.txt +++ b/techpack_stairway/COPYING.txt @@ -1,6 +1,6 @@ The stairway mod for Minetest is -Copyright (C) 2017-2018 Joachim Stolberg +Copyright (C) 2017-2021 Joachim Stolberg License of source code ---------------------- diff --git a/techpack_stairway/init.lua b/techpack_stairway/init.lua index 91c7716..c995e72 100644 --- a/techpack_stairway/init.lua +++ b/techpack_stairway/init.lua @@ -12,7 +12,11 @@ ]]-- -S = minetest.get_translator("techpack_stairway") +local S = minetest.get_translator("techpack_stairway") + +-- Test MT 5.4 new string mode +local CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or false + minetest.register_node("techpack_stairway:grating", { description = S("TechPack Grating"), @@ -39,6 +43,7 @@ minetest.register_node("techpack_stairway:grating", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -70,6 +75,7 @@ minetest.register_node("techpack_stairway:handrail1", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -103,6 +109,7 @@ minetest.register_node("techpack_stairway:handrail2", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -136,6 +143,7 @@ minetest.register_node("techpack_stairway:handrail3", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -171,6 +179,7 @@ minetest.register_node("techpack_stairway:handrail4", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -203,6 +212,7 @@ minetest.register_node("techpack_stairway:bridge1", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -236,6 +246,7 @@ minetest.register_node("techpack_stairway:bridge2", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -269,6 +280,7 @@ minetest.register_node("techpack_stairway:bridge3", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -303,6 +315,7 @@ minetest.register_node("techpack_stairway:bridge4", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -341,6 +354,7 @@ minetest.register_node("techpack_stairway:stairway", { --climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -372,6 +386,7 @@ minetest.register_node("techpack_stairway:ladder1", { climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -404,6 +419,7 @@ minetest.register_node("techpack_stairway:ladder2", { climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -436,6 +452,7 @@ minetest.register_node("techpack_stairway:ladder3", { climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -466,6 +483,7 @@ minetest.register_node("techpack_stairway:ladder4", { climbable = true, paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -498,6 +516,7 @@ minetest.register_node("techpack_stairway:lattice", { paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -546,6 +565,7 @@ minetest.register_node("techpack_stairway:lattice_slop", { paramtype2 = "facedir", paramtype = "light", + use_texture_alpha = CLIP, sunlight_propagates = true, is_ground_content = false, groups = {cracky = 2}, @@ -553,7 +573,7 @@ minetest.register_node("techpack_stairway:lattice_slop", { }) minetest.register_craft({ - output = "techpack_stairway:grating 4", + output = "techpack_stairway:grating 6", recipe = { {"", "", ""}, {"dye:dark_grey", "", "default:coal_lump"}, @@ -562,7 +582,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:handrail1 4", + output = "techpack_stairway:handrail1 6", recipe = { {"default:steel_ingot", "default:coal_lump", ""}, {"default:tin_ingot", "", ""}, @@ -571,7 +591,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:stairway 2", + output = "techpack_stairway:stairway 3", recipe = { {"", "", "default:steel_ingot"}, {"dye:dark_grey", "default:tin_ingot", "default:coal_lump"}, @@ -580,7 +600,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:ladder1 2", + output = "techpack_stairway:ladder1 3", recipe = { {"", "default:steel_ingot", ""}, {"dye:dark_grey", "default:tin_ingot", "default:coal_lump"}, @@ -589,7 +609,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:ladder3 4", + output = "techpack_stairway:ladder3 6", recipe = { {"", "", "default:steel_ingot"}, {"dye:dark_grey", "default:tin_ingot", "default:coal_lump"}, @@ -598,7 +618,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:ladder4 8", + output = "techpack_stairway:ladder4 12", recipe = { {"dye:dark_grey", "default:tin_ingot", "default:coal_lump"}, {"", "default:steel_ingot", ""}, @@ -607,7 +627,7 @@ minetest.register_craft({ }) minetest.register_craft({ - output = "techpack_stairway:lattice 2", + output = "techpack_stairway:lattice 4", recipe = { {"default:steel_ingot", "", "default:steel_ingot"}, {"dye:dark_grey", "default:tin_ingot", "default:coal_lump"}, diff --git a/techpack_stairway/readme.md b/techpack_stairway/readme.md index 929bce9..e5cb43b 100644 --- a/techpack_stairway/readme.md +++ b/techpack_stairway/readme.md @@ -7,7 +7,7 @@ Ladders, stairways, and bridges for your machines ### License -Copyright (C) 2018-2020 Joachim Stolberg +Copyright (C) 2018-2021 Joachim Stolberg Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt @@ -19,3 +19,4 @@ none - 2020-04-26 v1.00 * extracted from TechPack and released - 2020-10-08 v1.01 * Lattice Slope node added - 2020-10-10 v1.02 * German translation added +- 2021-03-20 v1.03 * Adapted for MT 5.4 diff --git a/towercrane/README.md b/towercrane/README.md index 3a50dd9..a25e487 100644 --- a/towercrane/README.md +++ b/towercrane/README.md @@ -33,8 +33,9 @@ default # License Copyright (C) 2017-2020 Joachim Stolberg Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt -Textures: CC0 (by Ammoth) +Textures: Mostly CC0 (by Ammoth) + * `morelights_extras_blocklight.png`: CC BY-SA 4.0 (by random-geek) # History: * 2017-06-04 v0.01 first version diff --git a/towercrane/config.lua b/towercrane/config.lua index 0ee5296..1c756f0 100644 --- a/towercrane/config.lua +++ b/towercrane/config.lua @@ -11,13 +11,13 @@ -- Maximum crane height in blocks (8..n) -towercrane.max_height = tonumber(minetest.setting_get("towercrane_max_height")) or 32 +towercrane.max_height = tonumber(minetest.settings:get("towercrane_max_height")) or 32 -- Maximum crane width in blocks (8..n) -towercrane.max_width = tonumber(minetest.setting_get("towercrane_max_width")) or 32 +towercrane.max_width = tonumber(minetest.settings:get("towercrane_max_width")) or 32 -- Crane rope lenght in block (max_height .. max_height+x) -towercrane.rope_length = tonumber(minetest.setting_get("towercrane_rope_length")) or 40 +towercrane.rope_length = tonumber(minetest.settings:get("towercrane_rope_length")) or 40 -- Recipe available (true/false) -towercrane.recipe = tonumber(minetest.setting_get("towercrane_recipe")) or true +towercrane.recipe = tonumber(minetest.settings:get("towercrane_recipe")) or true diff --git a/towercrane/init.lua b/towercrane/init.lua index 6cc0ae6..71f20fb 100644 --- a/towercrane/init.lua +++ b/towercrane/init.lua @@ -9,7 +9,7 @@ Nodes Meta data - +--------+ + +--------+ | | - last_known_pos as "(x,y,z)" | switch | - last_used | | - running @@ -26,7 +26,7 @@ local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos -- crane minimum size -local MIN_SIZE = 8 +local MIN_SIZE = 8 towercrane = {} @@ -53,7 +53,7 @@ local function formspec(height, width) return "size[5,4]".. "label[0,0;"..S("Construction area size").."]" .. "field[1,1.5;3,1;size;height,width;"..text.."]" .. - "button_exit[1,2;2,1;exit;"..S("Build").."]" + "button_exit[1,2;2,1;exit;"..S("Build").."]" end local function get_node_lvm(pos) @@ -154,7 +154,7 @@ end local function construct_crane(pos, dir, height, width) local add = function(pos, node_name, tArg) minetest.add_node(pos, { - name = node_name, + name = node_name, param2 = minetest.dir_to_facedir(tArg.dir)}) end local tArg = {dir = dir} @@ -292,7 +292,7 @@ minetest.register_node("towercrane:base", { return true end, - -- evaluate user input (height, width), + -- evaluate user input (height, width), -- destroy old crane and build a new one with -- the given size on_receive_fields = function(pos, formname, fields, player) @@ -324,7 +324,7 @@ minetest.register_node("towercrane:base", { end return true end, - + on_destruct = function(pos) towercrane.get_crane_down(pos) end, @@ -333,10 +333,11 @@ minetest.register_node("towercrane:base", { minetest.register_node("towercrane:balance", { description = S("Tower Crane Balance"), tiles = { - "towercrane_base.png^towercrane_screws.png", + "towercrane_base.png^towercrane_screws.png^morelights_extras_blocklight.png", }, paramtype = "light", paramtype2 = "facedir", + light_source = 12, sunlight_propagates = true, is_ground_content = false, groups = {crumbly=0, not_in_creative_inventory=1}, @@ -411,4 +412,3 @@ towercrane.turnright = turnright towercrane.turnleft = turnleft towercrane.is_my_crane = is_my_crane towercrane.get_crane_data = get_crane_data - diff --git a/towercrane/textures/morelights_extras_blocklight.png b/towercrane/textures/morelights_extras_blocklight.png new file mode 100644 index 0000000000000000000000000000000000000000..953c2a9c4c339cb6d6446529e155d2c4c3b1d802 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!aez;VYe=j@TSse0S9^DNQ+IDy zZ+}n!#J-7BCQh9`B|zW(v;jqh(CeERS}aBD>&P&;EukY6x^ z!?PP{K#seoi(`m{bP0l+XkKAFfg| literal 0 HcmV?d00001 diff --git a/unified_inventory/.luacheckrc b/unified_inventory/.luacheckrc index 9fb6a7c..e6fec97 100644 --- a/unified_inventory/.luacheckrc +++ b/unified_inventory/.luacheckrc @@ -14,6 +14,7 @@ read_globals = { "ItemStack", "datastorage", "hb", + "doors", } files["callbacks.lua"].ignore = { "player", "draw_lite_mode" } diff --git a/unified_inventory/README.md b/unified_inventory/README.md index 8c917ec..d159c02 100644 --- a/unified_inventory/README.md +++ b/unified_inventory/README.md @@ -24,7 +24,7 @@ Unified Inventory replaces the default survival and creative inventory. ## Requirements - * Minetest 5.0.0+ + * Minetest 5.4.0+ # Licenses @@ -96,4 +96,4 @@ Other files from Wikimedia Commons: RealBadAngel: (CC-BY-4.0) - * Everything else. \ No newline at end of file + * Everything else. diff --git a/unified_inventory/api.lua b/unified_inventory/api.lua index 81dd8f3..22788ae 100644 --- a/unified_inventory/api.lua +++ b/unified_inventory/api.lua @@ -1,5 +1,6 @@ local S = minetest.get_translator("unified_inventory") local F = minetest.formspec_escape +local ui = unified_inventory -- Create detached creative inventory after loading all mods minetest.after(0.01, function() @@ -8,12 +9,12 @@ minetest.after(0.01, function() if not rev_aliases[target] then rev_aliases[target] = {} end table.insert(rev_aliases[target], source) end - unified_inventory.items_list = {} + ui.items_list = {} for name, def in pairs(minetest.registered_items) do if (not def.groups.not_in_creative_inventory or def.groups.not_in_creative_inventory == 0) and def.description and def.description ~= "" then - table.insert(unified_inventory.items_list, name) + table.insert(ui.items_list, name) local all_names = rev_aliases[name] or {} table.insert(all_names, name) for _, player_name in ipairs(all_names) do @@ -26,30 +27,30 @@ minetest.after(0.01, function() for _,chk in pairs(recipe.items) do local groupchk = string.find(chk, "group:") if (not groupchk and not minetest.registered_items[chk]) - or (groupchk and not unified_inventory.get_group_item(string.gsub(chk, "group:", "")).item) + or (groupchk and not ui.get_group_item(string.gsub(chk, "group:", "")).item) or minetest.get_item_group(chk, "not_in_craft_guide") ~= 0 then unknowns = true end end if not unknowns then - unified_inventory.register_craft(recipe) + ui.register_craft(recipe) end end end end end end - table.sort(unified_inventory.items_list) - unified_inventory.items_list_size = #unified_inventory.items_list - print("Unified Inventory. inventory size: "..unified_inventory.items_list_size) - for _, name in ipairs(unified_inventory.items_list) do + table.sort(ui.items_list) + ui.items_list_size = #ui.items_list + print("Unified Inventory. inventory size: "..ui.items_list_size) + for _, name in ipairs(ui.items_list) do local def = minetest.registered_items[name] -- Simple drops if type(def.drop) == "string" then local dstack = ItemStack(def.drop) if not dstack:is_empty() and dstack:get_name() ~= name then - unified_inventory.register_craft({ + ui.register_craft({ type = "digging", items = {name}, output = def.drop, @@ -115,7 +116,7 @@ minetest.after(0.01, function() end end for itemstring, count in pairs(drop_guaranteed) do - unified_inventory.register_craft({ + ui.register_craft({ type = "digging", items = {name}, output = itemstring .. " " .. count, @@ -123,7 +124,7 @@ minetest.after(0.01, function() }) end for itemstring, count in pairs(drop_maybe) do - unified_inventory.register_craft({ + ui.register_craft({ type = "digging_chance", items = {name}, output = itemstring .. " " .. count, @@ -132,22 +133,22 @@ minetest.after(0.01, function() end end end - for _, recipes in pairs(unified_inventory.crafts_for.recipe) do + for _, recipes in pairs(ui.crafts_for.recipe) do for _, recipe in ipairs(recipes) do local ingredient_items = {} for _, spec in pairs(recipe.items) do - local matches_spec = unified_inventory.canonical_item_spec_matcher(spec) - for _, name in ipairs(unified_inventory.items_list) do + local matches_spec = ui.canonical_item_spec_matcher(spec) + for _, name in ipairs(ui.items_list) do if matches_spec(name) then ingredient_items[name] = true end end end for name, _ in pairs(ingredient_items) do - if unified_inventory.crafts_for.usage[name] == nil then - unified_inventory.crafts_for.usage[name] = {} + if ui.crafts_for.usage[name] == nil then + ui.crafts_for.usage[name] = {} end - table.insert(unified_inventory.crafts_for.usage[name], recipe) + table.insert(ui.crafts_for.usage[name], recipe) end end end @@ -156,9 +157,9 @@ end) -- load_home local function load_home() - local input = io.open(unified_inventory.home_filename, "r") + local input = io.open(ui.home_filename, "r") if not input then - unified_inventory.home_pos = {} + ui.home_pos = {} return end while true do @@ -167,25 +168,31 @@ local function load_home() local y = input:read("*n") local z = input:read("*n") local name = input:read("*l") - unified_inventory.home_pos[name:sub(2)] = {x = x, y = y, z = z} + ui.home_pos[name:sub(2)] = {x = x, y = y, z = z} end io.close(input) end load_home() -function unified_inventory.set_home(player, pos) +function ui.set_home(player, pos) local player_name = player:get_player_name() - unified_inventory.home_pos[player_name] = vector.round(pos) + ui.home_pos[player_name] = vector.round(pos) + -- save the home data from the table to the file - local output = io.open(unified_inventory.home_filename, "w") - for k, v in pairs(unified_inventory.home_pos) do + local output = io.open(ui.home_filename, "w") + if not output then + minetest.log("warning", "[unified_inventory] Failed to save file: " + .. ui.home_filename) + return + end + for k, v in pairs(ui.home_pos) do output:write(v.x.." "..v.y.." "..v.z.." "..k.."\n") end io.close(output) end -function unified_inventory.go_home(player) - local pos = unified_inventory.home_pos[player:get_player_name()] +function ui.go_home(player) + local pos = ui.home_pos[player:get_player_name()] if pos then player:set_pos(pos) return true @@ -194,7 +201,7 @@ function unified_inventory.go_home(player) end -- register_craft -function unified_inventory.register_craft(options) +function ui.register_craft(options) if not options.output then return end @@ -205,10 +212,10 @@ function unified_inventory.register_craft(options) if options.type == "normal" and options.width == 0 then options = { type = "shapeless", items = options.items, output = options.output, width = 0 } end - if not unified_inventory.crafts_for.recipe[itemstack:get_name()] then - unified_inventory.crafts_for.recipe[itemstack:get_name()] = {} + if not ui.crafts_for.recipe[itemstack:get_name()] then + ui.crafts_for.recipe[itemstack:get_name()] = {} end - table.insert(unified_inventory.crafts_for.recipe[itemstack:get_name()],options) + table.insert(ui.crafts_for.recipe[itemstack:get_name()],options) end @@ -219,7 +226,7 @@ local craft_type_defaults = { } -function unified_inventory.craft_type_defaults(name, options) +function ui.craft_type_defaults(name, options) if not options.description then options.description = name end @@ -228,13 +235,13 @@ function unified_inventory.craft_type_defaults(name, options) end -function unified_inventory.register_craft_type(name, options) - unified_inventory.registered_craft_types[name] = - unified_inventory.craft_type_defaults(name, options) +function ui.register_craft_type(name, options) + ui.registered_craft_types[name] = + ui.craft_type_defaults(name, options) end -unified_inventory.register_craft_type("normal", { +ui.register_craft_type("normal", { description = F(S("Crafting")), icon = "ui_craftgrid_icon.png", width = 3, @@ -250,7 +257,7 @@ unified_inventory.register_craft_type("normal", { }) -unified_inventory.register_craft_type("shapeless", { +ui.register_craft_type("shapeless", { description = F(S("Mixing")), icon = "ui_craftgrid_icon.png", width = 3, @@ -265,7 +272,7 @@ unified_inventory.register_craft_type("shapeless", { }) -unified_inventory.register_craft_type("cooking", { +ui.register_craft_type("cooking", { description = F(S("Cooking")), icon = "default_furnace_front.png", width = 1, @@ -273,37 +280,60 @@ unified_inventory.register_craft_type("cooking", { }) -unified_inventory.register_craft_type("digging", { +ui.register_craft_type("digging", { description = F(S("Digging")), icon = "default_tool_steelpick.png", width = 1, height = 1, }) -unified_inventory.register_craft_type("digging_chance", { +ui.register_craft_type("digging_chance", { description = "Digging (by chance)", icon = "default_tool_steelpick.png^[transformFY.png", width = 1, height = 1, }) -function unified_inventory.register_page(name, def) - unified_inventory.pages[name] = def +function ui.register_page(name, def) + ui.pages[name] = def end -function unified_inventory.register_button(name, def) +function ui.register_button(name, def) if not def.action then def.action = function(player) - unified_inventory.set_inventory_formspec(player, name) + ui.set_inventory_formspec(player, name) end end def.name = name - table.insert(unified_inventory.buttons, def) + table.insert(ui.buttons, def) end - -function unified_inventory.is_creative(playername) +function ui.is_creative(playername) return minetest.check_player_privs(playername, {creative=true}) or minetest.settings:get_bool("creative_mode") end + +function ui.single_slot(xpos, ypos, bright) + return string.format("background9[%f,%f;%f,%f;ui_single_slot%s.png;false;16]", + xpos, ypos, ui.imgscale, ui.imgscale, (bright and "_bright" or "") ) +end + +function ui.make_trash_slot(xpos, ypos) + return + ui.single_slot(xpos, ypos).. + "image["..xpos..","..ypos..";1.25,1.25;ui_trash_slot_icon.png]".. + "list[detached:trash;main;"..(xpos + ui.list_img_offset)..","..(ypos + ui.list_img_offset)..";1,1;]" +end + +function ui.make_inv_img_grid(xpos, ypos, width, height, bright) + local tiled = {} + local n=1 + for y = 0, (height - 1) do + for x = 0, (width -1) do + tiled[n] = ui.single_slot(xpos + (ui.imgscale * x), ypos + (ui.imgscale * y), bright) + n = n + 1 + end + end + return table.concat(tiled) +end diff --git a/unified_inventory/bags.lua b/unified_inventory/bags.lua index c61c982..14ac875 100644 --- a/unified_inventory/bags.lua +++ b/unified_inventory/bags.lua @@ -7,27 +7,32 @@ License: GPLv3 local S = minetest.get_translator("unified_inventory") local F = minetest.formspec_escape +local ui = unified_inventory -unified_inventory.register_page("bags", { +ui.register_page("bags", { get_formspec = function(player) local player_name = player:get_player_name() return { formspec = table.concat({ - "background[0.06,0.99;7.92,7.52;ui_bags_main_form.png]", - "label[0,0;" .. F(S("Bags")) .. "]", - "button[0,2;2,0.5;bag1;" .. F(S("Bag @1", 1)) .. "]", - "button[2,2;2,0.5;bag2;" .. F(S("Bag @1", 2)) .. "]", - "button[4,2;2,0.5;bag3;" .. F(S("Bag @1", 3)) .. "]", - "button[6,2;2,0.5;bag4;" .. F(S("Bag @1", 4)) .. "]", + ui.style_full.standard_inv_bg, + ui.single_slot(0.925, 1.5), + ui.single_slot(3.425, 1.5), + ui.single_slot(5.925, 1.5), + ui.single_slot(8.425, 1.5), + "label["..ui.style_full.form_header_x..","..ui.style_full.form_header_y..";" .. F(S("Bags")) .. "]", + "button[0.6125,2.75;1.875,0.75;bag1;" .. F(S("Bag @1", 1)) .. "]", + "button[3.1125,2.75;1.875,0.75;bag2;" .. F(S("Bag @1", 2)) .. "]", + "button[5.6125,2.75;1.875,0.75;bag3;" .. F(S("Bag @1", 3)) .. "]", + "button[8.1125,2.75;1.875,0.75;bag4;" .. F(S("Bag @1", 4)) .. "]", "listcolors[#00000000;#00000000]", - "list[detached:" .. F(player_name) .. "_bags;bag1;0.5,1;1,1;]", - "list[detached:" .. F(player_name) .. "_bags;bag2;2.5,1;1,1;]", - "list[detached:" .. F(player_name) .. "_bags;bag3;4.5,1;1,1;]", - "list[detached:" .. F(player_name) .. "_bags;bag4;6.5,1;1,1;]" + "list[detached:" .. F(player_name) .. "_bags;bag1;1.075,1.65;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag2;3.575,1.65;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag3;6.075,1.65;1,1;]", + "list[detached:" .. F(player_name) .. "_bags;bag4;8.575,1.65;1,1;]" }) } end, }) -unified_inventory.register_button("bags", { +ui.register_button("bags", { type = "image", image = "ui_bags_icon.png", tooltip = S("Bags"), @@ -42,32 +47,31 @@ local function get_player_bag_stack(player, i) end for bag_i = 1, 4 do - unified_inventory.register_page("bag" .. bag_i, { + ui.register_page("bag" .. bag_i, { get_formspec = function(player) local stack = get_player_bag_stack(player, bag_i) local image = stack:get_definition().inventory_image - local fs = { - "image[7,0;1,1;" .. image .. "]", - "label[0,0;" .. F(S("Bag @1", bag_i)) .. "]", - "listcolors[#00000000;#00000000]", - "list[current_player;bag" .. bag_i .. "contents;0,1;8,3;]", - "listring[current_name;bag" .. bag_i .. "contents]", - "listring[current_player;main]" - } local slots = stack:get_definition().groups.bagslots - if slots == 8 then - fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_sm_form.png]" - elseif slots == 16 then - fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_med_form.png]" - elseif slots == 24 then - fs[#fs + 1] = "background[0.06,0.99;7.92,7.52;ui_bags_lg_form.png]" - end + + local formspec = { + ui.style_full.standard_inv_bg, + ui.make_inv_img_grid(0.3, 1.5, 8, slots/8), + "image[9.2,0.4;1,1;" .. image .. "]", + "label[0.3,0.65;" .. F(S("Bag @1", bag_i)) .. "]", + "listcolors[#00000000;#00000000]", + "listring[current_player;main]", + string.format("list[current_player;bag%icontents;%f,%f;8,3;]", + bag_i, 0.3 + ui.list_img_offset, 1.5 + ui.list_img_offset), + "listring[current_name;bag" .. bag_i .. "contents]", + } + local n = #formspec + 1 + local player_name = player:get_player_name() -- For if statement. - if unified_inventory.trash_enabled - or unified_inventory.is_creative(player_name) - or minetest.get_player_privs(player_name).give then - fs[#fs + 1] = "background[6.06,0;0.92,0.92;ui_bags_trash.png]" - .. "list[detached:trash;main;6,0.1;1,1;]" + if ui.trash_enabled + or ui.is_creative(player_name) + or minetest.get_player_privs(player_name).give then + formspec[n] = ui.make_trash_slot(7.8, 0.25) + n = n + 1 end local inv = player:get_inventory() for i = 1, 4 do @@ -84,11 +88,12 @@ for bag_i = 1, 4 do end local img = def.inventory_image local label = F(S("Bag @1", i)) .. "\n" .. used .. "/" .. size - fs[#fs + 1] = string.format("image_button[%i,0;1,1;%s;bag%i;%s]", - i + 1, img, i, label) + formspec[n] = string.format("image_button[%f,0.4;1,1;%s;bag%i;%s]", + (i + 1.35)*1.25, img, i, label) + n = n + 1 end end - return { formspec = table.concat(fs) } + return { formspec = table.concat(formspec) } end, }) end @@ -103,7 +108,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not stack:get_definition().groups.bagslots then return end - unified_inventory.set_inventory_formspec(player, "bag" .. i) + ui.set_inventory_formspec(player, "bag" .. i) return end end @@ -132,7 +137,7 @@ end local function load_bags_metadata(player, bags_inv) local player_inv = player:get_inventory() local meta = player:get_meta() - local bags_meta = meta:get_string("unified_inventory:bags") + local bags_meta = meta:get("unified_inventory:bags") local bags = bags_meta and minetest.deserialize(bags_meta) or {} local dirty_meta = false if not bags_meta then diff --git a/unified_inventory/callbacks.lua b/unified_inventory/callbacks.lua index bc90237..1f43e39 100644 --- a/unified_inventory/callbacks.lua +++ b/unified_inventory/callbacks.lua @@ -19,6 +19,8 @@ minetest.register_on_joinplayer(function(player) unified_inventory.active_search_direction[player_name] = "nochange" unified_inventory.apply_filter(player, "", "nochange") unified_inventory.current_searchbox[player_name] = "" + unified_inventory.current_category[player_name] = "all" + unified_inventory.current_category_scroll[player_name] = 0 unified_inventory.alternate[player_name] = 1 unified_inventory.current_item[player_name] = nil unified_inventory.current_craft_direction[player_name] = "recipe" @@ -69,6 +71,41 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) unified_inventory.current_searchbox[player_name] = fields.searchbox end + + local clicked_category + for name, value in pairs(fields) do + local category_name = string.match(name, "^category_(.+)$") + if category_name then + clicked_category = category_name + break + end + end + + if clicked_category + and clicked_category ~= unified_inventory.current_category[player_name] then + unified_inventory.current_category[player_name] = clicked_category + unified_inventory.apply_filter(player, unified_inventory.current_searchbox[player_name], "nochange") + unified_inventory.set_inventory_formspec(player, + unified_inventory.current_page[player_name]) + end + + if fields.next_category then + local scroll = math.min(#unified_inventory.category_list-ui_peruser.pagecols, unified_inventory.current_category_scroll[player_name] + 1) + if scroll ~= unified_inventory.current_category_scroll[player_name] then + unified_inventory.current_category_scroll[player_name] = scroll + unified_inventory.set_inventory_formspec(player, + unified_inventory.current_page[player_name]) + end + end + if fields.prev_category then + local scroll = math.max(0, unified_inventory.current_category_scroll[player_name] - 1) + if scroll ~= unified_inventory.current_category_scroll[player_name] then + unified_inventory.current_category_scroll[player_name] = scroll + unified_inventory.set_inventory_formspec(player, + unified_inventory.current_page[player_name]) + end + end + for i, def in pairs(unified_inventory.buttons) do if fields[def.name] then def.action(player) @@ -126,6 +163,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) clicked_item = unified_inventory.demangle_for_formspec(mangled_item) if string.sub(clicked_item, 1, 6) == "group:" then -- Change search filter to this group + unified_inventory.current_category[player_name] = "all" apply_new_filter(player, clicked_item, new_dir) return end diff --git a/unified_inventory/category.lua b/unified_inventory/category.lua new file mode 100644 index 0000000..72e4038 --- /dev/null +++ b/unified_inventory/category.lua @@ -0,0 +1,149 @@ +local S = minetest.get_translator("unified_inventory") + +unified_inventory.registered_categories = {} +unified_inventory.registered_category_items = {} +unified_inventory.category_list = {} + +local function char_to_sort_index(char_code) + if char_code <= 32 then + -- Command codes, no thanks + return 0 + end + if char_code <= 64 then + -- Sorts numbers, and some punctuation, after letters + return char_code + end + if char_code >= 158 then + -- Out of sortable range + return 0 + end + if char_code > 122 then + -- Avoids overlap with {, |, } and ~ + return char_code - 58 + end + if char_code > 96 then + -- Normalises lowercase with uppercase + return char_code - 96 + end + return char_code - 64 +end + +local function string_to_sort_index(str) + local max_chars = 5 + local power = 100 + local index = 0 + for i=1,math.min(#str, max_chars) do + index = index + (char_to_sort_index(string.byte(str, i))/(power^i)) + end + return index +end + +function update_category_list() + local category_list = {} + table.insert(category_list, { + name = "all", + label = S("All Items"), + symbol = "ui_category_all.png", + index = -2, + }) + table.insert(category_list, { + name = "uncategorized", + label = S("Misc. Items"), + symbol = "ui_category_none.png", + index = -1, + }) + for category, def in pairs(unified_inventory.registered_categories) do + table.insert(category_list, { + name = category, + label = def.label or category, + symbol = def.symbol, + index = def.index or -- sortby defined order + string_to_sort_index(category) -- or do a rudimentary alphabetical sort + }) + end + table.sort(category_list, function (a,b) + return a.index < b.index + end) + unified_inventory.category_list = category_list +end + +local function ensure_category_exists(category_name) + if not unified_inventory.registered_categories[category_name] then + unified_inventory.registered_categories[category_name] = { + symbol = "default:stick", + label = category_name + } + end + if not unified_inventory.registered_category_items[category_name] then + unified_inventory.registered_category_items[category_name] = {} + end +end + +function unified_inventory.register_category(category_name, config) + ensure_category_exists(category_name) + if config and config.symbol then + unified_inventory.set_category_symbol(category_name, config.symbol) + end + if config and config.label then + unified_inventory.set_category_label(category_name, config.label) + end + if config and config.index then + unified_inventory.set_category_index(category_name, config.index) + end + if config and config.items then + unified_inventory.add_category_items(category_name, config.items) + end + update_category_list() +end +function unified_inventory.set_category_symbol(category_name, symbol) + ensure_category_exists(category_name) + unified_inventory.registered_categories[category_name].symbol = symbol + update_category_list() +end +function unified_inventory.set_category_label(category_name, label) + ensure_category_exists(category_name) + unified_inventory.registered_categories[category_name].label = label + update_category_list() +end +function unified_inventory.set_category_index(category_name, index) + ensure_category_exists(category_name) + unified_inventory.registered_categories[category_name].index = index + update_category_list() +end +function unified_inventory.add_category_item(category_name, item) + ensure_category_exists(category_name) + unified_inventory.registered_category_items[category_name][item] = true +end +function unified_inventory.add_category_items(category_name, items) + for _,item in ipairs(items) do + unified_inventory.add_category_item(category_name, item) + end +end + +function unified_inventory.remove_category_item(category_name, item) + unified_inventory.registered_category_items[category_name][item] = nil +end +function unified_inventory.remove_category(category_name) + unified_inventory.registered_categories[category_name] = nil + unified_inventory.registered_category_items[category_name] = nil + update_category_list() +end + +function unified_inventory.find_category(item) + -- Returns the first category the item exists in + -- Best for checking if an item has any category at all + for category, items in pairs(unified_inventory.registered_category_items) do + if items[item] then return category end + end +end +function unified_inventory.find_categories(item) + -- Returns all the categories the item exists in + -- Best for listing all categories + local categories = {} + for category, items in pairs(unified_inventory.registered_category_items) do + if items[item] then + table.insert(categories, category) + end + end + return categories +end diff --git a/unified_inventory/default-categories.lua b/unified_inventory/default-categories.lua new file mode 100644 index 0000000..57d3e88 --- /dev/null +++ b/unified_inventory/default-categories.lua @@ -0,0 +1,704 @@ +local S = minetest.get_translator("unified_inventory") + +unified_inventory.register_category('plants', { + symbol = "flowers:tulip", + label = S("Plant Life") +}) +unified_inventory.register_category('building', { + symbol = "default:brick", + label = S("Building Materials") +}) +unified_inventory.register_category('tools', { + symbol = "default:pick_diamond", + label = S("Tools") +}) +unified_inventory.register_category('minerals', { + symbol = "default:iron_lump", + label = S("Minerals and Metals") +}) +unified_inventory.register_category('environment', { + symbol = "default:dirt_with_grass", + label = S("Environment and Worldgen") +}) +unified_inventory.register_category('lighting', { + symbol = "default:torch", + label = S("Lighting") +}) + + +if unified_inventory.automatic_categorization then + minetest.register_on_mods_loaded(function() + + -- Add biome nodes to environment category + for _,def in pairs(minetest.registered_biomes) do + local env_nodes = { + def.node_riverbed, def.node_top, def.node_filler, def.node_dust, + } + for i,node in pairs(env_nodes) do + if node then + unified_inventory.add_category_item('environment', node) + end + end + end + + -- Add minable ores to minerals and everything else (pockets of stone & sand variations) to environment + for _,item in pairs(minetest.registered_ores) do + if item.ore_type == "scatter" then + local drop = minetest.registered_nodes[item.ore].drop + if drop and drop ~= "" then + unified_inventory.add_category_item('minerals', item.ore) + unified_inventory.add_category_item('minerals', drop) + else + unified_inventory.add_category_item('environment', item.ore) + end + else + unified_inventory.add_category_item('environment', item.ore) + end + end + + -- Add items by item definition + for name, def in pairs(minetest.registered_items) do + local group = def.groups or {} + if not group.not_in_creative_inventory then + if group.stair or + group.slab or + group.wall or + group.fence then + unified_inventory.add_category_item('building', name) + elseif group.flora or + group.flower or + group.seed or + group.leaves or + group.sapling or + group.tree then + unified_inventory.add_category_item('plants', name) + elseif def.type == 'tool' then + unified_inventory.add_category_item('tools', name) + elseif def.liquidtype == 'source' then + unified_inventory.add_category_item('environment', name) + elseif def.light_source and def.light_source > 0 then + unified_inventory.add_category_item('lighting', name) + elseif group.door or + minetest.global_exists("doors") and ( + doors.registered_doors and doors.registered_doors[name..'_a'] or + doors.registered_trapdoors and doors.registered_trapdoors[name] + ) then + unified_inventory.add_category_item('building', name) + end + end + end + end) +end + +-- [[ +unified_inventory.add_category_items('plants', { + "default:dry_grass_5", + "default:acacia_sapling", + "default:blueberry_bush_sapling", + "default:grass_2", + "default:pine_bush_stem", + "default:leaves", + "default:pine_needles", + "default:cactus", + "default:junglegrass", + "default:pine_sapling", + "default:sapling", + "default:bush_stem", + "default:dry_grass_2", + "default:fern_1", + "default:grass_3", + "default:marram_grass_1", + "default:pine_tree", + "default:dry_grass_3", + "default:dry_shrub", + "default:grass_4", + "default:marram_grass_2", + "default:jungleleaves", + "default:apple", + "default:tree", + "default:aspen_tree", + "default:bush_sapling", + "default:grass_5", + "default:blueberry_bush_leaves_with_berries", + "default:acacia_bush_sapling", + "default:grass_1", + "default:aspen_leaves", + "default:marram_grass_3", + "default:large_cactus_seedling", + "default:junglesapling", + "default:dry_grass_4", + "default:acacia_bush_stem", + "default:papyrus", + "default:pine_bush_needles", + "default:bush_leaves", + "default:fern_3", + "default:aspen_sapling", + "default:acacia_tree", + "default:apple_mark", + "default:acacia_leaves", + "default:jungletree", + "default:dry_grass_1", + "default:acacia_bush_leaves", + "default:emergent_jungle_sapling", + "default:fern_2", + "default:blueberries", + "default:sand_with_kelp", + "default:blueberry_bush_leaves", + "default:pine_bush_sapling", + + "farming:cotton", + "farming:cotton_1", + "farming:cotton_2", + "farming:cotton_3", + "farming:cotton_4", + "farming:cotton_5", + "farming:cotton_6", + "farming:cotton_7", + "farming:cotton_8", + "farming:cotton_wild", + "farming:seed_cotton", + "farming:seed_wheat", + "farming:straw", + "farming:wheat", + "farming:wheat_1", + "farming:wheat_2", + "farming:wheat_3", + "farming:wheat_4", + "farming:wheat_5", + "farming:wheat_6", + "farming:wheat_7", + "farming:wheat_8", + + "flowers:chrysanthemum_green", + "flowers:dandelion_white", + "flowers:dandelion_yellow", + "flowers:geranium", + "flowers:mushroom_brown", + "flowers:mushroom_red", + "flowers:rose", + "flowers:tulip", + "flowers:tulip_black", + "flowers:viola", + "flowers:waterlily", + "flowers:waterlily_waving", +}) + +unified_inventory.add_category_items('tools', { + "default:sword_diamond", + "default:axe_diamond", + "default:shovel_diamond", + "default:axe_steel", + "default:shovel_mese", + "default:sword_wood", + "default:pick_bronze", + "default:axe_stone", + "default:sword_stone", + "default:pick_stone", + "default:shovel_stone", + "default:sword_mese", + "default:shovel_bronze", + "default:sword_bronze", + "default:axe_bronze", + "default:shovel_steel", + "default:sword_steel", + "default:axe_mese", + "default:shovel_wood", + "default:pick_mese", + "default:axe_wood", + "default:pick_diamond", + "default:pick_wood", + "default:pick_steel", + + "farming:hoe_bronze", + "farming:hoe_diamond", + "farming:hoe_mese", + "farming:hoe_steel", + "farming:hoe_stone", + "farming:hoe_wood", + + "fire:flint_and_steel", + "map:mapping_kit", + "screwdriver:screwdriver", + + "fireflies:bug_net", + "bucket:bucket_empty", + + "binoculars:binoculars", + "default:skeleton_key", +}) + +unified_inventory.add_category_items('minerals', { + "default:stone_with_copper", + "default:stone_with_gold", + "default:stone_with_iron", + "default:copper_ingot", + "default:copper_lump", + "default:gold_lump", + "default:diamondblock", + "default:stone_with_diamond", + "default:stone_with_mese", + "default:steel_ingot", + "default:gold_ingot", + "default:iron_lump", + "default:tinblock", + "default:tin_lump", + "default:stone_with_tin", + "default:mese_crystal", + "default:diamond", + "default:bronze_ingot", + "default:mese", + "default:mese_crystal_fragment", + "default:copperblock", + "default:stone_with_coal", + "default:steelblock", + "default:tin_ingot", + "default:coalblock", + "default:coal_lump", + "default:bronzeblock", + "default:goldblock", + + "stairs:slab_bronzeblock", + "stairs:slab_copperblock", + "stairs:slab_steelblock", + "stairs:slab_tinblock", + "stairs:stair_bronzeblock", + "stairs:stair_copperblock", + "stairs:stair_inner_bronzeblock", + "stairs:stair_inner_copperblock", + "stairs:stair_inner_steelblock", + "stairs:stair_inner_tinblock", + "stairs:stair_outer_bronzeblock", + "stairs:stair_outer_copperblock", + "stairs:stair_outer_steelblock", + "stairs:stair_outer_tinblock", + "stairs:stair_steelblock", + "stairs:stair_tinblock", +}) + +unified_inventory.add_category_items('building', { + "default:fence_rail_aspen_wood", + "default:fence_rail_acacia_wood", + "default:fence_junglewood", + "default:fence_rail_junglewood", + "default:fence_aspen_wood", + "default:fence_pine_wood", + "default:fence_rail_wood", + "default:fence_rail_pine_wood", + "default:fence_acacia_wood", + "default:junglewood", + "default:acacia_wood", + "default:aspen_wood", + "default:fence_wood", + "default:pine_wood", + "default:silver_sandstone", + "default:desert_sandstone", + "default:sandstone_block", + "default:desert_sandstone_brick", + "default:stone_block", + "default:stonebrick", + "default:obsidian_glass", + "default:desert_sandstone_block", + "default:silver_sandstone_brick", + "default:brick", + "default:obsidianbrick", + "default:sandstonebrick", + "default:sandstone", + "default:desert_stone_block", + "default:silver_sandstone_block", + "default:wood", + "default:obsidian_block", + "default:glass", + "default:clay_brick", + "default:desert_stonebrick", + "default:desert_cobble", + "default:cobble", + "default:mossycobble", + + "doors:door_glass", + "doors:door_glass_a", + "doors:door_glass_b", + "doors:door_glass_c", + "doors:door_glass_d", + "doors:door_obsidian_glass", + "doors:door_obsidian_glass_a", + "doors:door_obsidian_glass_b", + "doors:door_obsidian_glass_c", + "doors:door_obsidian_glass_d", + "doors:door_steel", + "doors:door_steel_a", + "doors:door_steel_b", + "doors:door_steel_c", + "doors:door_steel_d", + "doors:door_wood", + "doors:door_wood_a", + "doors:door_wood_b", + "doors:door_wood_c", + "doors:door_wood_d", + "doors:gate_acacia_wood_closed", + "doors:gate_acacia_wood_open", + "doors:gate_aspen_wood_closed", + "doors:gate_aspen_wood_open", + "doors:gate_junglewood_closed", + "doors:gate_junglewood_open", + "doors:gate_pine_wood_closed", + "doors:gate_pine_wood_open", + "doors:gate_wood_closed", + "doors:gate_wood_open", + "doors:hidden", + "doors:trapdoor", + "doors:trapdoor_open", + "doors:trapdoor_steel", + "doors:trapdoor_steel_open", + + "stairs:slab_bronzeblock", + "stairs:slab_copperblock", + "stairs:slab_steelblock", + "stairs:slab_tinblock", + "stairs:stair_bronzeblock", + "stairs:stair_copperblock", + "stairs:stair_inner_bronzeblock", + "stairs:stair_inner_copperblock", + "stairs:stair_inner_steelblock", + "stairs:stair_inner_tinblock", + "stairs:stair_outer_bronzeblock", + "stairs:stair_outer_copperblock", + "stairs:stair_outer_steelblock", + "stairs:stair_outer_tinblock", + "stairs:stair_steelblock", + "stairs:stair_tinblock", + + "stairs:slab_acacia_wood", + "stairs:slab_aspen_wood", + "stairs:slab_brick", + "stairs:slab_cobble", + "stairs:slab_desert_cobble", + "stairs:slab_desert_sandstone", + "stairs:slab_desert_sandstone_block", + "stairs:slab_desert_sandstone_brick", + "stairs:slab_desert_stone", + "stairs:slab_desert_stone_block", + "stairs:slab_desert_stonebrick", + "stairs:slab_glass", + "stairs:slab_goldblock", + "stairs:slab_ice", + "stairs:slab_junglewood", + "stairs:slab_mossycobble", + "stairs:slab_obsidian", + "stairs:slab_obsidian_block", + "stairs:slab_obsidian_glass", + "stairs:slab_obsidianbrick", + "stairs:slab_pine_wood", + "stairs:slab_sandstone", + "stairs:slab_sandstone_block", + "stairs:slab_sandstonebrick", + "stairs:slab_silver_sandstone", + "stairs:slab_silver_sandstone_block", + "stairs:slab_silver_sandstone_brick", + "stairs:slab_snowblock", + "stairs:slab_stone", + "stairs:slab_stone_block", + "stairs:slab_stonebrick", + "stairs:slab_straw", + "stairs:slab_wood", + "stairs:stair_acacia_wood", + "stairs:stair_aspen_wood", + "stairs:stair_brick", + "stairs:stair_cobble", + "stairs:stair_desert_cobble", + "stairs:stair_desert_sandstone", + "stairs:stair_desert_sandstone_block", + "stairs:stair_desert_sandstone_brick", + "stairs:stair_desert_stone", + "stairs:stair_desert_stone_block", + "stairs:stair_desert_stonebrick", + "stairs:stair_glass", + "stairs:stair_goldblock", + "stairs:stair_ice", + "stairs:stair_inner_acacia_wood", + "stairs:stair_inner_aspen_wood", + "stairs:stair_inner_brick", + "stairs:stair_inner_cobble", + "stairs:stair_inner_desert_cobble", + "stairs:stair_inner_desert_sandstone", + "stairs:stair_inner_desert_sandstone_block", + "stairs:stair_inner_desert_sandstone_brick", + "stairs:stair_inner_desert_stone", + "stairs:stair_inner_desert_stone_block", + "stairs:stair_inner_desert_stonebrick", + "stairs:stair_inner_glass", + "stairs:stair_inner_goldblock", + "stairs:stair_inner_ice", + "stairs:stair_inner_junglewood", + "stairs:stair_inner_mossycobble", + "stairs:stair_inner_obsidian", + "stairs:stair_inner_obsidian_block", + "stairs:stair_inner_obsidian_glass", + "stairs:stair_inner_obsidianbrick", + "stairs:stair_inner_pine_wood", + "stairs:stair_inner_sandstone", + "stairs:stair_inner_sandstone_block", + "stairs:stair_inner_sandstonebrick", + "stairs:stair_inner_silver_sandstone", + "stairs:stair_inner_silver_sandstone_block", + "stairs:stair_inner_silver_sandstone_brick", + "stairs:stair_inner_snowblock", + "stairs:stair_inner_stone", + "stairs:stair_inner_stone_block", + "stairs:stair_inner_stonebrick", + "stairs:stair_inner_straw", + "stairs:stair_inner_wood", + "stairs:stair_junglewood", + "stairs:stair_mossycobble", + "stairs:stair_obsidian", + "stairs:stair_obsidian_block", + "stairs:stair_obsidian_glass", + "stairs:stair_obsidianbrick", + "stairs:stair_outer_acacia_wood", + "stairs:stair_outer_aspen_wood", + "stairs:stair_outer_brick", + "stairs:stair_outer_cobble", + "stairs:stair_outer_desert_cobble", + "stairs:stair_outer_desert_sandstone", + "stairs:stair_outer_desert_sandstone_block", + "stairs:stair_outer_desert_sandstone_brick", + "stairs:stair_outer_desert_stone", + "stairs:stair_outer_desert_stone_block", + "stairs:stair_outer_desert_stonebrick", + "stairs:stair_outer_glass", + "stairs:stair_outer_goldblock", + "stairs:stair_outer_ice", + "stairs:stair_outer_junglewood", + "stairs:stair_outer_mossycobble", + "stairs:stair_outer_obsidian", + "stairs:stair_outer_obsidian_block", + "stairs:stair_outer_obsidian_glass", + "stairs:stair_outer_obsidianbrick", + "stairs:stair_outer_pine_wood", + "stairs:stair_outer_sandstone", + "stairs:stair_outer_sandstone_block", + "stairs:stair_outer_sandstonebrick", + "stairs:stair_outer_silver_sandstone", + "stairs:stair_outer_silver_sandstone_block", + "stairs:stair_outer_silver_sandstone_brick", + "stairs:stair_outer_snowblock", + "stairs:stair_outer_stone", + "stairs:stair_outer_stone_block", + "stairs:stair_outer_stonebrick", + "stairs:stair_outer_straw", + "stairs:stair_outer_wood", + "stairs:stair_pine_wood", + "stairs:stair_sandstone", + "stairs:stair_sandstone_block", + "stairs:stair_sandstonebrick", + "stairs:stair_silver_sandstone", + "stairs:stair_silver_sandstone_block", + "stairs:stair_silver_sandstone_brick", + "stairs:stair_snowblock", + "stairs:stair_stone", + "stairs:stair_stone_block", + "stairs:stair_stonebrick", + "stairs:stair_straw", + "stairs:stair_wood", + + "xpanes:bar", + "xpanes:bar_flat", + "xpanes:door_steel_bar", + "xpanes:door_steel_bar_a", + "xpanes:door_steel_bar_b", + "xpanes:door_steel_bar_c", + "xpanes:door_steel_bar_d", + "xpanes:obsidian_pane", + "xpanes:obsidian_pane_flat", + "xpanes:pane", + "xpanes:pane_flat", + "xpanes:trapdoor_steel_bar", + "xpanes:trapdoor_steel_bar_open", + + "walls:cobble", + "walls:desertcobble", + "walls:mossycobble", +}) + +unified_inventory.add_category_items('environment', { + "air", + "default:cave_ice", + "default:dirt_with_rainforest_litter", + "default:gravel", + "default:dry_dirt_with_dry_grass", + "default:permafrost", + "default:desert_stone", + "default:ice", + "default:dry_dirt", + "default:obsidian", + "default:sand", + "default:river_water_source", + "default:dirt_with_snow", + "default:dirt_with_grass", + "default:water_flowing", + "default:dirt", + "default:desert_sand", + "default:permafrost_with_moss", + "default:dirt_with_coniferous_litter", + "default:water_source", + "default:dirt_with_dry_grass", + "default:river_water_flowing", + "default:stone", + "default:snow", + "default:lava_flowing", + "default:lava_source", + "default:permafrost_with_stones", + "default:dirt_with_grass_footsteps", + "default:silver_sand", + "default:snowblock", + "default:clay", + + "farming:desert_sand_soil", + "farming:desert_sand_soil_wet", + "farming:dry_soil", + "farming:dry_soil_wet", + "farming:soil", + "farming:soil_wet", +}) + +unified_inventory.add_category_items('lighting', { + "default:mese_post_light_junglewood", + "default:torch_ceiling", + "default:meselamp", + "default:torch", + "default:mese_post_light_acacia_wood", + "default:mese_post_light", + "default:torch_wall", + "default:mese_post_light_pine_wood", + "default:mese_post_light_aspen_wood" +}) +--]] + + +--[[ UNCATEGORISED + + "farming:string", + + "beds:bed_bottom", + "beds:bed_top", + "beds:fancy_bed_bottom", + "beds:fancy_bed_top", + "boats:boat", + "bones:bones", + + "bucket:bucket_lava", + "bucket:bucket_river_water", + "bucket:bucket_water", + + "butterflies:butterfly_red", + "butterflies:butterfly_violet", + "butterflies:butterfly_white", + "butterflies:hidden_butterfly_red", + "butterflies:hidden_butterfly_violet", + "butterflies:hidden_butterfly_white", + + "carts:brakerail", + "carts:cart", + "carts:powerrail", + "carts:rail", + + "default:book", + "default:book_written", + "default:bookshelf", + "default:chest", + "default:chest_locked", + "default:chest_locked_open", + "default:chest_open", + "default:clay_lump", + "default:cloud", + "default:coral_brown", + "default:coral_cyan", + "default:coral_green", + "default:coral_orange", + "default:coral_pink", + "default:coral_skeleton", + "default:flint", + "default:furnace", + "default:furnace_active", + "default:key", + "default:ladder_steel", + "default:ladder_wood", + "default:obsidian_shard", + "default:paper", + "default:sign_wall_steel", + "default:sign_wall_wood", + "default:stick", + + "fire:basic_flame", + "fire:permanent_flame", + "fireflies:firefly", + "fireflies:firefly_bottle", + "fireflies:hidden_firefly", + + "ignore", + "unknown", + + "tnt:boom", + "tnt:gunpowder", + "tnt:gunpowder_burning", + "tnt:tnt", + "tnt:tnt_burning", + "tnt:tnt_stick", + + "vessels:drinking_glass", + "vessels:glass_bottle", + "vessels:glass_fragments", + "vessels:shelf", + "vessels:steel_bottle", + + "dye:black", + "dye:blue", + "dye:brown", + "dye:cyan", + "dye:dark_green", + "dye:dark_grey", + "dye:green", + "dye:grey", + "dye:magenta", + "dye:orange", + "dye:pink", + "dye:red", + "dye:violet", + "dye:white", + "dye:yellow", + + "wool:black", + "wool:blue", + "wool:brown", + "wool:cyan", + "wool:dark_green", + "wool:dark_grey", + "wool:green", + "wool:grey", + "wool:magenta", + "wool:orange", + "wool:pink", + "wool:red", + "wool:violet", + "wool:white", + "wool:yellow", + + "unified_inventory:bag_large", + "unified_inventory:bag_medium", + "unified_inventory:bag_small", +--]] + +--[[ LIST UNCATEGORIZED AFTER LOAD +minetest.register_on_mods_loaded(function() + minetest.after(1, function ( ) + local l = {} + for name,_ in pairs(minetest.registered_items) do + if not unified_inventory.find_category(name) then + -- minetest.log("error", minetest.serialize(minetest.registered_items[name])) + table.insert(l, name) + end + end + table.sort(l) + minetest.log(table.concat(l, '",'.."\n"..'"')) + end) +end) +--]] \ No newline at end of file diff --git a/unified_inventory/doc/mod_api.txt b/unified_inventory/doc/mod_api.txt index c0be129..ff52792 100644 --- a/unified_inventory/doc/mod_api.txt +++ b/unified_inventory/doc/mod_api.txt @@ -3,6 +3,14 @@ unified_inventory API This file provides information about the API of unified_inventory. +API revisions within unified_inventory can be checked using: + + (unified_inventory.version or 1) + +**Revision history** + +* Version `1`: Classic formspec layout (no real_coordinates) +* Version `2`: Force formspec version 4 (includes real_coordinates) Misc functions -------------- @@ -93,3 +101,72 @@ Register a non-standard craft recipe: -- ^ Same as `minetest.register_recipe` }) + +Categories +---------- + +Register a new category: + The config table (second argument) is optional, and all its members are optional + See the unified_inventory.set_category_* functions for more details on the members of the config table + + unified_inventory.register_category("category_name", { + symbol = "mod_name:item_name" or "texture.png", + label = "Human Readable Label", + index = 5, + items = { + "mod_name:item_name", + "another_mod:different_item" + } + }) + +Add / override the symbol for a category: + The category does not need to exist first + The symbol can be an item name or a texture image + If unset this will default to "default:stick" + + unified_inventory.set_category_symbol("category_name", "mod_name:item_name" or "texture.png") + +Add / override the human readable label for a category: + If unset this will default to the category name + + unified_inventory.set_category_label("category_name", "Human Readable Label") + +Add / override the sorting index of the category: + Must be a number, can also be negative (-5) or fractional (2.345) + This determines the position the category appears in the list of categories + The "all" meta-category has index -2, the "misc"/"uncategorized" meta-category has index -1, use a negative number smaller than these to make a category appear before these in the list + By default categories are sorted alphabetically with an index between 0.0101(AA) and 0.2626(ZZ) + + unified_inventory.set_category_index("category_name", 5) + +Add a single item to a category: + + unified_inventory.add_category_item("category_name", "mod_name:item_name") + +Add multiple items to a category: + + unified_inventory.add_category_items("category_name", { + "mod_name:item_name", + "another_mod:different_item" + }) + +Remove an item from a category: + + unified_inventory.remove_category_item("category_name", "mod_name:item_name") + +Remove a category entirely: + + unified_inventory.remove_category("category_name") + +Finding existing items in categories: + This will find the first category an item exists in + It should be used for checking if an item is catgorised + Returns "category_name" or nil + + unified_inventory.find_category("mod_name:item_name") + + + This will find all the categories an item exists in + Returns a number indexed table (list) of category names + + unified_inventory.find_categories("mod_name:item_name") diff --git a/unified_inventory/init.lua b/unified_inventory/init.lua index 1a80abb..391eb3c 100644 --- a/unified_inventory/init.lua +++ b/unified_inventory/init.lua @@ -10,6 +10,8 @@ unified_inventory = { alternate = {}, current_page = {}, current_searchbox = {}, + current_category = {}, + current_category_scroll = {}, current_index = {}, current_item = {}, current_craft_direction = {}, @@ -33,20 +35,110 @@ unified_inventory = { -- "Lite" mode lite_mode = minetest.settings:get_bool("unified_inventory_lite"), + -- Items automatically added to categories based on item definitions + automatic_categorization = (minetest.settings:get_bool("unified_inventory_automatic_categorization") ~= false), + -- Trash enabled trash_enabled = (minetest.settings:get_bool("unified_inventory_trash") ~= false), - - pagecols = 8, - pagerows = 10, - page_y = 0, - formspec_y = 1, - main_button_x = 0, - main_button_y = 9, - craft_result_x = 0.3, - craft_result_y = 0.5, - form_header_y = 0 + imgscale = 1.25, + list_img_offset = 0.13, + standard_background = "background9[0,0;1,1;ui_formbg_9_sliced.png;true;16]", + version = 2 } +local ui = unified_inventory + +-- These tables establish position and layout for the two UI styles. +-- UI doesn't use formspec_[xy] anymore, but other mods may need them. + +ui.style_full = { + formspec_x = 1, + formspec_y = 1, + formw = 17.75, + formh = 12.25, + pagecols = 8, + pagerows = 9, + page_x = 10.75, + page_y = 2.30, + craft_x = 2.8, + craft_y = 1.15, + craftresult_x = 7.8, + craft_arrow_x = 6.55, + craft_guide_x = 3.3, + craft_guide_y = 1.15, + craft_guide_arrow_x = 7.05, + craft_guide_result_x = 8.3, + craft_guide_resultstr_x = 0.3, + craft_guide_resultstr_y = 0.6, + give_btn_x = 0.25, + main_button_x = 0.4, + main_button_y = 11.0, + page_buttons_x = 11.60, + page_buttons_y = 10.15, + searchwidth = 3.4, + form_header_x = 0.4, + form_header_y = 0.4, + btn_spc = 0.85, + btn_size = 0.75, + std_inv_x = 0.3, + std_inv_y = 5.75, +} + +ui.style_lite = { + formspec_x = 0.6, + formspec_y = 0.6, + formw = 14, + formh = 9.75, + pagecols = 4, + pagerows = 5, + page_x = 10.5, + page_y = 2.15, + craft_x = 2.6, + craft_y = 0.75, + craftresult_x = 5.75, + craft_arrow_x = 6.35, + craft_guide_x = 3.1, + craft_guide_y = 0.75, + craft_guide_arrow_x = 7.05, + craft_guide_result_x = 8.3, + craft_guide_resultstr_x = 0.15, + craft_guide_resultstr_y = 0.35, + give_btn_x = 0.15, + main_button_x = 10.5, + main_button_y = 8.15, + page_buttons_x = 10.5, + page_buttons_y = 6.15, + searchwidth = 1.6, + form_header_x = 0.2, + form_header_y = 0.2, + btn_spc = 0.8, + btn_size = 0.7, + std_inv_x = 0.1, + std_inv_y = 4.6, +} + +dofile(modpath.."/api.lua") + +for _, style in ipairs({ui.style_full, ui.style_lite}) do + style.items_per_page = style.pagecols * style.pagerows + style.standard_inv = string.format("list[current_player;main;%f,%f;8,4;]", + style.std_inv_x + ui.list_img_offset, style.std_inv_y + ui.list_img_offset) + + style.standard_inv_bg = ui.make_inv_img_grid(style.std_inv_x, style.std_inv_y, 8, 1, true).. + ui.make_inv_img_grid(style.std_inv_x, style.std_inv_y + ui.imgscale, 8, 3) + + style.craft_grid = table.concat({ + ui.make_inv_img_grid(style.craft_x, style.craft_y, 3, 3), + ui.single_slot(style.craft_x + ui.imgscale*4, style.craft_y), -- the craft result slot + string.format("image[%f,%f;%f,%f;ui_crafting_arrow.png]", + style.craft_arrow_x, style.craft_y, ui.imgscale, ui.imgscale), + string.format("list[current_player;craft;%f,%f;3,3;]", + style.craft_x + ui.list_img_offset, style.craft_y + ui.list_img_offset), + string.format("list[current_player;craftpreview;%f,%f;1,1;]", + style.craftresult_x + ui.list_img_offset, style.craft_y + ui.list_img_offset) + }) +end + -- Disable default creative inventory local creative = rawget(_G, "creative") or rawget(_G, "creative_inventory") if creative then @@ -62,7 +154,8 @@ if sfinv then end dofile(modpath.."/group.lua") -dofile(modpath.."/api.lua") +dofile(modpath.."/category.lua") +dofile(modpath.."/default-categories.lua") dofile(modpath.."/internal.lua") dofile(modpath.."/callbacks.lua") dofile(modpath.."/match_craft.lua") diff --git a/unified_inventory/internal.lua b/unified_inventory/internal.lua index eeab695..6113300 100644 --- a/unified_inventory/internal.lua +++ b/unified_inventory/internal.lua @@ -1,5 +1,6 @@ local S = minetest.get_translator("unified_inventory") local F = minetest.formspec_escape +local ui = unified_inventory -- This pair of encoding functions is used where variable text must go in -- button names, where the text might contain formspec metacharacters. @@ -9,78 +10,61 @@ local F = minetest.formspec_escape -- This is a game engine bug, and in the anticipation that it might be -- fixed some day we don't want to rely on it. So for safety we apply -- an encoding that avoids all formspec metacharacters. -function unified_inventory.mangle_for_formspec(str) + +function ui.mangle_for_formspec(str) return string.gsub(str, "([^A-Za-z0-9])", function (c) return string.format("_%d_", string.byte(c)) end) end -function unified_inventory.demangle_for_formspec(str) +function ui.demangle_for_formspec(str) return string.gsub(str, "_([0-9]+)_", function (v) return string.char(v) end) end -function unified_inventory.get_per_player_formspec(player_name) - local lite = unified_inventory.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true}) - local ui = {} - ui.pagecols = unified_inventory.pagecols - ui.pagerows = unified_inventory.pagerows - ui.page_y = unified_inventory.page_y - ui.formspec_y = unified_inventory.formspec_y - ui.main_button_x = unified_inventory.main_button_x - ui.main_button_y = unified_inventory.main_button_y - ui.craft_result_x = unified_inventory.craft_result_x - ui.craft_result_y = unified_inventory.craft_result_y - ui.form_header_y = unified_inventory.form_header_y +function ui.get_per_player_formspec(player_name) + local draw_lite_mode = ui.lite_mode and not minetest.check_player_privs(player_name, {ui_full=true}) - if lite then - ui.pagecols = 4 - ui.pagerows = 6 - ui.page_y = 0.25 - ui.formspec_y = 0.47 - ui.main_button_x = 8.2 - ui.main_button_y = 6.5 - ui.craft_result_x = 2.8 - ui.craft_result_y = 3.4 - ui.form_header_y = -0.1 - end - - ui.items_per_page = ui.pagecols * ui.pagerows - return ui, lite + return table.copy(draw_lite_mode and ui.style_lite or ui.style_full), draw_lite_mode end -function unified_inventory.get_formspec(player, page) +local function formspec_button(ui_peruser, name, image, offset, pos, scale, label) + local element = 'image_button' + if minetest.registered_items[image] then + element = 'item_image_button' + end + local spc = (1-scale)*ui_peruser.btn_size/2 + local size = ui_peruser.btn_size*scale + return string.format("%s[%f,%f;%f,%f;%s;%s;]", element, + (offset.x or offset[1]) + ( ui_peruser.btn_spc * (pos.x or pos[1]) ) + spc, + (offset.y or offset[2]) + ( ui_peruser.btn_spc * (pos.y or pos[2]) ) + spc, + size, size, image, name) .. + string.format("tooltip[%s;%s]", name, F(label or name)) +end + +function ui.get_formspec(player, page) if not player then return "" end local player_name = player:get_player_name() - local ui_peruser,draw_lite_mode = unified_inventory.get_per_player_formspec(player_name) + local ui_peruser,draw_lite_mode = ui.get_per_player_formspec(player_name) - unified_inventory.current_page[player_name] = page - local pagedef = unified_inventory.pages[page] + ui.current_page[player_name] = page + local pagedef = ui.pages[page] if not pagedef then return "" -- Invalid page name end - + local formspec = { - "size[14,10]", + "formspec_version[4]", + "size["..ui_peruser.formw..","..ui_peruser.formh.."]", pagedef.formspec_prepend and "" or "no_prepend[]", - "background[-0.19,-0.25;14.4,10.75;ui_form_bg.png]" -- Background + ui.standard_background } - local n = 4 - if draw_lite_mode then - formspec[1] = "size[11,7.7]" - formspec[3] = "background[-0.19,-0.2;11.4,8.4;ui_form_bg.png]" - end + local n = 5 - if unified_inventory.is_creative(player_name) - and page == "craft" then - formspec[n] = "background[0,"..(ui_peruser.formspec_y + 2)..";1,1;ui_single_slot.png]" - n = n+1 - end - - local perplayer_formspec = unified_inventory.get_per_player_formspec(player_name) + local perplayer_formspec = ui.get_per_player_formspec(player_name) local fsdata = pagedef.get_formspec(player, perplayer_formspec) formspec[n] = fsdata.formspec @@ -93,7 +77,7 @@ function unified_inventory.get_formspec(player, page) local filtered_inv_buttons = {} - for i, def in pairs(unified_inventory.buttons) do + for i, def in pairs(ui.buttons) do if not (draw_lite_mode and def.hide_lite) then table.insert(filtered_inv_buttons, def) end @@ -108,21 +92,20 @@ function unified_inventory.get_formspec(player, page) if def.type == "image" then if (def.condition == nil or def.condition(player) == true) then - formspec[n] = "image_button[" - formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4) - formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;" - formspec[n+3] = F(def.image)..";" - formspec[n+4] = F(def.name)..";]" - formspec[n+5] = "tooltip["..F(def.name) - formspec[n+6] = ";"..(def.tooltip or "").."]" - n = n+7 + formspec[n] = string.format("image_button[%f,%f;%f,%f;%s;%s;]", + ui_peruser.main_button_x + ui_peruser.btn_spc * (i - 1) - button_col * ui_peruser.btn_spc * 4, + ui_peruser.main_button_y + button_row * ui_peruser.btn_spc, + ui_peruser.btn_size,ui_peruser.btn_size, + F(def.image), + F(def.name)) + formspec[n+1] = "tooltip["..F(def.name)..";"..(def.tooltip or "").."]" + n = n+2 else - formspec[n] = "image[" - formspec[n+1] = ( ui_peruser.main_button_x + 0.65 * (i - 1) - button_col * 0.65 * 4) - formspec[n+2] = ","..(ui_peruser.main_button_y + button_row * 0.7)..";0.8,0.8;" - formspec[n+3] = F(def.image).."^[colorize:#808080:alpha]" - n = n+4 - + formspec[n] = string.format("image[%f,%f;%f,%f;%s^[colorize:#808080:alpha]", + ui_peruser.main_button_x + ui_peruser.btn_spc * (i - 1) - button_col * ui_peruser.btn_spc * 4, + ui_peruser.main_button_y + button_row * ui_peruser.btn_spc, + ui_peruser.btn_size,ui_peruser.btn_size,def.image) + n = n+1 end end end @@ -130,7 +113,7 @@ function unified_inventory.get_formspec(player, page) if fsdata.draw_inventory ~= false then -- Player inventory formspec[n] = "listcolors[#00000000;#00000000]" - formspec[n+1] = "list[current_player;main;0,"..(ui_peruser.formspec_y + 3.5)..";8,4;]" + formspec[n+1] = ui_peruser.standard_inv n = n+2 end @@ -138,71 +121,94 @@ function unified_inventory.get_formspec(player, page) return table.concat(formspec, "") end - -- Controls to flip items pages - local start_x = 9.2 + -- Category filters - if not draw_lite_mode then - formspec[n] = - "image_button[" .. (start_x + 0.6 * 0) - .. ",9;.8,.8;ui_skip_backward_icon.png;start_list;]" - .. "tooltip[start_list;" .. F(S("First page")) .. "]" + local categories_pos = { ui_peruser.page_x, ui_peruser.page_y-ui_peruser.btn_spc-0.5 } + local categories_scroll_pos = { ui_peruser.page_x, ui_peruser.form_header_y-(draw_lite_mode and 0 or 0.2) } - .. "image_button[" .. (start_x + 0.6 * 1) - .. ",9;.8,.8;ui_doubleleft_icon.png;rewind3;]" - .. "tooltip[rewind3;" .. F(S("Back three pages")) .. "]" - .. "image_button[" .. (start_x + 0.6 * 2) - .. ",9;.8,.8;ui_left_icon.png;rewind1;]" - .. "tooltip[rewind1;" .. F(S("Back one page")) .. "]" + formspec[n] = string.format("background9[%f,%f;%f,%f;%s;false;3]", + ui_peruser.page_x-0.1, categories_scroll_pos[2], + (ui_peruser.btn_spc * ui_peruser.pagecols) + 0.13, 1.4+(draw_lite_mode and 0 or 0.2), + "ui_smallbg_9_sliced.png") + n = n + 1 - .. "image_button[" .. (start_x + 0.6 * 3) - .. ",9;.8,.8;ui_right_icon.png;forward1;]" - .. "tooltip[forward1;" .. F(S("Forward one page")) .. "]" - .. "image_button[" .. (start_x + 0.6 * 4) - .. ",9;.8,.8;ui_doubleright_icon.png;forward3;]" - .. "tooltip[forward3;" .. F(S("Forward three pages")) .. "]" + formspec[n] = string.format("label[%f,%f;%s]", ui_peruser.page_x, ui_peruser.form_header_y+(draw_lite_mode and 0.3 or 0.2), "Category:") + n = n + 1 - .. "image_button[" .. (start_x + 0.6 * 5) - .. ",9;.8,.8;ui_skip_forward_icon.png;end_list;]" - .. "tooltip[end_list;" .. F(S("Last page")) .. "]" - else - formspec[n] = - "image_button[" .. (8.2 + 0.65 * 0) - .. ",5.8;.8,.8;ui_skip_backward_icon.png;start_list;]" - .. "tooltip[start_list;" .. F(S("First page")) .. "]" - .. "image_button[" .. (8.2 + 0.65 * 1) - .. ",5.8;.8,.8;ui_left_icon.png;rewind1;]" - .. "tooltip[rewind1;" .. F(S("Back one page")) .. "]" - .. "image_button[" .. (8.2 + 0.65 * 2) - .. ",5.8;.8,.8;ui_right_icon.png;forward1;]" - .. "tooltip[forward1;" .. F(S("Forward one page")) .. "]" - .. "image_button[" .. (8.2 + 0.65 * 3) - .. ",5.8;.8,.8;ui_skip_forward_icon.png;end_list;]" - .. "tooltip[end_list;" .. F(S("Last page")) .. "]" + local scroll_offset = 0 + local category_count = #unified_inventory.category_list + if category_count > ui_peruser.pagecols then + scroll_offset = unified_inventory.current_category_scroll[player_name] + end + + for index, category in ipairs(unified_inventory.category_list) do + local column = index - scroll_offset + if column > 0 and column <= ui_peruser.pagecols then + local scale = 0.8 + if unified_inventory.current_category[player_name] == category.name then + scale = 1 + end + formspec[n] = formspec_button(ui_peruser, "category_"..category.name, category.symbol, categories_pos, {column-1, 0}, scale, category.label) + n = n + 1 + end + end + if category_count > ui_peruser.pagecols and scroll_offset > 0 then + -- prev + formspec[n] = formspec_button(ui_peruser, "prev_category", "ui_left_icon.png", categories_scroll_pos, {ui_peruser.pagecols - 2, 0}, 0.8, S("Scroll categories left")) + n = n + 1 + end + if category_count > ui_peruser.pagecols and category_count - scroll_offset > ui_peruser.pagecols then + -- next + formspec[n] = formspec_button(ui_peruser, "next_category", "ui_right_icon.png", categories_scroll_pos, {ui_peruser.pagecols - 1, 0}, 0.8, S("Scroll categories right")) + n = n + 1 end - n = n+1 -- Search box formspec[n] = "field_close_on_enter[searchbox;false]" - n = n+1 - if not draw_lite_mode then - formspec[n] = "field[9.5,8.325;3,1;searchbox;;" - .. F(unified_inventory.current_searchbox[player_name]) .. "]" - formspec[n+1] = "image_button[12.2,8.1;.8,.8;ui_search_icon.png;searchbutton;]" - .. "tooltip[searchbutton;" ..F(S("Search")) .. "]" - formspec[n+2] = "image_button[12.9,8.1;.8,.8;ui_reset_icon.png;searchresetbutton;]" - .. "tooltip[searchbutton;" ..F(S("Search")) .. "]" - .. "tooltip[searchresetbutton;" ..F(S("Reset search and display everything")) .. "]" - else - formspec[n] = "field[8.5,5.225;2.2,1;searchbox;;" - .. F(unified_inventory.current_searchbox[player_name]) .. "]" - formspec[n+1] = "image_button[10.3,5;.8,.8;ui_search_icon.png;searchbutton;]" - .. "tooltip[searchbutton;" ..F(S("Search")) .. "]" - formspec[n+2] = "image_button[11,5;.8,.8;ui_reset_icon.png;searchresetbutton;]" - .. "tooltip[searchbutton;" ..F(S("Search")) .. "]" - .. "tooltip[searchresetbutton;" ..F(S("Reset search and display everything")) .. "]" + formspec[n+1] = string.format("field[%f,%f;%f,%f;searchbox;;%s]", + ui_peruser.page_buttons_x, ui_peruser.page_buttons_y, + ui_peruser.searchwidth - 0.1, ui_peruser.btn_size, + F(ui.current_searchbox[player_name])) + formspec[n+2] = string.format("image_button[%f,%f;%f,%f;ui_search_icon.png;searchbutton;]", + ui_peruser.page_buttons_x + ui_peruser.searchwidth, ui_peruser.page_buttons_y, + ui_peruser.btn_size,ui_peruser.btn_size) + formspec[n+3] = "tooltip[searchbutton;" ..F(S("Search")) .. "]" + formspec[n+4] = string.format("image_button[%f,%f;%f,%f;ui_reset_icon.png;searchresetbutton;]", + ui_peruser.page_buttons_x + ui_peruser.searchwidth + ui_peruser.btn_spc, + ui_peruser.page_buttons_y, + ui_peruser.btn_size, ui_peruser.btn_size) + formspec[n+5] = "tooltip[searchresetbutton;"..F(S("Reset search and display everything")).."]" + + n = n + 6 + + -- Controls to flip items pages + + local btnlist = { + { "ui_skip_backward_icon.png", "start_list", S("First page") }, + { "ui_doubleleft_icon.png", "rewind3", S("Back three pages") }, + { "ui_left_icon.png", "rewind1", S("Back one page") }, + { "ui_right_icon.png", "forward1", S("Forward one page") }, + { "ui_doubleright_icon.png", "forward3", S("Forward three pages") }, + { "ui_skip_forward_icon.png", "end_list", S("Last page") }, + } + + if draw_lite_mode then + btnlist[5] = nil + btnlist[2] = nil + end + + local bn = 0 + for _, b in pairs(btnlist) do + formspec[n] = string.format("image_button[%f,%f;%f,%f;%s;%s;]", + ui_peruser.page_buttons_x + ui_peruser.btn_spc*bn, + ui_peruser.page_buttons_y + ui_peruser.btn_spc, + ui_peruser.btn_size, ui_peruser.btn_size, + b[1],b[2]) + formspec[n+1] = "tooltip["..b[2]..";"..F(b[3]).."]" + bn = bn + 1 + n = n + 2 end - n = n+3 local no_matches = S("No matching items") if draw_lite_mode then @@ -210,23 +216,23 @@ function unified_inventory.get_formspec(player, page) end -- Items list - if #unified_inventory.filtered_items_list[player_name] == 0 then - formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";" .. F(no_matches) .. "]" + if #ui.filtered_items_list[player_name] == 0 then + formspec[n] = "label["..ui_peruser.page_x..","..(ui_peruser.page_y+0.15)..";" .. F(no_matches) .. "]" else - local dir = unified_inventory.active_search_direction[player_name] - local list_index = unified_inventory.current_index[player_name] + local dir = ui.active_search_direction[player_name] + local list_index = ui.current_index[player_name] local page2 = math.floor(list_index / (ui_peruser.items_per_page) + 1) local pagemax = math.floor( - (#unified_inventory.filtered_items_list[player_name] - 1) + (#ui.filtered_items_list[player_name] - 1) / (ui_peruser.items_per_page) + 1) for y = 0, ui_peruser.pagerows - 1 do for x = 0, ui_peruser.pagecols - 1 do - local name = unified_inventory.filtered_items_list[player_name][list_index] + local name = ui.filtered_items_list[player_name][list_index] local item = minetest.registered_items[name] if item then -- Clicked on current item: Flip crafting direction - if name == unified_inventory.current_item[player_name] then - local cdir = unified_inventory.current_craft_direction[player_name] + if name == ui.current_item[player_name] then + local cdir = ui.current_craft_direction[player_name] if cdir == "recipe" then dir = "usage" elseif cdir == "usage" then @@ -234,13 +240,15 @@ function unified_inventory.get_formspec(player, page) end else -- Default: use active search direction by default - dir = unified_inventory.active_search_direction[player_name] + dir = ui.active_search_direction[player_name] end local button_name = "item_button_" .. dir .. "_" - .. unified_inventory.mangle_for_formspec(name) - formspec[n] = ("item_image_button[%f,%f;.81,.81;%s;%s;]"):format( - 8.2 + x * 0.7, ui_peruser.formspec_y + ui_peruser.page_y + y * 0.7, + .. ui.mangle_for_formspec(name) + formspec[n] = ("item_image_button[%f,%f;%f,%f;%s;%s;]"):format( + ui_peruser.page_x + x * ui_peruser.btn_spc, + ui_peruser.page_y + y * ui_peruser.btn_spc, + ui_peruser.btn_size, ui_peruser.btn_size, name, button_name ) formspec[n + 1] = ("tooltip[%s;%s \\[%s\\]]"):format( @@ -252,26 +260,36 @@ function unified_inventory.get_formspec(player, page) end end end - formspec[n] = "label[8.2,"..ui_peruser.form_header_y..";"..F(S("Page")) .. ": " - .. S("@1 of @2",page2,pagemax).."]" + formspec[n] = string.format("label[%f,%f;%s: %s]", + ui_peruser.page_buttons_x + ui_peruser.btn_spc * (draw_lite_mode and 1 or 2), + ui_peruser.page_buttons_y + 0.1 + ui_peruser.btn_spc * 2, + F(S("Page")), S("@1 of @2",page2,pagemax)) end n= n+1 - if unified_inventory.activefilter[player_name] ~= "" then - formspec[n] = "label[8.2,"..(ui_peruser.form_header_y + 0.4)..";" .. F(S("Filter")) .. ":]" - formspec[n+1] = "label[9.1,"..(ui_peruser.form_header_y + 0.4)..";"..F(unified_inventory.activefilter[player_name]).."]" + if ui.activefilter[player_name] ~= "" then + formspec[n] = string.format("label[%f,%f;%s: %s]", + ui_peruser.page_x, ui_peruser.page_y - 0.25, + F(S("Filter")), F(ui.activefilter[player_name])) end return table.concat(formspec, "") end -function unified_inventory.set_inventory_formspec(player, page) +function ui.set_inventory_formspec(player, page) if player then - player:set_inventory_formspec(unified_inventory.get_formspec(player, page)) + player:set_inventory_formspec(ui.get_formspec(player, page)) end end +local function valid_def(def) + return (not def.groups.not_in_creative_inventory + or def.groups.not_in_creative_inventory == 0) + and def.description + and def.description ~= "" +end + --apply filter to the inventory list (create filtered copy of full one) -function unified_inventory.apply_filter(player, filter, search_dir) +function ui.apply_filter(player, filter, search_dir) if not player then return false end @@ -300,26 +318,43 @@ function unified_inventory.apply_filter(player, filter, search_dir) or llocaldesc and string.find(llocaldesc, lfilter, 1, true) end end - unified_inventory.filtered_items_list[player_name]={} - for name, def in pairs(minetest.registered_items) do - if (not def.groups.not_in_creative_inventory - or def.groups.not_in_creative_inventory == 0) - and def.description - and def.description ~= "" - and ffilter(name, def) then - table.insert(unified_inventory.filtered_items_list[player_name], name) + ui.filtered_items_list[player_name]={} + local category = ui.current_category[player_name] or 'all' + if category == 'all' then + for name, def in pairs(minetest.registered_items) do + if valid_def(def) + and ffilter(name, def) then + table.insert(ui.filtered_items_list[player_name], name) + end + end + elseif category == 'uncategorized' then + for name, def in pairs(minetest.registered_items) do + if (not ui.find_category(name)) + and valid_def(def) + and ffilter(name, def) then + table.insert(ui.filtered_items_list[player_name], name) + end + end + else + for name,exists in pairs(ui.registered_category_items[category]) do + local def = minetest.registered_items[name] + if exists and def + and valid_def(def) + and ffilter(name, def) then + table.insert(ui.filtered_items_list[player_name], name) + end end end - table.sort(unified_inventory.filtered_items_list[player_name]) - unified_inventory.filtered_items_list_size[player_name] = #unified_inventory.filtered_items_list[player_name] - unified_inventory.current_index[player_name] = 1 - unified_inventory.activefilter[player_name] = filter - unified_inventory.active_search_direction[player_name] = search_dir - unified_inventory.set_inventory_formspec(player, - unified_inventory.current_page[player_name]) + table.sort(ui.filtered_items_list[player_name]) + ui.filtered_items_list_size[player_name] = #ui.filtered_items_list[player_name] + ui.current_index[player_name] = 1 + ui.activefilter[player_name] = filter + ui.active_search_direction[player_name] = search_dir + ui.set_inventory_formspec(player, + ui.current_page[player_name]) end -function unified_inventory.items_in_group(groups) +function ui.items_in_group(groups) local items = {} for name, item in pairs(minetest.registered_items) do for _, group in pairs(groups:split(',')) do @@ -331,7 +366,7 @@ function unified_inventory.items_in_group(groups) return items end -function unified_inventory.sort_inventory(inv) +function ui.sort_inventory(inv) local inlist = inv:get_list("main") local typecnt = {} local typekeys = {} diff --git a/unified_inventory/mod.conf b/unified_inventory/mod.conf index bca70ca..89be528 100644 --- a/unified_inventory/mod.conf +++ b/unified_inventory/mod.conf @@ -5,3 +5,4 @@ description = """ Unified Inventory replaces the default survival and creative inventory. It adds a nicer interface and a number of features, such as a crafting guide. """ +min_minetest_version = 5.4.0 diff --git a/unified_inventory/register.lua b/unified_inventory/register.lua index 017e4af..3cb5f59 100644 --- a/unified_inventory/register.lua +++ b/unified_inventory/register.lua @@ -1,6 +1,7 @@ local S = minetest.get_translator("unified_inventory") local NS = function(s) return s end local F = minetest.formspec_escape +local ui = unified_inventory minetest.register_privilege("creative", { description = S("Can use the creative inventory"), @@ -12,10 +13,9 @@ minetest.register_privilege("ui_full", { give_to_singleplayer = false, }) - local trash = minetest.create_detached_inventory("trash", { --allow_put = function(inv, listname, index, stack, player) - -- if unified_inventory.is_creative(player:get_player_name()) then + -- if ui.is_creative(player:get_player_name()) then -- return stack:get_count() -- else -- return 0 @@ -29,19 +29,19 @@ local trash = minetest.create_detached_inventory("trash", { }) trash:set_size("main", 1) -unified_inventory.register_button("craft", { +ui.register_button("craft", { type = "image", image = "ui_craft_icon.png", tooltip = S("Crafting Grid") }) -unified_inventory.register_button("craftguide", { +ui.register_button("craftguide", { type = "image", image = "ui_craftguide_icon.png", tooltip = S("Crafting Guide") }) -unified_inventory.register_button("home_gui_set", { +ui.register_button("home_gui_set", { type = "image", image = "ui_sethome_icon.png", tooltip = S("Set home position"), @@ -49,8 +49,8 @@ unified_inventory.register_button("home_gui_set", { action = function(player) local player_name = player:get_player_name() if minetest.check_player_privs(player_name, {home=true}) then - unified_inventory.set_home(player, player:get_pos()) - local home = unified_inventory.home_pos[player_name] + ui.set_home(player, player:get_pos()) + local home = ui.home_pos[player_name] if home ~= nil then minetest.sound_play("dingdong", {to_player=player_name, gain = 1.0}) @@ -60,7 +60,7 @@ unified_inventory.register_button("home_gui_set", { else minetest.chat_send_player(player_name, S("You don't have the \"home\" privilege!")) - unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name]) + ui.set_inventory_formspec(player, ui.current_page[player_name]) end end, condition = function(player) @@ -68,7 +68,7 @@ unified_inventory.register_button("home_gui_set", { end, }) -unified_inventory.register_button("home_gui_go", { +ui.register_button("home_gui_go", { type = "image", image = "ui_gohome_icon.png", tooltip = S("Go home"), @@ -76,13 +76,13 @@ unified_inventory.register_button("home_gui_go", { action = function(player) local player_name = player:get_player_name() if minetest.check_player_privs(player_name, {home=true}) then - if unified_inventory.go_home(player) then + if ui.go_home(player) then minetest.sound_play("teleport", {to_player = player_name}) end else minetest.chat_send_player(player_name, S("You don't have the \"home\" privilege!")) - unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name]) + ui.set_inventory_formspec(player, ui.current_page[player_name]) end end, condition = function(player) @@ -90,7 +90,7 @@ unified_inventory.register_button("home_gui_go", { end, }) -unified_inventory.register_button("misc_set_day", { +ui.register_button("misc_set_day", { type = "image", image = "ui_sun_icon.png", tooltip = S("Set time to day"), @@ -106,7 +106,7 @@ unified_inventory.register_button("misc_set_day", { else minetest.chat_send_player(player_name, S("You don't have the settime privilege!")) - unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name]) + ui.set_inventory_formspec(player, ui.current_page[player_name]) end end, condition = function(player) @@ -114,7 +114,7 @@ unified_inventory.register_button("misc_set_day", { end, }) -unified_inventory.register_button("misc_set_night", { +ui.register_button("misc_set_night", { type = "image", image = "ui_moon_icon.png", tooltip = S("Set time to night"), @@ -130,7 +130,7 @@ unified_inventory.register_button("misc_set_night", { else minetest.chat_send_player(player_name, S("You don't have the settime privilege!")) - unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name]) + ui.set_inventory_formspec(player, ui.current_page[player_name]) end end, condition = function(player) @@ -138,19 +138,19 @@ unified_inventory.register_button("misc_set_night", { end, }) -unified_inventory.register_button("clear_inv", { +ui.register_button("clear_inv", { type = "image", image = "ui_trash_icon.png", tooltip = S("Clear inventory"), action = function(player) local player_name = player:get_player_name() - if not unified_inventory.is_creative(player_name) then + if not ui.is_creative(player_name) then minetest.chat_send_player(player_name, S("This button has been disabled outside" .." of creative mode to prevent" .." accidental inventory trashing." .."\nUse the trash slot instead.")) - unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name]) + ui.set_inventory_formspec(player, ui.current_page[player_name]) return end player:get_inventory():set_list("main", {}) @@ -159,35 +159,42 @@ unified_inventory.register_button("clear_inv", { {to_player=player_name, gain = 1.0}) end, condition = function(player) - return unified_inventory.is_creative(player:get_player_name()) + return ui.is_creative(player:get_player_name()) end, }) -unified_inventory.register_page("craft", { +ui.register_page("craft", { get_formspec = function(player, perplayer_formspec) - local formspecy = perplayer_formspec.formspec_y - local formheadery = perplayer_formspec.form_header_y + local formheaderx = perplayer_formspec.form_header_x + local formheadery = perplayer_formspec.form_header_y + local craftx = perplayer_formspec.craft_x + local crafty = perplayer_formspec.craft_y local player_name = player:get_player_name() - local formspec = "background[2,"..formspecy..";6,3;ui_crafting_form.png]" - formspec = formspec.."background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]" - formspec = formspec.."label[0,"..formheadery..";" ..F(S("Crafting")).."]" - formspec = formspec.."listcolors[#00000000;#00000000]" - formspec = formspec.."list[current_player;craftpreview;6,"..formspecy..";1,1;]" - formspec = formspec.."list[current_player;craft;2,"..formspecy..";3,3;]" - if unified_inventory.trash_enabled or unified_inventory.is_creative(player_name) or minetest.get_player_privs(player_name).give then - formspec = formspec.."label[7,"..(formspecy + 1.5)..";" .. F(S("Trash:")) .. "]" - formspec = formspec.."background[7,"..(formspecy + 2)..";1,1;ui_single_slot.png]" - formspec = formspec.."list[detached:trash;main;7,"..(formspecy + 2)..";1,1;]" + local formspec = { + perplayer_formspec.standard_inv_bg, + perplayer_formspec.craft_grid, + "label["..formheaderx..","..formheadery..";" ..F(S("Crafting")).."]", + "listcolors[#00000000;#00000000]", + "listring[current_name;craft]", + "listring[current_player;main]" + } + local n=#formspec+1 + + if ui.trash_enabled or ui.is_creative(player_name) or minetest.get_player_privs(player_name).give then + formspec[n] = string.format("label[%f,%f;%s]", craftx + 6.45, crafty + 2.4, F(S("Trash:"))) + formspec[n+1] = ui.make_trash_slot(craftx + 6.25, crafty + 2.5) + n=n + 2 end - formspec = formspec.."listring[current_name;craft]" - formspec = formspec.."listring[current_player;main]" - if unified_inventory.is_creative(player_name) then - formspec = formspec.."label[0,"..(formspecy + 1.5)..";" .. F(S("Refill:")) .. "]" - formspec = formspec.."list[detached:"..F(player_name).."refill;main;0,"..(formspecy +2)..";1,1;]" + + if ui.is_creative(player_name) then + formspec[n] = ui.single_slot(craftx - 2.5, crafty + 2.5) + formspec[n+1] = string.format("label[%f,%f;%s]", craftx - 2.3, crafty + 2.4,F(S("Refill:"))) + formspec[n+2] = string.format("list[detached:%srefill;main;%f,%f;1,1;]", + F(player_name), craftx - 2.5 + ui.list_img_offset, crafty + 2.5 + ui.list_img_offset) end - return {formspec=formspec} + return {formspec=table.concat(formspec)} end, }) @@ -206,18 +213,18 @@ local function stack_image_button(x, y, w, h, buttonname_prefix, item) local selectitem = name if name:sub(1, 6) == "group:" then local group_name = name:sub(7) - local group_item = unified_inventory.get_group_item(group_name) + local group_item = ui.get_group_item(group_name) show_is_group = not group_item.sole displayitem = group_item.item or "unknown" selectitem = group_item.sole and displayitem or name end local label = show_is_group and "G" or "" - local buttonname = F(buttonname_prefix..unified_inventory.mangle_for_formspec(selectitem)) + local buttonname = F(buttonname_prefix..ui.mangle_for_formspec(selectitem)) local button = string.format("item_image_button[%f,%f;%f,%f;%s;%s;%s]", x, y, w, h, F(displayitem), buttonname, label) if show_is_group then - local groupstring, andcount = unified_inventory.extract_groupnames(name) + local groupstring, andcount = ui.extract_groupnames(name) local grouptip if andcount == 1 then grouptip = S("Any item belonging to the @1 group", groupstring) @@ -257,26 +264,33 @@ local other_dir = { usage = "recipe", } -unified_inventory.register_page("craftguide", { +ui.register_page("craftguide", { get_formspec = function(player, perplayer_formspec) - local formspecy = perplayer_formspec.formspec_y - local formheadery = perplayer_formspec.form_header_y - local craftresultx = perplayer_formspec.craft_result_x - local craftresulty = perplayer_formspec.craft_result_y + local craftguidex = perplayer_formspec.craft_guide_x + local craftguidey = perplayer_formspec.craft_guide_y + local craftguidearrowx = perplayer_formspec.craft_guide_arrow_x + local craftguideresultx = perplayer_formspec.craft_guide_result_x + local formheaderx = perplayer_formspec.form_header_x + local formheadery = perplayer_formspec.form_header_y + local give_x = perplayer_formspec.give_btn_x local player_name = player:get_player_name() local player_privs = minetest.get_player_privs(player_name) - local fs = { - "background[0,"..(formspecy + 3.5)..";8,4;ui_main_inventory.png]", - "label[0,"..formheadery..";" .. F(S("Crafting Guide")) .. "]", + + local formspec = { + perplayer_formspec.standard_inv_bg, + "label["..formheaderx..","..formheadery..";" .. F(S("Crafting Guide")) .. "]", "listcolors[#00000000;#00000000]" } - local item_name = unified_inventory.current_item[player_name] + + local item_name = ui.current_item[player_name] if not item_name then - return { formspec = table.concat(fs) } + return { formspec = table.concat(formspec) } end + local n = 4 + local item_name_shown if minetest.registered_items[item_name] and minetest.registered_items[item_name].description then @@ -286,51 +300,60 @@ unified_inventory.register_page("craftguide", { item_name_shown = item_name end - local dir = unified_inventory.current_craft_direction[player_name] + local dir = ui.current_craft_direction[player_name] local rdir = dir == "recipe" and "usage" or "recipe" - local crafts = unified_inventory.crafts_for[dir][item_name] - local alternate = unified_inventory.alternate[player_name] + local crafts = ui.crafts_for[dir][item_name] + local alternate = ui.alternate[player_name] local alternates, craft if crafts and #crafts > 0 then alternates = #crafts craft = crafts[alternate] end - local has_give = player_privs.give or unified_inventory.is_creative(player_name) + local has_give = player_privs.give or ui.is_creative(player_name) - fs[#fs + 1] = "background[0.5,"..(formspecy + 0.2)..";8,3;ui_craftguide_form.png]" - fs[#fs + 1] = string.format("textarea[%f,%f;10,1;;%s: %s;]", - craftresultx, craftresulty, F(role_text[dir]), item_name_shown) - fs[#fs + 1] = stack_image_button(0, formspecy, 1.1, 1.1, - "item_button_" .. rdir .. "_", ItemStack(item_name)) + formspec[n] = string.format("image[%f,%f;%f,%f;ui_crafting_arrow.png]", + craftguidearrowx, craftguidey, ui.imgscale, ui.imgscale) + + formspec[n+1] = string.format("textarea[%f,%f;10,1;;%s: %s;]", + perplayer_formspec.craft_guide_resultstr_x, perplayer_formspec.craft_guide_resultstr_y, + F(role_text[dir]), item_name_shown) + n = n + 2 + + local giveme_form = table.concat({ + "label[".. (give_x+0.1)..",".. (craftguidey + 2.7) .. ";" .. F(S("Give me:")) .. "]", + "button["..(give_x)..",".. (craftguidey + 2.9) .. ";0.75,0.5;craftguide_giveme_1;1]", + "button["..(give_x+0.8)..",".. (craftguidey + 2.9) .. ";0.75,0.5;craftguide_giveme_10;10]", + "button["..(give_x+1.6)..",".. (craftguidey + 2.9) .. ";0.75,0.5;craftguide_giveme_99;99]" + }) if not craft then -- No craft recipes available for this item. - fs[#fs + 1] = "label[5.5,"..(formspecy + 2.35)..";" - .. F(no_recipe_text[dir]) .. "]" - local no_pos = dir == "recipe" and 4.5 or 6.5 - local item_pos = dir == "recipe" and 6.5 or 4.5 - fs[#fs + 1] = "image["..no_pos..","..formspecy..";1.1,1.1;ui_no.png]" - fs[#fs + 1] = stack_image_button(item_pos, formspecy, 1.1, 1.1, + formspec[n] = string.format("label[%f,%f;%s]", craftguidex+2.5, craftguidey+1.5, F(no_recipe_text[dir])) + local no_pos = dir == "recipe" and (craftguidex+2.5) or craftguideresultx + local item_pos = dir == "recipe" and craftguideresultx or (craftguidex+2.5) + formspec[n+1] = "image["..no_pos..","..craftguidey..";1.2,1.2;ui_no.png]" + formspec[n+2] = stack_image_button(item_pos, craftguidey, 1.2, 1.2, "item_button_" .. other_dir[dir] .. "_", ItemStack(item_name)) if has_give then - fs[#fs + 1] = "label[0," .. (formspecy + 2.10) .. ";" .. F(S("Give me:")) .. "]" - .. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]" - .. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]" - .. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]" + formspec[n+3] = giveme_form end - return { formspec = table.concat(fs) } + return { formspec = table.concat(formspec) } + else + formspec[n] = stack_image_button(craftguideresultx, craftguidey, 1.2, 1.2, + "item_button_" .. rdir .. "_", ItemStack(craft.output)) + n = n + 1 end - local craft_type = unified_inventory.registered_craft_types[craft.type] or - unified_inventory.craft_type_defaults(craft.type, {}) + local craft_type = ui.registered_craft_types[craft.type] or + ui.craft_type_defaults(craft.type, {}) if craft_type.icon then - fs[#fs + 1] = string.format("image[%f,%f;%f,%f;%s]", - 5.7, (formspecy + 0.05), 0.5, 0.5, craft_type.icon) + formspec[n] = string.format("image[%f,%f;%f,%f;%s]", + craftguidearrowx+0.35, craftguidey, 0.5, 0.5, craft_type.icon) + n = n + 1 end - fs[#fs + 1] = "label[5.5,"..(formspecy + 1)..";" .. F(craft_type.description).."]" - fs[#fs + 1] = stack_image_button(6.5, formspecy, 1.1, 1.1, - "item_button_usage_", ItemStack(craft.output)) + formspec[n] = string.format("label[%f,%f;%s]", craftguidearrowx + 0.15, craftguidey + 1.4, F(craft_type.description)) + n = n + 1 local display_size = craft_type.dynamic_display_size and craft_type.dynamic_display_size(craft) @@ -341,11 +364,12 @@ unified_inventory.register_page("craftguide", { -- This keeps recipes aligned to the right, -- so that they're close to the arrow. - local xoffset = 5.5 + local xoffset = craftguidex+3.75 + local bspc = 1.25 -- Offset factor for crafting grids with side length > 4 local of = (3/math.max(3, math.max(display_size.width, display_size.height))) local od = 0 - -- Minimum grid size at which size optimazation measures kick in + -- Minimum grid size at which size optimization measures kick in local mini_craft_size = 6 if display_size.width >= mini_craft_size then od = math.max(1, display_size.width - 2) @@ -354,12 +378,12 @@ unified_inventory.register_page("craftguide", { -- Size modifier factor local sf = math.min(1, of * (1.05 + 0.05*od)) -- Button size - local bsize_h = 1.1 * sf - local bsize_w = bsize_h - if display_size.width >= mini_craft_size then - bsize_w = 1.175 * sf + local bsize = 1.2 * sf + + if display_size.width >= mini_craft_size then -- it's not a normal 3x3 grid + bsize = 0.8 * sf end - if (bsize_h > 0.35 and display_size.width) then + if (bsize > 0.35 and display_size.width) then for y = 1, display_size.height do for x = 1, display_size.width do local item @@ -369,48 +393,53 @@ unified_inventory.register_page("craftguide", { -- Flipped x, used to build formspec buttons from right to left local fx = display_size.width - (x-1) -- x offset, y offset - local xof = (fx-1) * of + of - local yof = (y-1) * of + 1 + local xof = ((fx-1) * of + of) * bspc + local yof = ((y-1) * of + 1) * bspc if item then - fs[#fs + 1] = stack_image_button( - xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h, + formspec[n] = stack_image_button( + xoffset - xof, craftguidey - 1.25 + yof, bsize, bsize, "item_button_recipe_", ItemStack(item)) else -- Fake buttons just to make grid - fs[#fs + 1] = string.format("image_button[%f,%f;%f,%f;ui_blank_image.png;;]", - xoffset - xof, formspecy - 1 + yof, bsize_w, bsize_h) + formspec[n] = string.format("image_button[%f,%f;%f,%f;ui_blank_image.png;;]", + xoffset - xof, craftguidey - 1.25 + yof, bsize, bsize) end + n = n + 1 end end else -- Error - fs[#fs + 1] = string.format("label[2,%f;%s]", - formspecy, F(S("This recipe is too@nlarge to be displayed."))) + formspec[n] = string.format("label[2,%f;%s]", + craftguidey, F(S("This recipe is too@nlarge to be displayed."))) + n = n + 1 end if craft_type.uses_crafting_grid and display_size.width <= 3 then - fs[#fs + 1] = "label[0," .. (formspecy + 0.9) .. ";" .. F(S("To craft grid:")) .. "]" - .. "button[0, " .. (formspecy + 1.5) .. ";0.6,0.5;craftguide_craft_1;1]" - .. "button[0.6," .. (formspecy + 1.5) .. ";0.7,0.5;craftguide_craft_10;10]" - .. "button[1.3," .. (formspecy + 1.5) .. ";0.8,0.5;craftguide_craft_max;" .. F(S("All")) .. "]" + formspec[n] = "label["..(give_x+0.1)..",".. (craftguidey + 1.7) .. ";" .. F(S("To craft grid:")) .. "]" + formspec[n+1] = "button[".. (give_x)..",".. (craftguidey + 1.9) .. ";0.75,0.5;craftguide_craft_1;1]" + formspec[n+2] = "button[".. (give_x+0.8)..",".. (craftguidey + 1.9) .. ";0.75,0.5;craftguide_craft_10;10]" + formspec[n+3] = "button[".. (give_x+1.6)..",".. (craftguidey + 1.9) .. ";0.75,0.5;craftguide_craft_max;" .. F(S("All")) .. "]" + n = n + 4 end + if has_give then - fs[#fs + 1] = "label[0," .. (formspecy + 2.1) .. ";" .. F(S("Give me:")) .. "]" - .. "button[0, " .. (formspecy + 2.7) .. ";0.6,0.5;craftguide_giveme_1;1]" - .. "button[0.6," .. (formspecy + 2.7) .. ";0.7,0.5;craftguide_giveme_10;10]" - .. "button[1.3," .. (formspecy + 2.7) .. ";0.8,0.5;craftguide_giveme_99;99]" + formspec[n] = giveme_form + n = n + 1 end if alternates and alternates > 1 then - fs[#fs + 1] = "label[5.5," .. (formspecy + 1.6) .. ";" - .. F(S(recipe_text[dir], alternate, alternates)) .. "]" - .. "image_button[5.5," .. (formspecy + 2) .. ";1,1;ui_left_icon.png;alternate_prev;]" - .. "image_button[6.5," .. (formspecy + 2) .. ";1,1;ui_right_icon.png;alternate;]" - .. "tooltip[alternate_prev;" .. F(prev_alt_text[dir]) .. "]" - .. "tooltip[alternate;" .. F(next_alt_text[dir]) .. "]" + formspec[n] = string.format("label[%f,%f;%s]", + craftguidex+4, craftguidey + 2.3, F(S(recipe_text[dir], alternate, alternates))) + formspec[n+1] = string.format("image_button[%f,%f;1.1,1.1;ui_left_icon.png;alternate_prev;]", + craftguidearrowx+0.2, craftguidey + 2.6) + formspec[n+2] = string.format("image_button[%f,%f;1.1,1.1;ui_right_icon.png;alternate;]", + craftguidearrowx+1.35, craftguidey + 2.6) + formspec[n+3] = "tooltip[alternate_prev;" .. F(prev_alt_text[dir]) .. "]" + formspec[n+4] = "tooltip[alternate;" .. F(next_alt_text[dir]) .. "]" end - return { formspec = table.concat(fs) } + + return { formspec = table.concat(formspec) } end, }) @@ -418,7 +447,7 @@ local function craftguide_giveme(player, formname, fields) local player_name = player:get_player_name() local player_privs = minetest.get_player_privs(player_name) if not player_privs.give and - not unified_inventory.is_creative(player_name) then + not ui.is_creative(player_name) then minetest.log("action", "[unified_inventory] Denied give action to player " .. player_name) return @@ -433,7 +462,7 @@ local function craftguide_giveme(player, formname, fields) amount = tonumber(amount) or 0 if amount == 0 then return end - local output = unified_inventory.current_item[player_name] + local output = ui.current_item[player_name] if (not output) or (output == "") then return end local player_inv = player:get_inventory() @@ -454,21 +483,21 @@ local function craftguide_craft(player, formname, fields) local player_name = player:get_player_name() - local output = unified_inventory.current_item[player_name] or "" + local output = ui.current_item[player_name] or "" if output == "" then return end - local crafts = unified_inventory.crafts_for[ - unified_inventory.current_craft_direction[player_name]][output] or {} + local crafts = ui.crafts_for[ + ui.current_craft_direction[player_name]][output] or {} if #crafts == 0 then return end - local alternate = unified_inventory.alternate[player_name] + local alternate = ui.alternate[player_name] local craft = crafts[alternate] if craft.width > 3 then return end - unified_inventory.craftguide_match_craft(player, "main", "craft", craft, amount) + ui.craftguide_match_craft(player, "main", "craft", craft, amount) - unified_inventory.set_inventory_formspec(player, "craft") + ui.set_inventory_formspec(player, "craft") end minetest.register_on_player_receive_fields(function(player, formname, fields) diff --git a/unified_inventory/settingtypes.txt b/unified_inventory/settingtypes.txt index 910989f..27768ac 100644 --- a/unified_inventory/settingtypes.txt +++ b/unified_inventory/settingtypes.txt @@ -9,3 +9,6 @@ unified_inventory_bags (Enable bags) bool true #If enabled, the trash slot can be used by those without both creative #and the give privilege. unified_inventory_trash (Enable trash) bool true + + +unified_inventory_automatic_categorization (Items automatically added to categories) bool true \ No newline at end of file diff --git a/unified_inventory/textures/ui_bags_lg_form.png b/unified_inventory/textures/ui_bags_lg_form.png deleted file mode 100644 index 24dab308b163f3e395886aea17223b05e530ad7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8119 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 diff --git a/unified_inventory/textures/ui_bags_med_form.png b/unified_inventory/textures/ui_bags_med_form.png deleted file mode 100644 index 27a1591c0af720bf9f5279a3e60af124052126cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7197 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 diff --git a/unified_inventory/textures/ui_bags_sm_form.png b/unified_inventory/textures/ui_bags_sm_form.png deleted file mode 100644 index 6234eb8820eedbe0e6d8c3524f532d48459ee11e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6411 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 literal 0 HcmV?d00001 diff --git a/unified_inventory/textures/ui_category_none.png b/unified_inventory/textures/ui_category_none.png new file mode 100644 index 0000000000000000000000000000000000000000..8976fb0eb122a801499fd2dbe009c0449fed1a6e GIT binary patch literal 7966 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 UNozg^(*OVf07*qoM6N<$f(*TUkpKVy literal 0 HcmV?d00001 diff --git a/unified_inventory/textures/ui_craftguide_form.png b/unified_inventory/textures/ui_craftguide_form.png deleted file mode 100644 index d9be53f7dd54cad096c80fb9d7a784ba09bb7078..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 962 zcmeAS@N?(olHy`uVBq!ia0y~yU;;834sb97N!N?op$rU+VF5lNu0U=^MuwM{*VL&~ zTUuJ8qM{NL6RoYSKYsjJQBeUDkB^T}N=gFCCL|;@G&ERQS=rj!nwy(jT3P~?LC86C z<^WaN+1UX_GBY#(|Nrmi=Jw~$pTfdI3k!>?sw!7k*S&l9&YU?DD0Sh&h3V6$&jy3{ z@8AFZ`}fP2FFC&Zb+qMBM1zNFg-MYBAxE(uo zT)TEHKR^Hb_wT;GzCfn}-5eSkdh6D$($dnhva+>n*S5B{-nemN(xgd1m)P6ecXoDe z*|KHRrcDbMF5IEakt5%>0vYjH@RK*PmzE^3^O67SyW z<*TgQv3vLL|K@3qj-g?bSy}hkr5FD{5FT58S@tw2lA++hiHsDtuW2dAKHPZa(ZhQ^ zZ++*U6@K^kZnV9$=+}dbtNEP+)z+-v@+Ca)Z`jH|HPc?*w~Vcct`SN-H0@-PrIc~{ zb+gE|{^qyfXNSYQv&$TO!0WITMQhe4QOUahm;sKg-WAUuTYdS2nD=@0K>lLh`>U z%x_@&Mb{OHjw=y@D>rKWqMth^|`6r5!k z7#JMFmmG8WlX`>uKpcY_S>__#V%_+~C9A|Gi^nbN%TakYpy>>M7?>Cv4hjF;KN-aH MboFyt=akR{0KFTassI20 diff --git a/unified_inventory/textures/ui_crafting_arrow.png b/unified_inventory/textures/ui_crafting_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..6901c58888398695f07f51a97b6815b92c7c482e GIT binary patch literal 788 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2{2-Fx~caaSW-r_4dYDZ;?b9_7CsR zXuQ@t|7O?ajGihj8yCSVXT3{GBwyJJbl%&rNZjbeo1<|$hSiEX!m1q?irN1P{}SF) za+S|fV2au_WB>NT;On8~->;l`|9hui@sGOi55MnWaKQ$?*jr~@`!;vx6sB+fWml#g z_3~moZqJrmohG*BhG@c~r(3M=C3fg4G;~ihes?=ddxf_b$@YX$p7wwy!O+_y> zl`Tm)vaDrS{X+Aw1SbDK1!X(hm|iWM(D_rR;~cxhr~0#fU6M`F#}n_}_`RyYF@fJ-mFPvvN_uw6FVuBla0>%gOy2Yg&=#QBN>|8!^VS#^6s zlkDPRhXzrWXfwO*7QeRaTXJmG9zJ;oYpEomde-}9xp{$qmVNy({p##h_nG^Y_>IN* zmG*yRjsGaj>oK*Pzc#q{+6RS20!lqcAH^R~uW4(pefs$O%7=6J-QU)5_+uq6GegPi zO&@<>y^t5aw1tCF!sB3^eC>tpS0CSf-^F?Bd+>>cl6pY5?9SRS>2y(2-rtv7fApR> z)UC6k(zs~wEk4~SE zsn=z1m(QX5V0YZ5ZsDZI%%`ggi|)0=c)xc!&i*XrxVjPs(0Z=#c479d8~ZI*15-PL Mr>mdKI;Vst09D^+`v3p{ literal 0 HcmV?d00001 diff --git a/unified_inventory/textures/ui_crafting_form.png b/unified_inventory/textures/ui_crafting_form.png deleted file mode 100644 index 904328799f0095583663358921550fe04a99fb02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2343 zcma)8c|4Ts9)D?$Gs!Yz%dup7W$AD%sgRv9hA`Br6lIyIAraY^XeLQUldWcuu_SJX zvd(ZRLndp7QehCHLy}R^plP@>y61Cm=bn4+eg1eqzvuV3VB zTgDZwqRH;}TB?T3J91Xb&yM=p^5mcK+aZ_|V`=u4u?*URM)Oh*d`=?O#&BOjesjnp z5IbC%%Q0q{2NkGBp5?q54U*iXp1luiy@U11B*m+jX0d4adx_=N-i7G!t&*Y{0cZeg z6wG@4^VHIQW05-VJulCmi8+%vlmFddtu-z-^Mma7Yn@}u*HBLqUW6w;4APRNompkr zgU{pw`in;9-#xf4;?MMJOq6NW(u)(TxufM1t%Pe>qeuetN1T^2ptkODK>gu!qmUgT zlqg`hlFBJS`T)r0oeJuLSuO{oc?tDPA^(@zwWM_{m}-rLr)Z19?GeRLq#Pjygt
ZEv7VNgIIfqiM#UpE34`10a@rQScZjR5> zXw+Z-qBUw`P6wG^j7e#5!?byX=-ySn>(E9 z3Jx4ewT=u`Q}31ya5r%19bb`lxv^@Q7$STvt8w0O?3q1lFYXPe%qJ*YO)Mw?ortl? zxr+9__1B4%bNW^Yv-jspdgTZes9>J`jvCeS^|r*BQp1`&wSKeU1w+zOBwGBywh7vP z?yg!#IJ$M0PF^JS=xr#gI;n?xJk@$Sa>(*jPJ$wdpIeF8{LSXK7?k!6p@fjjf}+t> z%WEAXLpT?nd_O;{NqGfqH7MOV+1cgjXeV}ot5eF;g1?jMa@b~2+LiUJ#F6ynSRh1r zrU`vPZLon+Hk=Gab}NWDxKi|XuBOyS-rMBQJl!}x^l{W_sx7kIrLYhoT;XYwL+IIYLn^oY0~$>1{=4hq$+jU2id2avdWTuHF|fH>@HSg%X`k9i1u$u;_BSy zT{Zg<+sh9ampWFJ;-0&s)RFN@FG7#Q49?gr#IN#*dqX|HCC%-vTf38oO2noSe{bAu@14#l$Cxws=`QH z?9rR}$j=YuNofI0lw9jGF57o`n%w(xz(AY#Q?*D2Y;X#3&NPnuxZ`WVRM@L8WQVAy z2v|-s#9;3f%Gq^~D>GcynzyGIx@}}U2H*Ng7nVcAePI!-YL@x8-~s|WHq7H5X8knI zK;v>jmwn+)`48%M0rP{&;0o(xpY`5P5DCmhiNW<6kwX5k8|2dMEj~zUtQZ->`6y-0 zqZdIXE_50{@`G%|V8)Mvqssgv9siW0|9|o^@K0KQv&0~6)`;;m;k-66DQR{wTIGVv zK%MuN)Ix&3j083pB;UN5y&DJkGxy51a?;~pi(qrjO5;p82}B}VJEGlpcMg^iBNJU8QIRMqEape*v^(?vVH0nHJaYGR$I5D;}P7wslhf zoN>IG&bf41%-NsmDkpNE7M3_%e^Ei#FI)3UKxRa|78XjYsNE`VVgrOq(Dk7zAygq*Mk3U1?s62 z)%^S@1+PBQj9JHajLM37+_;sJ4ewA^(uk|3Is0P}30bIKy6? z-HLy?cxQPEYTE^mRtldr#Gq6@zMYI{7dFr|p{scJ@M1V2CKXH2;kx(RrH%$RM^A_e zY+K$Q_$w(B$W62le5Gyhf)6_82QWG6R0|9iuFqrGOGw{a>^7;=_J_@U@|QQGw? jlec05o}Jtfv9&D{HJrW~MBtj>2MBPqcSYW_^&$QP6{$O_ diff --git a/unified_inventory/textures/ui_form_bg.png b/unified_inventory/textures/ui_form_bg.png deleted file mode 100644 index d973b34a9d397be23f563a3d08a3f6f34747cb04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1650 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 diff --git a/unified_inventory/textures/ui_misc_form.png b/unified_inventory/textures/ui_misc_form.png deleted file mode 100644 index 2d6ad018d4b51f3a0315475e0a1e070a08521d55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5368 zcmd^Bc{G%L-$#Xn$-YFEEXls_TNwM2Z5nGBX0q2*C|N@!`;wsu*_SlgLWB^;5@X30 zF|vo^o-Fgs7`^Xv-lzM%-{(C4yyv`g&YbIaeShEm`h1sYGZS4JDt0Oo5)vAHJ#BLm z60$D*--q%fp3>W}QA$F>*sQOuY3c8<+9G)xPb8DrE8Jm@ed&rn|3Ch5{2z+lY-i1_ zx>%I@#J2{J^^PebyTED)&TT17Ij49E3Nv-L~zh3r1*;EK09YF*e$~~O%c(7rWQS% z3Dc745kdA!>9Uv=tj0W#$bK7KHTk($ia?|@n-dtyE^$dy;4;F*QVkOLdOQtt;$5k8 zlfXio=aiuatFa4_urz|o8irig&MX@Ba$Q{}ZhiQN8-3@o`W4~lb}S=)F*x_iB=X*v z9VacVlkeVhHu{v&BP7!Qq~3;s=+wAUE`@X4bsnwm8i<_A_u4dyXUR{#2%C%xD!B1> z1AD)_7oR@%BH$(MFfeLLC+fqK2fn;)XMaw_Xh?A;aZ{*3q}sD|Tjo)LU;%A{!y?k5 z?R;_a@oLNiU+R5p^)B1x}S)vOb^9-mLN8y|r=bIh)$t2{lQI^@g;p&_}KxzY#MW7p)k>ECHbR@vIap z&xvQAGYA$^b*8Oy-l>^Z4``BffFmr#eo$j0;?HX$TnT9wW!Jm7oD(JBF#YKedR@;s z=2Fi~i*}=^ns?U%7RP~`;yRsTf)=AJ{X9f|8fRW<-U-&-QIH3B+HlH49coDql{Zn6 zI$pSw!zQ@qUpdOqE`U96S$E7FHFP#MGwgPqoL*ivPZ`wVW4DmEPar@B?51@qHZ~f$ zz%;~DwQeBMB6Z&Jwpguovq`hH_OUrp5rj&<-r*SQuqnh@h=%|~%N_K2b|q0wcA{3BFmXlv85y=ZD*S}{%b!bDf4=va8jQeP%Q zR9S%IyRx$1%T@`>2TlW?$vX#*W0uZ5j2|7Qx+MH@V{O5&Qy_CIWrb{+vK}L4*si@s zRonTGc8BIlE=V^NLbj7`;7c-gO6K^DEkPRIf|UW}fRUKVaE=pwB>`vgT!pL0 z5^^?@aO!Ibq!P!+5KVIY5q}v@{cj#yxKH54sHN~?|BK`3&JHOlB5I%v44=1M{6Rfd zX{t>0c(>xy_O|~%&66rHA)dSi^t1Ycu8Hna72A=Irl3k%|6M|u9zjU>u8Oy%6KiC) zqVmp|gHNOXjaQymDLqdueI*m-Wzmf>7k#3z?U#EgyZ_v6a#Legf?2FHR->;x+x)7G3l z2pM*@|IFS0^B5o8fYZQ-xVmH>u#hBgRXFF#qA%~f+AaBqU0+haH|0VdwI*_x?nsgP z-3hu`y3(?*(Nz)KWBg}_r|LHBBE1Ok~Rb zRQ*l>uaK#Oq6UB#`dYWjW^3!QecBIk`m0EcUsR z9Tj405qM_~oQ@^>ODlWT#P zzVuOljyhSWPyl;Onz^*XRbZPAcowkq^s#&^5Nwq8{p|sE=iR}&T7~(rYw#>(=X*zy zc`)WVvCnISw@?~qy%Tt*aZdD^tzUTuAZtNgm(Ab>$IwNVnNH4l|J0kRVdfIKYEw@9;+O$R z29o=J9Q0t_6LclCY%TNcLlB!zK1xY))|+wTu$uBahl*dai4NWBZJ!l{bZ7%B5*lS` z4K%`HBn7py7DGgAzmeu|5<$X&`P$1VEvJ#%LKWboDH6VTK*yn3p!*bJ>RzBdZ_1gOSV>wInR6B`J{_JA{$bFq=U zp&VN4sMo_^lGx<*^!4SjEanltyLxi0V#Dr}zx-W~axkw1+x;or^5B$Xm-b887y3@p4mK-zA7d{rxekzs4uhw!k^kQ8>TrP#fc%g_-5-c zBGXIv^~K;*V%Llcnhvojev*D-q?)94to9tMh@b8cPYDJ)qunVs=|e!o3|WvUrOO$+ zzR*^1U@~TIYCOq}w~DKU97a7=*3)E-Y?Xk?Udr{p%TbvV_4NR7C8ADJPcCeJll6(t z8Ss8-yt#pkmv?xeeP5<|{Nw7<;Pwge1BNBJ2~Q+Su2xfR+OUb!>n`rh#-MuQ4LoVW z6OOIR%8XaVY1b!wsMG7lrGc>WonVQQ)t!$>J~F|8_N zvloG6&5w@n6Y1zq+nI*ma;&u0^aJ(F_;)Z$fn`|Y-4NGn7CyP%ynw={s$iNc{ z6xF&47&Fk{F2!Va5Z*J~N)x*slY|t8ij-6ezl-Y_yGWyChK!JCJ)C~?w|1MlH|(>r zT*B_`nFMA;Iq=Pj0}tH)J)aVKcp$c97O@h8AZo%uOy{>Ne8x)hL+`X%_+usVOk=^- z!)CmBQ_x>LZh5K|_vL52ySq!dOxlK3=q;>TON)!GyUKccD#dDPHY@{eicLYx31?8pq}I_bae>UomPQ?o-z;q}t)*sY=ayD*RYi&*@65snw> zi^Gh}3L{Isi=9*Q{KRX$HztFt>f<#XUJJg>XjX+nEmKoK3mOf}?z)*ld-uv!e;`5# zVh=I%?WWr-iL*Y6LrU-E`JRa812YxRYR7Z!vME z=DdFI_O=Umv30}Xm6Is80eo`HErP|1WKa~|o^e?85_SkKCn|HLt+B+dwO%?VSMdZM zU)b&}gW*6ccIhspurWV-5sV4l_ComhGN9TLHQGu0YL7S3YBSc{3i8&Hk*GO4tt3GI zLNqW*%R1cmJu+#ZM`tjL6#q3n4bh%5&efw5Rr>{1M~EI@b|fXiFG~1Lhp;T+7byJZ zLm&_qDm($dJ`srcUFh$)@L~imycj{(@5Bhizvud`3%|t1Q!WX4|0*Vf|EN@n8Cf+% ztFS6}9S9z@GI-1`yFVBWEW3!1&cP=qSlDX2(uJbFVVy}T3S^XPVN8Q6Vy~#LIKKBu zFR3q2{HwCf#cPqy!3OPGYrVMsmIb@vKl(P)N8WV-4JdSSKObFuX%;G}a(WdBS_~aO7v-HVfw(1sq{QX7U4F~V3>3WQ5vMd49S#+om(b*XBzTzA=FZGWLg-!9aTZ`UHyB9iX zyM$6vONL9m-+pc_M^2242;)8gbr}f7$2Kr&a~IyuJh^)C?(rYBP50eQ4+HkE8k!V@ z_zcQ#&ECCR*e47RLR1+!hUCzQbzu?2N!ND$YccGu!bUDf>_+W%n zhxj%2d*GSRlf}hO4fFCld3lsXVA&QPI+$HY04B#cwo8Y$ z3l}|YV7ZY2R;UMXD5DOX9N)vYQx#K-uq85X2~ZHiK*ntFQc08{=+~utF`_b_6SAwr zTRO+~qNFRZ_!yGvqE_2UK>EjgQDx+_F;IT2)^V>GPb(RRwYyh3Ib;88L^YHBU1k4S zA^&BG{-*x_sh9jJ{)x57 z!f#tnz-(5ed+~E^U_(FS(X`OmK3Ff0G#CmL-2o&)!4Hi=_dqv}`@~J0hlk<7>Wo7S?p=)cn-Q*cm{oC- zuRx(*skRk*9@2&#RXzh%CQio?jP=T6Co;d4RWb{Ag;xU77vpH@4}-wQSD=Mz@)fo^rn7`$0`dNqBoW^yoyP)Fl~ wlLnOnqty`OIMhw?+;fB*mh diff --git a/unified_inventory/textures/ui_single_slot.png b/unified_inventory/textures/ui_single_slot.png index 63da98d9188248e4fc8fa0f1d0a626af809e2b0f..2451623c93dec16b8c9c7d9a1c156857930ec58a 100644 GIT binary patch literal 648 zcmV;30(bq1P)XloE%A&LJ(4b!`QP^ttn@JHhnEMd z@7tQ{@j2R1Hr+s(_53Lf;!${Yr2Kz8<&Jc@B~2DfE{J8pGal3A`LS)gJiaL1kbrEh i6baU58AfpNFUD^dq1ONa4fwmRy0006!NklyonS3kDT19K7ItE#pdeToISix|Vy9qZ zr4Vcaf{h`C2v(*Mj0d?(+?{8!IbHP5#<`ruye(##f&J~9uVM!9;bS5}zg}*V26?wp0*mdZL{HlB?fbIG9f0r>3ZJtp7*@xJ@0wX zd*1V&_q^vl?|ILA-t+&$lk@ZYVL_vLu`S)8pvoh35H0EPfhJ=9uz*bkY z-)qHtHKKc5=57!nLJadM&MSQ1+KxmHF{W9-UgF<^dD})R3Wp7$MBN#`5vJGWNi}&)*)&>w!`zP{2CA%TWlCB10V-+{feQ>L07& z?m?b2ZDS-Pq|+fN`5Iy<%YAgB+sD2RS&R=4&_#W1v9n#0~Zdy?)PYSh-4*0!DyLKUG<5Cy@Df;avk|A6=-yzoL4RJ;(SqKKj( zg7grRmPTWZN#pK5%t->@@Wzv(uaTKA4NSd7rh`yVk|`3iisNF?cj^(HP3D zn1lgcB4QHa;cu}|-lsu}S!Owbi-RmtK4XUg_t;>AExLq5gdu>B#|-B<&m!{#xTq1l zqm>0x2DDjch3nklA0om%0G|n-V2M{a&omxNiMnuQS^9#VtZQRSO2j7D`GKGLgAQR1 zU}&(wC0^q!6R27Bq&w#|v=O2dafu?c{p4QR7 z^Zr78%J?|w5>7xMkO&|n=6<}~`!W1CqsKKq<0^Lv1wK)3rc&IA}Do z1Rn~(VzFdoMD}+7>&~5&HsA9RzpyQ6a+)`}!l|0R)Lg7L@o_L16bf;~U9g3Gw)>4) z?R|Xz?}R&i!l(Qx@t9?iY19k;Q?-CPK0ZD!4ho4lQcENXU9`cW#<|+-K8k4;nMX+) z%rcL6Qk|&>!pFtML6`anfJ7B-sEL>AOA3#9TKE!|69lLQH@BXHDaC$=-v^1rFl}CT zQ3M2d5{-*%#A9BqkXMYe1QLe}-i*MZftDB?R8vf7gGQsqvikkkQlOaA3j&2kNkD=p zJ(2rfj&=PO5NMiWGJhTlJt2!1YrOJ9Lz;aDEk{pB91FmH9r@t9HSQ7za2|1002M$5 zPyti`6+i`00aO4LKm||%Q~(vgu>*+z0}zjL2aanzAs#j#P$=>l&UocjG5#C{umXK7 zJ0Oc;rQTht1Tu&-YsE2azeb^PaBP_643d}~(rp_h<2GSF7&JK4iIP=IN-gTcWg;AXUD*`Z4!8E}s_>GGf-%i=DS?}l97!Nq418Gkt-rA?bi(q)5n zqHF1mUS<`0ElNCj2M-sI8ck;GcfAi&BG%a=ltgT>!Y1j}aIG(QC;O%tT{}l?7q3Vl zYBZSQac=kD4@uc%g>}-A$mUt~&cxZ87BHOZ$`U*w3+H=G(z2`lcROEK|E0FH(?QLmksj8}y5%Zf5 zo&VKa{5Uz`Fa!Iv#d8Ar*w|Hf fE>KaKqQb_IlIdwG-SNT~XbOX;tDnm{r-UW|$5SPz literal 0 HcmV?d00001 diff --git a/unified_inventory/textures/ui_trash_slot_icon.png b/unified_inventory/textures/ui_trash_slot_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc4de1dff740d7db6dec814fa59bab030a0ef1a GIT binary patch literal 697 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 diff --git a/unified_inventory/textures/ui_xyz_on_icon.png b/unified_inventory/textures/ui_xyz_on_icon.png deleted file mode 100644 index 003ea63344be5dbf1f7a2b5da8eb6911e4aeb329..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2182 zcmaKu`9Bkm1I8!i*i}f5ZKO}BIX{lcIoHV9D9W9jIhyO!T5^R*V#<~KEZd7*ZTL=fbaM9dYI!>XEw ztCMvHMkKG;PZ*znC}1cLB4Eo>@xVRciLTcv(<;n!RWltG$_bA<0x~RF7ieNZ=Kfpl zq|4kD1F3~VR=RTa^e%qj>yN6IZKsDdE!$KD!&Arqx8iGr1wte%E*7fC>v5hgyDdF& zIMepR$}?Cp8||xR91h9NIHJdi5BOof!n9 zNr{YjQ913cMtkkT;kcjka&pVNMlwaxhP)1E6x#)-Hcl%hL1@Ax!7+Bf#E6VB-LY43 zLK#YukOBwmpojFXGFk*h#On0|d07SVJWvzTGi5)MZO!X^IOlBBZ&tLQsW{HsD=m;H z3nu9*t_mKe60XZfBo%69txNRJ2}fy@eGx(ICYAj&0|5pes~~JJZ`NWs zXfGc@WgS{3{qo)L@4;|pkr*B+; zpl__~81Q_Y6M6lKltEtB=6&Dgvlh`JML)7q{0om}8-1=LZw+^>wHM`Yn1hmhi{mqI zkUlEWXx5Q@E#*^fAp8FLsDw;n$^-^Mpk4?7VCCLBS0ioxefJQph}Y8WDxHYKC%DaQ z;S}S{SxnxrX!9e)2O$3D(a}s)^K?Fsnr)dgYAEgs*N+MRAcFhsAtt+nKqn3tX!>A| z1ov$oM*^>J27j`N+!YN9YUgUVDi7_^D2K1$;313Dr4NYEy>ZC!n*?sr4bU`n7X_G# zK{&~O*Kj((kKI=w2WB+EPWcc2#H<)eJ^{NJXi}UR;}%KD1hd!U zrzlkqN2*Y`%i5Uh*xC->5p`nn-txzl!Q3n>dHca*Bh5TYHps&g{ozy;zTt|o>Edl1 zGZb36Bo{vk=IC2nMpmFtP|UePYO|={)YE6oFFjRlt+Ky3`lS|;{=(+14mjMnLPXRb z=%$uxJ8-$2t1#y^7}V6~%B{VevlrJm-VD{LLA%dpIAwU1e_iR|;d?CfdWxB= zbsW0u$iNG4;z#+L&BAYpyDkSmLkQhz!>PXr4Wh^EF6jZqi?k>6M9#OAM$mg?TH!|z zmtluqC?B?WV@!9!nCU3Log)4<+f4e6Xv<*!cX^+(t@wu;i^$T0m~&{ch3VWE>q%i@ z1|2ucYMEm`;RBBJz*}ENv~w%L)W*&Tt-A_Vmb=pl`O3pB)+I4u19?_`g6$m=UUoOV zJ4iCLa6=0jw*LJ2E|pO;mt)v>qz=b(0&q-;>~<%wlal}}*J=NwXP!y~Ka7dufwZCc zol_l@ld4hwn4up9~Ohll2w^?KZn@|4IqM>?o2sYWdzV=I#HaV4u@Z%+cb-$ls zJ)&m>qI_b9TkysleiNE|}Mrvu3O^xpPCH&hbs5@O@OD1J2IZc;z z;MeFgh5WE#L^LQRpJxS@BKg;*Nw~$Ij%|EYr!}tbnB~J73wyNN;N&?QgghL{OL2?S zhrvSmF>G}ayjIFgH+X8geB)a`}fF^>uc>{K*ChO1tmvbO|6}R z=ezexO#6VrJKM+E49hPQJ7>FqzH(^hYZ`_~!CVW9*%J`$*eCXwJFz5zxb3$#)ykbT z8x$eyO*@5UW=Xu=kbUHN!4sfUzG_eqvaXeQ%fZknc{kp6`(02^9}=Vt8$F-7VM|5< z7(1@))*4XvhUK9jV?b^@>u>ToJDB%?ucy?n2yfYnknr z@#~Cu(b|LlVuc*tWFK}5W