Compare commits

...

157 Commits

Author SHA1 Message Date
teknomunk 769ab6e3dc Add back shift+punch to immediately drop minecart 2024-10-01 16:37:23 +02:00
teknomunk 533005b0da Fix another crash, fix rail tee on texture 2024-10-01 16:37:23 +02:00
teknomunk d2e1f8b1e2 Calculate acceleration of trains based on average of acceleration for all carts in the train, make velocity not change on slopes and 45 degree track 2024-10-01 16:37:23 +02:00
teknomunk 71e871db3f Prevent trains from slowing on 45 degree track 2024-10-01 16:37:23 +02:00
teknomunk 401eae46cb Add guard that prevents crash when itemstack is nil 2024-10-01 16:37:23 +02:00
teknomunk 3eebff4bc9 Silence debug prints and logging 2024-10-01 16:37:23 +02:00
teknomunk 11e5f36697 Prevent removal of old minecarts 2024-10-01 16:37:23 +02:00
teknomunk 5e4e5d3200 Add legacy node conversion to vl_legacy and update rails.lua to use it 2024-10-01 16:37:23 +02:00
teknomunk 6efcf089d3 Make activated tnt minecarts glow in the dark, fix crash with lit tnt minecarts 2024-10-01 16:37:23 +02:00
teknomunk f235214be9 Fix a couple of crashes (TNT minecart trying to update orientation after exploding, trying to punch/push a minecart not on track) 2024-10-01 16:37:23 +02:00
teknomunk 32a5a01147 Expand mcl_util.hopper_pull() to mcl_util.hopper_pull_to_inventory() 2024-10-01 16:37:23 +02:00
teknomunk 9ebf39d846 Add profiling code to force_get_node() 2024-10-01 16:37:23 +02:00
teknomunk 34c5b90725 Address additional review comments 2024-10-01 16:37:23 +02:00
teknomunk d4c2ecc16f Update API documentation to always use , add compatibility shim to mcl_minecarts.is_rail() and mcl_minecarts.ge_rail_direction() 2024-10-01 16:37:23 +02:00
teknomunk 3ac29cc455 Rewrite mcl_util.hopper_pull in terms of mcl_util.hopper_pull_to_inventory 2024-10-01 16:37:23 +02:00
teknomunk 928748c93c Correct documentation per review 2024-10-01 16:37:23 +02:00
teknomunk 43bc54db1b Switch over to using vl_legacy for item conversion in player inventories 2024-10-01 16:37:23 +02:00
teknomunk cba2b6bcee Fix typo 2024-10-01 16:37:23 +02:00
teknomunk 9f9e839b24 Register rail conversions 2024-10-01 16:37:23 +02:00
teknomunk d4077b3e06 Implement vl_legacy deprecated function and item conversion APIs 2024-10-01 16:37:23 +02:00
teknomunk afa0bee22d Fix crashes 2024-10-01 16:37:23 +02:00
teknomunk b4cd290447 Make old rails have a drawtype, make update lbm always run 2024-10-01 16:37:23 +02:00
teknomunk 83e63f19e8 Move the various rails to their own files, code cleanup 2024-10-01 16:37:23 +02:00
teknomunk 44669cd4cb Remove undefined global for optional environmental physics 2024-10-01 16:37:23 +02:00
teknomunk d58acf828c Remove Emerge-0 warning that occurs when placing mineshafts 2024-10-01 16:37:23 +02:00
teknomunk 27fb5be403 Restore 45 degree cart movement, remove warning about unknown global 2024-10-01 16:37:23 +02:00
teknomunk e3fca3ce59 Complete rework of curve/tee rail direction functions 2024-10-01 16:37:23 +02:00
teknomunk e7cc1ac507 Rework rail_dir_curve to significantly reduce code size 2024-10-01 16:37:23 +02:00
teknomunk f241755c3d Convert curved rails direction code to use fourdir 2024-10-01 16:37:23 +02:00
teknomunk de01c37be5 Change verticle offset for testing reattaching to rail to 0.55, which is a bit more than the stair step height 2024-10-01 16:37:23 +02:00
teknomunk f03fbc8647 Fix cart detaching without unregistering from everything 2024-10-01 16:37:23 +02:00
teknomunk c0f34e0b2c Fix typo, set use_texture_alpha = clip for all rail 2024-10-01 16:37:23 +02:00
teknomunk c1cfe238dc Fix several undefined global warnings, fix cart movement when over maximum speed, fix cart reattachment to sloped track 2024-10-01 16:37:23 +02:00
teknomunk 965d3dbe4e Revert changed made to debug minecart-updates integration into tsm_railcorridors 2024-10-01 16:37:23 +02:00
teknomunk 6c84ca0bf3 Make punch move minecarts a little, comment out more debug prints 2024-10-01 16:37:23 +02:00
teknomunk 67e8be68e6 Fix visual artifacts on the sides of rails 2024-10-01 16:37:23 +02:00
teknomunk 328eea0899 Stop carts from reversing when they stop, make stopped carts try to start moving in the direction the player is facing 2024-10-01 16:37:23 +02:00
teknomunk df8c827df6 Fix crash after entering a minecart not on rails 2024-10-01 16:37:23 +02:00
teknomunk 1314d6e15c Fix placed rail conversion, start automatic inventory rail conversion 2024-10-01 16:37:23 +02:00
teknomunk 847631202e Fix players repelling carts with new player metadata system 2024-10-01 16:37:23 +02:00
teknomunk 8095c2a148 Cleanup debug prints 2024-10-01 16:37:23 +02:00
teknomunk 8e4953cf3d Add documentation for newly exposed attach_driver 2024-10-01 16:37:23 +02:00
teknomunk f8d3ffd86b Add persistent player-specific metadata into mcl_playerinfo, simple cart reattachment (only exists if the luaentity for the cart exists when the player logs in) 2024-10-01 16:37:23 +02:00
teknomunk f8b47e6728 More fixes for minecart-hopper movement 2024-10-01 16:37:23 +02:00
teknomunk 070ddac8c4 Get rail placement creating corners that lead into a downward sloped rail 2024-10-01 16:37:23 +02:00
teknomunk 8093f5e425 Create mcl_util.metadata_timer, fix crashes, add checks to prevent hoppers from pulling from carts that are not in the square above it 2024-10-01 16:37:23 +02:00
teknomunk 122cb53e44 Fix hopper-minecart interaction, convert ipairs(table) to use for i=1,#table instead 2024-10-01 16:37:23 +02:00
teknomunk ac8cdd5fe1 Update mineshafts for new rail and minecarts, add loot to generated chest and hopper minecarts (and remove notes about a hack) 2024-10-01 16:37:23 +02:00
teknomunk 5349d2cd43 Give carts a small vertical lift when pushed to allow them to get back on rails 2024-10-01 16:37:23 +02:00
teknomunk be2d5d8e7d Stop rail from being placed directly above rail (floating in air) 2024-10-01 16:37:23 +02:00
teknomunk d676019c08 Fix sloped power,activator and detector rails, remove debug print 2024-10-01 16:37:23 +02:00
teknomunk eba2018f15 Modify mcl_entity_invs to add support for save/load items hooks in entities, add save/load hooks to minecarts to store item list in the minecart data and not in the entity data so that respawn doesn't destroy items 2024-10-01 16:37:23 +02:00
teknomunk 7711721924 Add documentation on the rail 2024-10-01 16:37:23 +02:00
teknomunk becad064cb Add documentation on file structure and overviewes of each file 2024-10-01 16:37:23 +02:00
teknomunk 4b0f6e32fc Fix crashes, fix link in documentation 2024-10-01 16:37:23 +02:00
teknomunk efd2bf913d More documentation, add myself to copyright list in README.txt 2024-10-01 16:37:23 +02:00
teknomunk fa295ad3ae More minor changes to API.md, start overall implementation documentation 2024-10-01 16:37:23 +02:00
teknomunk d97637914a Fix table of contents 2024-10-01 16:37:23 +02:00
teknomunk 92d462cf09 Finish writing API documentation, remove drop_railcarts (replaced by try_detach_minecart), rename constants to ALL CAPS for consistency, change mcl_minecarts. to mod. for API function definitions 2024-10-01 16:37:23 +02:00
teknomunk fd4016f854 Nearly finish API documentation, create mcl_minecarts.add_blocks_to_map() 2024-10-01 16:37:23 +02:00
teknomunk 2fb598c02c Continue writing API documentation, update call signatures for a couple of API functions 2024-10-01 16:37:23 +02:00
teknomunk 6ee0bc99c4 Change document formatting, finally move cactus cart dropping to node definition for mcl_core:cactus 2024-10-01 16:37:23 +02:00
teknomunk 51c5e340fd Correct crashes/item duplication with dropping carts, start API documentation 2024-10-01 16:37:23 +02:00
teknomunk 798adaba4d Fix cart controls, cart pushing 2024-10-01 16:37:23 +02:00
teknomunk 8b3e0bebca Fix typo in rail replacement mapping, fix several crashes 2024-10-01 16:37:23 +02:00
teknomunk 70ca9566c3 Implement movement thru tee rails 2024-10-01 16:37:23 +02:00
teknomunk 426de847e4 Tune respawn distance limit 2024-10-01 16:37:23 +02:00
teknomunk 0f570537ed Fix crashes 2024-10-01 16:37:23 +02:00
teknomunk 90cfbd3628 Remove memory leak for cart data, check distance to players before respawning distant carts to prevent adding entities that are immediately inactivated 2024-10-01 16:37:23 +02:00
teknomunk 8e7ece79e8 Implement offline/out of range minecart movement and fix minecart respawning, remove railtype tracking 2024-10-01 16:37:23 +02:00
teknomunk 0b348a97ba Remove do_movement dependency on the existence of a cart luaentity 2024-10-01 16:37:23 +02:00
teknomunk c1b09626a5 Fix undefined global warning, move player off to the side of a cart when dismounting so trains don't get pushed apart when getting out 2024-10-01 16:37:23 +02:00
teknomunk 1c07d321f7 Make trains containing a player in a minecart function, minor cleanup in mcl_playerinfo 2024-10-01 16:37:23 +02:00
teknomunk a27f4422c2 Fix crashes in train logic, allow breaking apart trains 2024-10-01 16:37:23 +02:00
teknomunk 64d7637f13 Implement train reversing 2024-10-01 16:37:23 +02:00
teknomunk bfc7f51e10 Repair vectors in cart data, mostly fix train movement bugs (still possible to have a furnace minecart flip, without the train also flipping) 2024-10-01 16:37:23 +02:00
teknomunk 85c59d037c Add cart entity respawn/destroy to match cart data (partially working) 2024-10-01 16:37:23 +02:00
teknomunk 49738b791c Give furnace minecart minimum velocity when lit, add train separation code, update logging code, add sequence number to entity staticdata to allow respawn/despawn when carts move when the entity is unloaded 2024-10-01 16:37:23 +02:00
teknomunk 4b6c204102 Fix rail detach crash, make tnt minecarts explode if they hit something hard (off rails) 2024-10-01 16:37:23 +02:00
teknomunk 464b5868a3 Make sure carts get detatch if the rail under them is removed 2024-10-01 16:37:23 +02:00
teknomunk 9ab509d3fc Fixish reorganizing, initial train implementation 2024-10-01 16:37:23 +02:00
teknomunk 3ba21bb35d Major reorganization, start setup for trains 2024-10-01 16:37:23 +02:00
teknomunk 5b877d2fec Make sure carts that collide move in the same direction the colliding cart was 2024-10-01 16:37:23 +02:00
teknomunk eae47e38ff Add utilities to convert between an ObjectRef, it's active object id and a 128bit uuid, move minecart data from entity staticdata to mod storage to eventually allow updating carts when out of range of players and also track what carts are alive, implement on-rail cart collisions 2024-10-01 16:37:23 +02:00
teknomunk e31a8db98a Harden against unknown nodes 2024-10-01 16:37:23 +02:00
teknomunk 8d461ffb50 Allow players to push minecarts that are not on track 2024-10-01 16:37:23 +02:00
teknomunk 5ed49d452f Fix rails in creative inventory, make minecart with tnt not crash server when exploding, make minecart with tnt slightly more powerful than regular tnt 2024-10-01 16:37:23 +02:00
teknomunk 4f11ba4bc8 Fix rail movement regressions 2024-10-01 16:37:23 +02:00
teknomunk 93e33246ea Move cart code to its own file, more code cleanup, add aliases for old track items 2024-10-01 16:37:23 +02:00
teknomunk 3ab154d29b Cleanup code, restore uphill/downhill cart movement, completely remove old rail 2024-10-01 16:37:23 +02:00
teknomunk 6dfc4c2afe Get rail reattachment (especially after jumps) working correctly 2024-10-01 16:37:23 +02:00
teknomunk 60d73b51d6 Make legacy rail update apply to all old rail types, add basic detached railcart physics with a stub to use mcl_physics when it gets merged 2024-10-01 16:37:23 +02:00
teknomunk 01ab573a9c Silence unmaskable print statements 2024-10-01 16:37:23 +02:00
teknomunk 097124f8c2 Add immortal item entity support, add legacy rail conversion that uses immortal item drops for corners/tees/crosses that are no longer possible 2024-10-01 16:37:23 +02:00
teknomunk ef44f15310 Fix more rail connection bugs 2024-10-01 16:37:23 +02:00
teknomunk eadb17eb79 Get sloped connections working correctly 2024-10-01 16:37:23 +02:00
teknomunk e4c0e51181 Re-enable rule for powering rail from underneath, have stairs block minecart movement, fix crash when lightning strikes a minecart 2024-10-01 16:37:23 +02:00
teknomunk 359cc03017 Fix mcl_util.table_merge where a standard value overwrites a table, fix base definition usage, implement behavior difference when there is a solid block after a straight piece of track (this will eventually allow minecarts to fly off the end of the track) 2024-10-01 16:37:23 +02:00
teknomunk 4c70bbcb48 Update all rail types to new version 2024-10-01 16:37:23 +02:00
teknomunk fa295082c5 Reorganize 2024-10-01 16:37:23 +02:00
teknomunk 68c78e6eba Finish reverting 08b41a3b39 2024-10-01 16:37:23 +02:00
teknomunk e074fa3f7e Enable new track with get_next_dir handlers 2024-10-01 16:37:23 +02:00
teknomunk bf3f8bf9ed Change connection rules again to allow building parallel track, tees and crosses), start implementing rail rules callbacks 2024-10-01 16:37:23 +02:00
teknomunk 5e5d06fb89 Add sloped rail 2024-10-01 16:37:23 +02:00
teknomunk b2f5991937 Fix rail visuals, add switch operation 2024-10-01 16:37:23 +02:00
teknomunk 4ef9b1d1fd Implement initial rail connection logic (no vertical track yet), experiment with texture modifiers and gravel underlay for display (not working) 2024-10-01 16:37:23 +02:00
teknomunk 3cfe9e0652 Start implementing new rail nodes 2024-10-01 16:37:23 +02:00
teknomunk 439c396716 Implement minecart with command block 2024-10-01 16:37:23 +02:00
teknomunk a334c874da Create mesecons command API and modify commandblock to use it 2024-10-01 16:37:23 +02:00
teknomunk 9cdcc319a4 Disable punch to move minecarts, implement punch to drop minecart, enable basic cart keyboard controls (accelerate and brake) 2024-10-01 16:37:23 +02:00
teknomunk 59305d2ff1 Remove cart oscillation when pushed 2024-10-01 16:37:23 +02:00
teknomunk 36a8bae986 Limit top speed of furnace minecarts to 4 blocks/second, limit total fuel time to 27 minutes 2024-10-01 16:37:23 +02:00
teknomunk a1e4f15bd7 Fix bug with furnace minecart at max velocity (stopped until fuel ran out), move _fueltime into staticdata 2024-10-01 16:37:23 +02:00
teknomunk ab8f641798 Fix call signature of mcl_util.hopper_pull_to_inventory, move cart-specific behaviors to _mcl_minecarts_on_step handlers, fix typo, change distance used in rail reattach code, move cart_id generation 2024-10-01 16:37:23 +02:00
teknomunk 32fd0af531 Add groups to minecart entities (for containers), fix cart node watch handling, relocate hopper_push_to_mc in mcl_hopper/init.lua, implement hopper-to-minecart push using enter/leave hooks for both straight and bent hoppers 2024-10-01 16:37:23 +02:00
teknomunk 261ba5d890 Add API function to remove node watch 2024-10-01 16:37:23 +02:00
teknomunk 4541c37e5f Refactor enter/leave hook processing, add node watches for implementing hopper-to-minecart functionality (should properly handle heavy server lag without missing any time), temporarily disable hopper push/pull to minecart in mcl_hoppers, prepare to move minecart-specific on_step behavior out of main on_step function and to a minecart-specific handler 2024-10-01 16:37:23 +02:00
teknomunk 867217ea49 Start adding hooks for implpementing minecart with command block 2024-10-01 16:37:23 +02:00
teknomunk c12e5c2f3d Make minecarts solid and add players pushing 2024-10-01 16:37:23 +02:00
teknomunk 544b5dc600 Fix forwards/backwars tilt in all directions 2024-10-01 16:37:23 +02:00
teknomunk 190d0e04b9 Prevent players from entering minecarts when sneaking, prevents players from causing MineClone2/MineClone2#3188 2024-10-01 16:37:23 +02:00
teknomunk eb2dc15679 Increase default track friction, disable right-click to exit minecarts 2024-10-01 16:37:23 +02:00
teknomunk d692e130cc Initial tuning of acceleration/gravity, fix crash when entering an activator rail, detach mobs from cart on active activator rail, remove commented out code no longer needed 2024-10-01 16:37:23 +02:00
teknomunk 8c82c48165 Move code that handles below-rail hoppers to handle_cart_enter, implement timestep-independent cart physics (will need tuning punch, power rail and gravityaccelerations to make game fun) 2024-10-01 16:37:23 +02:00
teknomunk 32f7a5b543 Mostly fix carts stopping between powered rails (there is still some strangeness with acceleration physics) 2024-10-01 16:37:23 +02:00
teknomunk 58855d4773 Fix diagonal movement 2024-10-01 16:37:23 +02:00
teknomunk 12a107d5a5 Add diagonal track movement on zig-zag track, rewrite mcl_minecarts:get_rail_direction 2024-10-01 16:37:23 +02:00
teknomunk 230adca7e5 Make TNT minecarts available in creative menu 2024-10-01 16:37:23 +02:00
teknomunk d155818ccb Implement custom item dropper handlers, implement droppers placing minecarts 2024-10-01 16:37:23 +02:00
teknomunk 2e41ac1587 Hopper minecarts pull from containers above rail 2024-10-01 16:37:23 +02:00
teknomunk 7333cb4d21 Rework in preparation to add code to pull from containers into the hopper minecart 2024-10-01 16:37:23 +02:00
teknomunk 0cf9ca2022 Move fiction constant to top of file, suppress cart flips when direction reverses due to gravity or end of track 2024-10-01 16:37:23 +02:00
teknomunk 7795d33344 Add code to reattach carts to rail when data corruption occurs, fix bug in last commit that caused carts to bury into the ground 2024-10-01 16:37:23 +02:00
teknomunk 0f5f640e82 Remove dip into the ground that occured when gravity caused the cart to reverse directions 2024-10-01 16:37:23 +02:00
teknomunk cc0af5d326 Implement gravity, move orientation update to own function, fix cart stopping in process_acceleration 2024-10-01 16:37:23 +02:00
teknomunk e2126cd061 Change connected railcar behavior to fix unreliable end of track stopping, set maximum acceleration of powered rails to 8 blocks per second (per https://minecraft.fandom.com/wiki/Powered_Rail), stop powered rails from powering the block underneath it (allows below rail hopper to work while the rail is powered like in https://www.youtube.com/watch?v=szjO0-duTAk), modify mcl_hoppers to allow triggering a hopper pull once the minecart is stopped on top of the hopper and wait before allowing the cart to move to allow redstone circuits time to process 2024-10-01 16:37:23 +02:00
teknomunk 1a766f3067 Make minecart always stop at correct location at end of track, fix crash when placing chest minecart after changing how staticdata is handled 2024-10-01 16:37:23 +02:00
teknomunk 7e4f2607d8 Modify do_movement_step to move to always move to the edge of the current rail segment before updating the direction to prevent oscillations at corners, fix end of track stop location with new movement behavior, disable experimental controls, swap code to detach the driver on float with a call to detach_driver() 2024-10-01 16:37:23 +02:00
teknomunk 0cb0fb16cc Add DEBUG flag, stop small do_movement_step's from occuring (this improves but doesn't eliminate the bug I with the a6be179ed commit), add recovery when staticdata field gets lost 2024-10-01 16:37:23 +02:00
teknomunk ffe06b97a6 Fix initial_properties for minecarts 2024-10-01 16:37:23 +02:00
teknomunk f99410d96d Change left,right and back vectors to matrix math results with no branching 2024-10-01 16:37:23 +02:00
teknomunk 1924caa1f1 Remove now unused properties from minecart definition, convert more vectors to use vector.new syntax 2024-10-01 16:37:23 +02:00
teknomunk 419a88ee72 Complete rewrite of minecart movement that resolves MineClone2/MineClone2#2446 and MineClone2/MineClone2#247 (comment) but has a bug where carts will synchronize movements that I am still investigating 2024-10-01 16:37:23 +02:00
teknomunk 9ab2e141bb Change staticdata serialization (with migration from old data), disable debugging code used to investigate MineClone2/MineClone2#2446 2024-10-01 16:37:23 +02:00
teknomunk b03ad36032 Change to vector.new from {x=...}, relocate movement code to own function for future changes 2024-10-01 16:37:23 +02:00
kno10 253a06fa08 Fix mob egg double-spawns (#4657)
If you spawn a mob clicking on a wall, two mobs will be spawned.

To reproduce: face a stack of stones, with a spawn egg click on the side of a stone. It does not happen when you click the top of a node, because spawning below fails and only the second one succeeds.

Reviewed-on: VoxeLibre/VoxeLibre#4657
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: kno10 <kno10@noreply.git.minetest.land>
Co-committed-by: kno10 <kno10@noreply.git.minetest.land>
2024-09-30 19:21:40 +02:00
kno10 dcfd31d17a Avoid random jumps when standing due to gravity (fewer villagers on the roofs) (#4547)
Reviewed-on: VoxeLibre/VoxeLibre#4547
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: kno10 <erich.schubert@gmail.com>
Co-committed-by: kno10 <erich.schubert@gmail.com>
2024-09-30 11:22:31 +02:00
teknomunk c34aecfcab Don't make 'ignore' nodes break bamboo or kelp (#4551)
This modifies the behavior of kelp and bamboo so that neither breaks when an unloaded node is encountered.

Reviewed-on: VoxeLibre/VoxeLibre#4551
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: teknomunk <teknomunk@protonmail.com>
Co-committed-by: teknomunk <teknomunk@protonmail.com>
2024-09-29 13:57:52 +02:00
Mikita Wiśniewski 9cb4f51468 Fix invalid global call in mcl_chests LBM (#4667)
Reviewed-on: VoxeLibre/VoxeLibre#4667
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: Mikita Wiśniewski <rudzik8@protonmail.com>
Co-committed-by: Mikita Wiśniewski <rudzik8@protonmail.com>
2024-09-29 13:34:20 +02:00
kno10 d264ba70d8 Fix growth logic, clean up mcl_farming/shared_functions (#4640)
Reviewed-on: VoxeLibre/VoxeLibre#4640
Reviewed-by: teknomunk <teknomunk@protonmail.com>
Co-authored-by: kno10 <erich.schubert@gmail.com>
Co-committed-by: kno10 <erich.schubert@gmail.com>
2024-09-20 14:00:49 +02:00
Mikita Wiśniewski 513413afc7 Use `remove_node` instead of `dig_node` in mcl_core ABMs (fixes #4628) (#4629)
The mycelium ABM has been left untouched because of the potential destructiveness. If we ever find that to be an issue, it can be fixed as part of a bigger PR.

Reviewed-on: VoxeLibre/VoxeLibre#4629
Reviewed-by: teknomunk <teknomunk@protonmail.com>
Co-authored-by: Mikita Wiśniewski <rudzik8@protonmail.com>
Co-committed-by: Mikita Wiśniewski <rudzik8@protonmail.com>
2024-09-19 18:54:39 +02:00
kno10 011be754ca Allow deepslate copper to be mined with stone pickaxe (#4635)
Reviewed-on: VoxeLibre/VoxeLibre#4635
Reviewed-by: Mikita Wiśniewski <rudzik8@protonmail.com>
Co-authored-by: kno10 <kno10@noreply.git.minetest.land>
Co-committed-by: kno10 <kno10@noreply.git.minetest.land>
2024-09-18 10:11:55 +02:00
teknomunk eea96867c4 Don't add rain skycolor layer if the current layer is already the rain skycolor (#4648)
Fixes #4647 Rain makes the sky black until restart. This also fixes a memory leak caused by rain adding a color layer every time step.

Reviewed-on: VoxeLibre/VoxeLibre#4648
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
Co-authored-by: teknomunk <teknomunk@protonmail.com>
Co-committed-by: teknomunk <teknomunk@protonmail.com>
2024-09-18 10:10:53 +02:00
the-real-herowl cd2ee49591 Merge pull request 'Make Soul Speed work on Soul Soil' (#4604) from upstream/soul_soil_speed into master
Reviewed-on: VoxeLibre/VoxeLibre#4604
Reviewed-by: Mikita Wiśniewski <rudzik8@protonmail.com>
2024-09-18 10:06:03 +02:00
seventeenthShulker de3b34f5ea Update English translation keys with soul soil 2024-09-18 10:06:03 +02:00
seventeenthShulker e2bcd129c1 Use soul_block group for soul speed bonus 2024-09-18 10:06:03 +02:00
seventeenthShulker 79e8452f62 Soul speed works on soul soil too
(needs localization)
2024-09-18 10:06:03 +02:00
58 changed files with 4669 additions and 2064 deletions

View File

@ -73,6 +73,15 @@ function mcl_util.mcl_log(message, module, bypass_default_logger)
minetest.log(selected_module .. " " .. message)
end
end
function mcl_util.make_mcl_logger(label, option)
-- Return dummy function if debug option isn't set
if not minetest.settings:get_bool(option,false) then return function() end, false end
local label_text = "["..tostring(label).."]"
return function(message)
mcl_util.mcl_log(message, label_text, true)
end, true
end
local player_timers = {}
@ -359,13 +368,7 @@ function mcl_util.hopper_push(pos, dst_pos)
return ok
end
-- Try pulling from source inventory to hopper inventory
---@param pos Vector
---@param src_pos Vector
function mcl_util.hopper_pull(pos, src_pos)
local hop_inv = minetest.get_meta(pos):get_inventory()
local hop_list = 'main'
function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
-- Get node pos' for item transfer
local src = minetest.get_node(src_pos)
if not minetest.registered_nodes[src.name] then return end
@ -381,7 +384,7 @@ function mcl_util.hopper_pull(pos, src_pos)
else
local src_meta = minetest.get_meta(src_pos)
src_inv = src_meta:get_inventory()
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list, nil, 1)
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list)
end
if stack_id ~= nil then
@ -391,6 +394,12 @@ function mcl_util.hopper_pull(pos, src_pos)
end
end
end
-- Try pulling from source inventory to hopper inventory
---@param pos Vector
---@param src_pos Vector
function mcl_util.hopper_pull(pos, src_pos)
return mcl_util.hopper_pull_to_inventory(minetest.get_meta(pos):get_inventory(), "main", src_pos, pos)
end
local function drop_item_stack(pos, stack)
if not stack or stack:is_empty() then return end
@ -1131,3 +1140,89 @@ if not vector.in_area then
(pos.z >= min.z) and (pos.z <= max.z)
end
end
local function table_merge(base, overlay)
for k,v in pairs(overlay) do
if type(base[k]) == "table" and type(v) == "table" then
table_merge(base[k], v)
else
base[k] = v
end
end
return base
end
mcl_util.table_merge = table_merge
function mcl_util.table_keys(t)
local keys = {}
for k,_ in pairs(t) do
keys[#keys + 1] = k
end
return keys
end
local uuid_to_aoid_cache = {}
local function scan_active_objects()
-- Update active object ids for all active objects
for active_object_id,o in pairs(minetest.luaentities) do
o._active_object_id = active_object_id
if o._uuid then
uuid_to_aoid_cache[o._uuid] = active_object_id
end
end
end
function mcl_util.get_active_object_id(obj)
local le = obj:get_luaentity()
-- If the active object id in the lua entity is correct, return that
if le._active_object_id and minetest.luaentities[le._active_object_id] == le then
return le._active_object_id
end
scan_active_objects()
return le._active_object_id
end
function mcl_util.get_active_object_id_from_uuid(uuid)
return uuid_to_aoid_cache[uuid] or scan_active_objects() or uuid_to_aoid_cache[uuid]
end
function mcl_util.get_luaentity_from_uuid(uuid)
return minetest.luaentities[ mcl_util.get_active_object_id_from_uuid(uuid) ]
end
function mcl_util.gen_uuid()
-- Generate a random 128-bit ID that can be assumed to be unique
-- To have a 1% chance of a collision, there would have to be 1.6x10^76 IDs generated
-- https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
local u = {}
for i = 1,16 do
u[#u + 1] = string.format("%02X",math.random(1,255))
end
return table.concat(u)
end
function mcl_util.assign_uuid(obj)
assert(obj)
local le = obj:get_luaentity()
if not le._uuid then
le._uuid = mcl_util.gen_uuid()
end
-- Update the cache with this new id
local aoid = mcl_util.get_active_object_id(obj)
uuid_to_aoid_cache[le._uuid] = aoid
return le._uuid
end
function mcl_util.metadata_last_act(meta, name, delay)
local last_act = meta:get_float(name)
local now = minetest.get_us_time() * 1e-6
if last_act > now + 0.5 then
-- Last action was in the future, clock went backwards, so reset
elseif last_act >= now - delay then
return false
end
meta:set_float(name, now)
return true
end

View File

@ -0,0 +1,35 @@
# Legacy Code Support Functions
## `vl_legacy.deprecated(description, replacement)`
Creates a wrapper that logs calls to deprecated function.
Arguments:
* `description`: The text logged when the deprecated function is called.
* `replacement`: The function that should be called instead. This is invoked passing
along the parameters exactly as provided.
## `vl_legacy.register_item_conversion`
Allows automatic conversion of items.
Arguments:
* `old`: Itemstring to be converted
* `new`: New item string
## `vl_legacy.convert_node(pos, node)`
Converts legacy nodes to newer versions.
Arguments:
* `pos`: Position of the node to attempt conversion
* `node`: Node definition to convert. The node will be loaded from map data if `nil`.
The node definition for the old node must contain the field `_vl_legacy_convert` with
a value that is either a `function(pos, node)` or `string` for this call to have any
affect. If a function is provided, the function is called with `pos` and `node` as
arguments. If a string is provided, a node name conversion will occur.
This mod provides an LBM and ABM that will automatically call this function for nodes
with `group:legacy` set.

View File

@ -0,0 +1,75 @@
local mod = {}
vl_legacy = mod
function mod.deprecated(description, func)
return function(...)
minetest.log("warning",description .. debug.traceback())
return func(...)
end
end
local item_conversions = {}
mod.registered_item_conversions = item_conversions
function mod.register_item_conversion(old, new, func)
item_conversions[old] = {new, func}
end
function mod.convert_inventory_lists(lists)
for _,list in pairs(lists) do
for i = 1,#list do
local itemstack = list[i]
local conversion = itemstack and item_conversions[itemstack:get_name()]
if conversion then
local new_name,func = conversion[1],conversion[2]
if func then
func(itemstack)
else
itemstack:set_name(new_name)
end
end
end
end
end
function mod.convert_inventory(inv)
local lists = inv:get_lists()
mod.convert_inventory_lists(lists)
inv:set_lists(lists)
end
function mod.convert_node(pos, node)
local node = node or minetest.get_node(pos)
local node_def = minetest.registered_nodes[node.name]
local convert = node_def._vl_legacy_convert_node
if type(convert) == "function" then
convert(pos, node)
elseif type(convert) == "string" then
node.name = convert
minetest.swap_node(pos, node)
end
end
minetest.register_on_joinplayer(function(player)
mod.convert_inventory(player:get_inventory())
end)
minetest.register_lbm({
name = "vl_legacy:convert_container_inventories",
nodenames = "group:container",
run_at_every_load = true,
action = function(pos, node)
local meta = minetest.get_meta(pos)
mod.convert_inventory(meta:get_inventory())
end
})
minetest.register_lbm({
name = "vl_legacy:convert_nodes",
nodenames = "group:legacy",
run_at_every_load = true,
action = mod.convert_node,
})
minetest.register_abm({
label = "Convert Legacy Nodes",
nodenames = "group:legacy",
interval = 5,
chance = 1,
action = mod.convert_node,
})

View File

@ -0,0 +1,3 @@
name = vl_legacy
author = teknomunk
description = API to ease conversion of items, deprecated function logging and similar functions

View File

@ -27,28 +27,33 @@ local inv_callbacks = {
}
function mcl_entity_invs.load_inv(ent,size)
mcl_log("load_inv")
if not ent._inv_id then return end
mcl_log("load_inv 2")
local inv = minetest.get_inventory({type="detached", name=ent._inv_id})
if not inv then
mcl_log("load_inv 3")
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
inv:set_size("main", size)
if ent._items then
if ent._mcl_entity_invs_load_items then
local lists = ent:_mcl_entity_invs_load_items()
vl_legacy.convert_inventory_lists(lists)
inv:set_list("main", lists)
elseif ent._items then
vl_legacy.convert_inventory_lists(ent._items)
inv:set_list("main",ent._items)
end
else
mcl_log("load_inv 4")
end
return inv
end
function mcl_entity_invs.save_inv(ent)
if ent._inv then
ent._items = {}
local items = {}
for i,it in ipairs(ent._inv:get_list("main")) do
ent._items[i] = it:to_string()
items[i] = it:to_string()
end
if ent._mcl_entity_invs_save_items then
ent:_mcl_entity_invs_save_items(items)
else
ent._items = items
end
minetest.remove_detached_inventory(ent._inv_id)
ent._inv = nil

View File

@ -1,3 +1,3 @@
name = mcl_entity_invs
author = cora
depends = mcl_formspec
depends = mcl_formspec, vl_legacy

View File

@ -840,6 +840,7 @@ minetest.register_entity(":__builtin:item", {
_insta_collect = self._insta_collect,
_flowing = self._flowing,
_removed = self._removed,
_immortal = self._immortal,
})
-- sfan5 guessed that the biggest serializable item
-- entity would have a size of 65530 bytes. This has
@ -892,6 +893,7 @@ minetest.register_entity(":__builtin:item", {
self._insta_collect = data._insta_collect
self._flowing = data._flowing
self._removed = data._removed
self._immortal = data._immortal
end
else
self.itemstring = staticdata
@ -985,7 +987,7 @@ minetest.register_entity(":__builtin:item", {
if self._collector_timer then
self._collector_timer = self._collector_timer + dtime
end
if time_to_live > 0 and self.age > time_to_live then
if time_to_live > 0 and ( self.age > time_to_live and not self._immortal ) then
self._removed = true
self.object:remove()
return

View File

@ -0,0 +1,284 @@
# Table of Contents
1. [Useful Constants](#useful-constants)
2. [Rail](#rail)
1. [Constants](#constants)
2. [Functions](#functions)
3. [Node Definition Options](#node-definition-options)
3. [Cart Functions](#cart-functions)
4. [Cart Data Functions](#cart-data-functions)
5. [Cart-Node Interactions](#cart-node-interactions)
6. [Train Functions](#train-functions)
## Useful Constants
- `mcl_minecarts.north`
- `mcl_minecarts.south`
- `mcl_minecarts.east`
- `mcl_minecarts.west`
Human-readable names for the cardinal directions.
- `mcl_minecarts.SPEED_MAX`
Maximum speed that minecarts will be accelerated to with powered rails, in blocks per
second. Defined as 10 blocks/second.
- `mcl_minecarts.CART_BLOCKS_SIZE`
The size of blocks to use when searching for carts to respawn. Defined as is 64 blocks.
- `mcl_minecarts.FRICTION`
Rail friction. Defined as is 0.4 blocks/second^2.
- `mcl_minecarts.MAX_TRAIN_LENGTH`
The maximum number of carts that can be in a single train. Defined as 4 carts.
- `mcl_minecarts.PASSENGER_ATTACH_POSITION`
Where to attach passengers to the minecarts.
## Rail
### Constants
`mcl_minecarts.HORIZONTAL_CURVES_RULES`
`mcl_minecarts.HORIZONTAL_STANDARD_RULES`
Rail connection rules. Each rule is a table with the following indexes:
1. `node_name_suffix` - The suffix added to a node's `_mcl_minecarts.base_name` to
get the name of the node to use for this connection.
2. `param2_value` - The value of the node's param2. Used to specify rotation.
and the following named options:
- `mask` - Directional connections mask
- `score` - priority of the rule. If more than one rule matches, the one with the
highest store is selected.
- `can_slope` - true if the result of this rule can be converted into a slope.
`mcl_minecarts.RAIL_GROUPS.STANDARD`
`mcl_minecarts.RAIL_GROUPS.CURVES`
These constants are used to specify a rail node's `group.rail` value.
### Functions
`mcl_minecarts.get_rail_connections(node_position, options)`
Calculate the rail adjacency information for rail placement. Arguments are:
- `node_position` - the location of the node to calculate adjacency for.
- `options` - A table containing any of these options:
- `legacy`- if true, don't check that a connection proceeds out in a direction
a cart can travel. Used for converting legacy rail to newer equivalents.
- `ignore_neightbor_connections` - if true, don't check that a cart could leave
the neighboring node from this direction.
`mcl_minecarts.is_rail(position, railtype)`
Determines if the node at `position` is a rail. If `railtype` is provided,
determine if the node at `position` is that type of rail.
`mcl_minecarts.register_rail(itemstring, node_definition)`
Registers a rail with a few sensible defaults and if a craft recipe was specified,
register that as well.
`mcl_minecarts.register_straight_rail(base_name, tiles, node_definition)`
Registers a rail with only straight and sloped variants.
`mcl_minecarts.register_curves_rail(base_name, tiles, node_definition)`
Registers a rail with straight, sloped, curved, tee and cross variants.
`mcl_minecarts.update_rail_connections(node_position, options)`
Converts the rail at `node_position`, if possible, another variant (curve, etc.)
and rotates the node as needed so that rails connect together. `options` is
passed thru to `mcl_minecarts.get_rail_connections()`
`mcl_minecarts.get_rail_direction(rail_position, cart_direction)`
Returns the next direction a cart traveling in the direction specified in `cart_direction`
will travel from the rail located at `rail_position`.
### Node Definition Options
`_mcl_minecarts.railtype`
This declares the variant type of the rail. This will be one of the following:
- "straight" - two connections opposite each other and no vertical change.
- "sloped" - two connections opposite each other with one of these connections
one block higher.
- "corner" - two connections at 90 degrees from each other.
- "tee" - three connections
- "cross" - four connections allowing only straight-thru movement
#### Hooks
`_mcl_minecarts.get_next_dir = function(node_position, current_direction, node)`
Called to get the next direction a cart will travel after passing thru this node.
## Cart Functions
`mcl_minecarts.attach_driver(cart, player)`
This attaches (ObjectRef) `player` to the (LuaEntity) `cart`.
`mcl_minecarts.detach_minecart(cart_data)`
This detaches a minecart from any rail it is attached to and makes it start moving
as an entity affected by gravity. It will keep moving in the same direction and
at the same speed it was moving at before it detaches.
`mcl_minecarts.get_cart_position(cart_data)`
Compute the location of a minecart from its cart data. This works even when the entity
is unloaded.
`mcl_minecarts.kill_cart(cart_data)`
Kills a cart and drops it as an item, even if the cart entity is unloaded.
`mcl_minecarts.place_minecart(itemstack, pointed_thing, placer)`
Places a minecart at the location specified by `pointed_thing`
`mcl_minecarts.register_minecart(minecart_definition)`
Registers a minecart. `minecart_definition` defines the entity. All the options supported by
normal minetest entities are supported, with a few additions:
- `craft` - Crafting recipe for this cart.
- `drop` - List of items to drop when the cart is killed. (required)
- `entity_id` - The entity id of the cart. (required)
- `itemstring` - This is the itemstring to use for this entity. (required)
`mcl_minecarts.reverse_cart_direction(cart_data)`
Force a minecart to start moving in the opposite direction of its current direction.
`mcl_minecarts.snap_direction(direction_vector)`
Returns a valid cart movement direction that has the smallest angle between it and `direction_vector`.
`mcl_minecarts.update_cart_orientation(cart)`
Updates the rotation of a cart entity to match the cart's data.
## Cart Data Functions
`mcl_minecarts.destroy_cart_data(uuid)`
Destroys the data for the cart with the identitfier in `uuid`.
`mcl_minecarts.find_carts_by_block_map(block_map)`
Returns a list of cart data for carts located in the blocks specified in `block_map`. Used
to respawn carts entering areas around players.
`mcl_minecarts.add_blocks_to_map(block_map, min_pos, max_pos)`
Add blocks that fully contain `min_pos` and `max_pos` to `block_map` for use by
`mcl_minecarts.find_cart_by_block_map`.
`mcl_minecarts.get_cart_data(uuid)`
Loads the data for the cart with the identitfier in `uuid`.
`mcl_minecarts.save_cart_data(uuid)`
Saves the data for the cart with the identifier in `uuid`.
`mcl_minecart.update_cart_data(data)`
Replaces the cart data for the cart with the identifier in `data.uuid`, then saves
the data.
## Cart-Node Interactions
As the cart moves thru the environment, it can interact with the surrounding blocks
thru a number of handlers in the block definitions. All these handlers are defined
as:
`function(node_position, cart_luaentity, cart_direction, cart_position)`
Arguments:
- `node_position` - position of the node the cart is interacting with
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
be nil for minecarts moving thru unloaded blocks
- `cart_direction` - The direction the cart is moving
- `cart_position` - The location of the cart
- `cart_data` - Information about the cart. This will always be defined.
There are several variants of this handler:
- `_mcl_minecarts_on_enter` - The cart enters this block
- `_mcl_minecarts_on_enter_below` - The cart enters above this block
- `_mcl_minecarts_on_enter_above` - The cart enters below this block
- `_mcl_minecarts_on_enter_side` - The cart enters beside this block
Mods can also define global handlers that are called for every node. These
handlers are defined as:
`function(node_position, cart_luaentity, cart_direction, node_definition, cart_data)`
Arguments:
- `node_position` - position of the node the cart is interacting with
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
be nil for minecarts moving thru unloaded blocks
- `cart_direction` - The direction the cart is moving
- `cart_position` - The location of the cart
- `cart_data` - Information about the cart. This will always be defined.
- `node_definition` - The definition of the node at `node_position`
The available hooks are:
- `_mcl_minecarts.on_enter` - The cart enters this block
- `_mcl_minecarts.on_enter_below` - The cart enters above this block
- `_mcl_minecarts.on_enter_above` - The cart enters below this block
- `_mcl_minecarts.on_enter_side` - The cart enters beside this block
Only a single function can be installed in each of these handlers. Before installing,
preserve the existing handler and call it from inside your handler if not `nil`.
## Train Functions
`mcl_minecarts.break_train_at(cart_data)`
Splits a train apart at the specified cart.
`mcl_minecarts.distance_between_cars(cart1_data, cart2_data)`
Returns the distance between two carts even if both entities are unloaded, or nil if either
cart is not on a rail.
`mcl_minecarts.is_in_same_train(cart1_data, cart2_data)`
Returns true if cart1 and cart2 are a part of the same train and false otherwise.
`mcl_minecarts.link_cart_ahead(cart_data, cart_ahead_data)`
Given two carts, link them together into a train, with the second cart ahead of the first.
`mcl_minecarts.train_cars(cart_data)`
Use to iterate over all carts in a train. Expected usage:
`for cart in mcl_minecarts.train_cars(cart) do --[[ code ]] end`
`mcl_minecarts.reverse_train(cart)`
Make all carts in a train reverse and start moving in the opposite direction.
`mcl_minecarts.train_length(cart_data)`
Compute the current length of the train containing the cart whose data is `cart_data`.
`mcl_minecarts.update_train(cart_data)`
When provided with the rear-most cart of a tain, update speeds of all carts in the train
so that it holds together and moves as a unit.

View File

@ -0,0 +1,120 @@
## Organization
- [ init.lua](./init.lua) - module entrypoint. The other files are included from here
and several constants are defined here
- [carts.lua](./carts/lua) - This file contains code related to cart entities, cart
type registration, creation, estruction and updating. The global step function
responsible for updating attached carts is in this file. The various carts are
referenced from this file but actually reside in the subdirectory [carts/](./carts/).
- [functions.lua](./functions.lua) - This file contains various minecart and rail
utility functions used by the rest of the code.
- [movement.lua](./movement.lua) - This file contains the code related to cart
movement physics.
- [rails.lua](./rails.lua) - This file contains code related to rail registation,
placement, connection rules and cart direction selection. This contains the rail
behaviors and the LBM code for updating legacy rail nodes to the new versions
that don't use the raillike draw type.
- [storage.lua](./storage.lua) - This file contains the code than manages minecart
state data to allow processing minecarts while entities are unloaded.
- [train.lua](./train.lua) - This file contains code related to multi-car trains.
## Rail Nodes
Previous versions of mcl\_minecarts used one node type for each rail type (standard,
powered, detector and activator) using the raillike draw type that minetest provides.
This version does not use the raillike draw type and instead uses a 1/16th of a block
high nodebox and uses an additional node definition for each variant. The variants
present are:
- straight
- sloped
- corner
- tee
- cross
Of the rail types provided by this module, standard has all of these variants. The
remaining types only have straight and sloped variants.
Unlike the old rail type, this version will only update connections when placed, and
will only place a variant that already has connections into the space the rail is
being placed. Here is how to create the various varients:
- Straight rail is placed when with zero or one adjacent rail nodes. If no rails
are adjacent, the rail is placed in line with the direction the player is facing.
If there is exactly one adjacent rail present, the straight rail will always rotate
to connect to it.
- Sloped rail is placed when there are two rails in a straight line, with one being
one block higher. When rail is placed adjacent to a straight rail one block lower
and the rail is facing the block the rail is being placed on, the lower rail will
convert into a slope.
- A corner rail is placed when there are exactly two adjacent rails that are not in
a line and lead into the space the rail is being placed. The corner will be rotated
to connect these two rails.
- A tee rail is placed where there are exactly three rails adjact and those existing
rails lead into the the space the new rail is being placed.
- A rail cross is placed when there is rail in all four adjacent blocks and they all
have a path into the space the new rail is being placed.
The tee variant will interact with redstone and mesecons to switch the curved section.
## On-rail Minecart Movement
Minecart movement is handled in two distinct regimes: on a rail and off. The
off-rail movement is handled with minetest's builtin entity movement handling.
The on-rail movement is handled with a custom algorithm. This section details
the latter.
The data for on-rail minecart movement is stored entirely inside mod storage
and indexed by a hex-encoded 128-bit universally-unique identifier (uuid). Minecart
entities store this uuid and a sequence identifier. The code for handling this
storage is in [storage.lua](./storage.lua). This was done so that minecarts can
still move while no players are connected or when out of range of players. Inspiration
for this was the [Advanced Trains mod](http://advtrains.de/). This is a behavior difference
when compared to minecraft, as carts there will stop movement when out of range of
players.
Processing for minecart movement is as follows:
1. In a globalstep handler in [carts.lua](./carts.lua), determine which carts are
moving.
2. Call `do_movement` in [movement.lua](./movement.lua) to update
each cart's location and handle interactions with the environment.
1. Each movement is broken up into one or more steps that are completely
contained inside a block. This prevents carts from ever jumping from
one rail to another over a gap or thru solid blocks because of server
lag. Each step is processed with `do_movement_step`
2. Each step uses physically accurate, timestep-independent physics
to move the cart. Calculating the acceleration to apply to a cart
is broken out into its own function (`calculate_acceperation`).
3. As the cart enters and leaves blocks, handlers in nearby blocks are called
to allow the cart to efficiently interact with the environment. Handled by
the functions `handle_cart_enter` and `handle_cart_leave`
4. The cart checks for nearby carts and collides elastically with these. The
calculations for these collisions are in the function `handle_cart_collision`
5. If the cart enters a new block, determine the new direction the cart will
move with `mcl_minecarts:get_rail_direction` in [functions.lua](./functions.lua).
The rail nodes provide a hook `_mcl_minecarts.get_next_direction` that
provides this information based on the previous movement direction.
3. If an entity exists for a given cart, the entity will update its position
while loaded in.
Cart movement when on a rail occurs regarless of whether an entity for that
cart exists or is loaded into memory. As a consequence of this movement, it
is possible for carts with unloaded entities to enter range of a player.
To handle this, periodic checks are performed around players and carts that
are within range but don't have a cart have a new entity spawned.
Every time a cart has a new entity spawned, it increases a sequence number in
the cart data to allow removing old entities from the minetest engine. Any cart
entity that does not have the current sequence number for a minecart gets removed
once processing for that entity resumes.

View File

@ -10,6 +10,7 @@ MIT License
Copyright (C) 2012-2016 PilzAdam
Copyright (C) 2014-2016 SmallJoker
Copyright (C) 2012-2016 Various Minetest developers and contributors
Copyright (C) 2024 teknomunk
Authors/licenses of media files:
-----------------------

View File

@ -0,0 +1,662 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local mcl_log,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
-- Imports
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
local table_merge = mcl_util.table_merge
local get_cart_data = mod.get_cart_data
local save_cart_data = mod.save_cart_data
local update_cart_data = mod.update_cart_data
local destroy_cart_data = mod.destroy_cart_data
local find_carts_by_block_map = mod.find_carts_by_block_map
local do_movement,do_detached_movement,handle_cart_enter = dofile(modpath.."/movement.lua")
assert(do_movement)
assert(do_detached_movement)
assert(handle_cart_enter)
-- Constants
local max_step_distance = 0.5
local MINECART_MAX_HP = 4
local TWO_OVER_PI = 2 / math.pi
local function detach_driver(self)
local staticdata = self._staticdata
if not self._driver then
return
end
-- Update player infomation
local driver_name = self._driver
local playerinfo = mcl_playerinfo[driver_name]
if playerinfo then
playerinfo.attached_to = nil
end
mcl_player.player_attached[driver_name] = nil
minetest.log("action", driver_name.." left a minecart")
-- Update cart informatino
self._driver = nil
self._start_pos = nil
local player_meta = mcl_playerinfo.get_mod_meta(driver_name, modname)
player_meta.attached_to = nil
-- Detatch the player object from the minecart
local player = minetest.get_player_by_name(driver_name)
if player then
local dir = staticdata.dir or vector.new(1,0,0)
local cart_pos = mod.get_cart_position(staticdata) or self.object:get_pos()
local new_pos = vector.offset(cart_pos, -dir.z, 0, dir.x)
player:set_detach()
--print("placing player at "..tostring(new_pos).." from cart at "..tostring(cart_pos)..", old_pos="..tostring(player:get_pos()).."dir="..tostring(dir))
-- There needs to be a delay here or the player's position won't update
minetest.after(0.1,function(driver_name,new_pos)
local player = minetest.get_player_by_name(driver_name)
player:moveto(new_pos, false)
end, driver_name, new_pos)
player:set_eye_offset(vector.zero(),vector.zero())
mcl_player.player_set_animation(player, "stand" , 30)
--else
--print("No player object found for "..driver_name)
end
end
function mod.kill_cart(staticdata, killer)
local pos
mcl_log("cart #"..staticdata.uuid.." was killed")
-- Leave nodes
if staticdata.attached_at then
handle_cart_leave(self, staticdata.attached_at, staticdata.dir )
else
--mcl_log("TODO: handle detatched minecart death")
end
-- Handle entity-related items
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
if le then
pos = le.object:get_pos()
detach_driver(le)
-- Detach passenger
if le._passenger then
local mob = le._passenger.object
mob:set_detach()
end
-- Remove the entity
le.object:remove()
else
pos = mod.get_cart_position(staticdata)
end
-- Drop items
if not staticdata.dropped then
-- Try to drop the cart
local entity_def = minetest.registered_entities[staticdata.cart_type]
if entity_def then
local drop_cart = true
if killer and minetest.is_creative_enabled(killer:get_player_name()) then
drop_cart = false
end
if drop_cart then
local drop = entity_def.drop
for d=1, #drop do
minetest.add_item(pos, drop[d])
end
end
end
-- Drop any items in the inventory
local inventory = staticdata.inventory
if inventory then
for i=1,#inventory do
minetest.add_item(pos, inventory[i])
end
end
-- Prevent item duplication
staticdata.dropped = true
end
-- Remove data
destroy_cart_data(staticdata.uuid)
end
local kill_cart = mod.kill_cart
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
local entity_mapping = {}
local function make_staticdata( _, connected_at, dir )
return {
connected_at = connected_at,
distance = 0,
velocity = 0,
dir = vector.new(dir),
mass = 1,
seq = 1,
}
end
local DEFAULT_CART_DEF = {
initial_properties = {
physical = true,
collisionbox = {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
visual = "mesh",
visual_size = {x=1, y=1},
},
hp_max = MINECART_MAX_HP,
groups = {
minecart = 1,
},
_driver = nil, -- player who sits in and controls the minecart (only for minecart!)
_passenger = nil, -- for mobs
_start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
_last_float_check = nil, -- timestamp of last time the cart was checked to be still on a rail
_boomtimer = nil, -- how many seconds are left before exploding
_blinktimer = nil, -- how many seconds are left before TNT blinking
_blink = false, -- is TNT blink texture active?
_old_pos = nil,
_staticdata = nil,
}
function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
-- Transfer older data
local data = minetest.deserialize(staticdata) or {}
if not data.uuid then
data.uuid = mcl_util.assign_uuid(self.object)
end
self._seq = data.seq or 1
local cd = get_cart_data(data.uuid)
if not cd then
update_cart_data(data)
else
if not cd.seq then cd.seq = 1 end
data = cd
end
-- Fix up types
data.dir = vector.new(data.dir)
-- Fix mass
data.mass = data.mass or 1
-- Make sure all carts have an ID to isolate them
self._uuid = data.uuid
self._staticdata = data
-- Activate cart if on powered activator rail
if self.on_activate_by_rail then
local pos = self.object:get_pos()
local node = minetest.get_node(vector.floor(pos))
if node.name == "mcl_minecarts:activator_rail_on" then
self:on_activate_by_rail()
end
end
end
function DEFAULT_CART_DEF:get_staticdata()
save_cart_data(self._staticdata.uuid)
return minetest.serialize({uuid = self._staticdata.uuid, seq=self._seq})
end
function DEFAULT_CART_DEF:_mcl_entity_invs_load_items()
local staticdata = self._staticdata
return staticdata.inventory or {}
end
function DEFAULT_CART_DEF:_mcl_entity_invs_save_items(items)
local staticdata = self._staticdata
staticdata.inventory = table.copy(items)
end
function DEFAULT_CART_DEF:add_node_watch(pos)
local staticdata = self._staticdata
local watches = staticdata.node_watches or {}
for i=1,#watches do
if watches[i] == pos then return end
end
watches[#watches+1] = pos
staticdata.node_watches = watches
end
function DEFAULT_CART_DEF:remove_node_watch(pos)
local staticdata = self._staticdata
local watches = staticdata.node_watches or {}
local new_watches = {}
for i=1,#watches do
local node_pos = watches[i]
if node_pos ~= pos then
new_watches[#new_watches + 1] = node_pos
end
end
staticdata.node_watches = new_watches
end
function DEFAULT_CART_DEF:get_cart_position()
local staticdata = self._staticdata
if staticdata.connected_at then
return staticdata.connected_at + staticdata.dir * staticdata.distance
else
return self.object:get_pos()
end
end
function DEFAULT_CART_DEF:on_punch(puncher, time_from_last_punch, tool_capabilities, dir, damage)
if puncher == self._driver then return end
local staticdata = self._staticdata
if puncher:get_player_control().sneak then
mod.kill_cart(staticdata, puncher)
return
end
local controls = staticdata.controls or {}
local impulse = vector.multiply(dir, damage * 20)
local accel = vector.dot(staticdata.dir, impulse)
if accel < 0 and staticdata.velocity == 0 then
mod.reverse_direction(staticdata)
end
controls.impulse = impulse
--print("uuid="..self._uuid..", controls="..dump(controls))
staticdata.controls = controls
end
function DEFAULT_CART_DEF:on_step(dtime)
local staticdata = self._staticdata
if not staticdata then
staticdata = make_staticdata()
self._staticdata = staticdata
end
-- Update entity position
local pos = mod.get_cart_position(staticdata)
if pos then self.object:move_to(pos) end
-- Repair cart_type
if not staticdata.cart_type then
staticdata.cart_type = self.name
end
-- Remove superceded entities
if staticdata.seq and self._seq < staticdata.seq then
--print("removing cart #"..staticdata.uuid.." with sequence number mismatch")
self.object:remove()
return
end
-- Regen
local hp = self.object:get_hp()
local time_now = minetest.get_gametime()
if hp < MINECART_MAX_HP and (staticdata.last_regen or 0) <= time_now - 1 then
staticdata.last_regen = time_now
hp = hp + 1
self.object:set_hp(hp)
end
-- Cart specific behaviors
local hook = self._mcl_minecarts_on_step
if hook then hook(self,dtime) end
if (staticdata.hopper_delay or 0) > 0 then
staticdata.hopper_delay = staticdata.hopper_delay - dtime
end
-- Controls
local ctrl, player = nil, nil
if self._driver then
player = minetest.get_player_by_name(self._driver)
if player then
ctrl = player:get_player_control()
-- player detach
if ctrl.sneak then
detach_driver(self)
return
end
-- Experimental controls
local now_time = minetest.get_gametime()
local controls = {}
if ctrl.up then controls.forward = now_time end
if ctrl.down then controls.brake = now_time end
controls.look = math.round(player:get_look_horizontal() * TWO_OVER_PI) % 4
staticdata.controls = controls
end
-- Give achievement when player reached a distance of 1000 nodes from the start position
if pos and vector.distance(self._start_pos, pos) >= 1000 then
awards.unlock(self._driver, "mcl:onARail")
end
end
if not staticdata.connected_at then
do_detached_movement(self, dtime)
end
mod.update_cart_orientation(self)
end
function DEFAULT_CART_DEF:on_death(killer)
kill_cart(self._staticdata, killer)
end
-- Create a minecart
function mod.create_minecart(entity_id, pos, dir)
-- Setup cart data
local uuid = mcl_util.gen_uuid()
local data = make_staticdata( nil, pos, dir )
data.uuid = uuid
data.cart_type = entity_id
update_cart_data(data)
save_cart_data(uuid)
return uuid
end
local create_minecart = mod.create_minecart
-- Place a minecart at pointed_thing
function mod.place_minecart(itemstack, pointed_thing, placer)
if not pointed_thing.type == "node" then
return
end
local spawn_pos = pointed_thing.above
local cart_dir = vector.new(1,0,0)
local railpos, node
if mcl_minecarts.is_rail(pointed_thing.under) then
railpos = pointed_thing.under
elseif mcl_minecarts.is_rail(pointed_thing.above) then
railpos = pointed_thing.above
end
if railpos then
spawn_pos = railpos
node = minetest.get_node(railpos)
cart_dir = mcl_minecarts.get_rail_direction(railpos, vector.new(1,0,0))
end
local entity_id = entity_mapping[itemstack:get_name()]
local uuid = create_minecart(entity_id, railpos, cart_dir)
-- Create the entity with the staticdata already setup
local sd = minetest.serialize({ uuid=uuid, seq=1 })
local cart = minetest.add_entity(spawn_pos, entity_id, sd)
local staticdata = get_cart_data(uuid)
cart:set_yaw(minetest.dir_to_yaw(cart_dir))
-- Call placer
local le = cart:get_luaentity()
if le._mcl_minecarts_on_place then
le._mcl_minecarts_on_place(le, placer)
end
if railpos then
handle_cart_enter(staticdata, railpos)
end
local pname = placer and placer:get_player_name() or ""
if not minetest.is_creative_enabled(pname) then
itemstack:take_item()
end
return itemstack
end
local function dropper_place_minecart(dropitem, pos)
-- Don't try to place the minecart if pos isn't a rail
local node = minetest.get_node(pos)
if minetest.get_item_group(node.name, "rail") == 0 then return false end
mod.place_minecart(dropitem, {
above = pos,
under = vector.offset(pos,0,-1,0)
})
return true
end
local function register_minecart_craftitem(itemstring, def)
local groups = { minecart = 1, transport = 1 }
if def.creative == false then
groups.not_in_creative_inventory = 1
end
local item_def = {
stack_max = 1,
_mcl_dropper_on_drop = dropper_place_minecart,
on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing.type == "node" then
return
end
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node(pointed_thing.under)
if placer and not placer:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
return mod.place_minecart(itemstack, pointed_thing, placer)
end,
_on_dispense = function(stack, pos, droppos, dropnode, dropdir)
-- Place minecart as entity on rail. If there's no rail, just drop it.
local placed
if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
-- FIXME: This places minecarts even if the spot is already occupied
local pointed_thing = { under = droppos, above = vector.new( droppos.x, droppos.y+1, droppos.z ) }
placed = mod.place_minecart(stack, pointed_thing)
end
if placed == nil then
-- Drop item
minetest.add_item(droppos, stack)
end
end,
groups = groups,
}
item_def.description = def.description
item_def._tt_help = def.tt_help
item_def._doc_items_longdesc = def.longdesc
item_def._doc_items_usagehelp = def.usagehelp
item_def.inventory_image = def.icon
item_def.wield_image = def.icon
minetest.register_craftitem(itemstring, item_def)
end
--[[
Register a minecart
* itemstring: Itemstring of minecart item
* entity_id: ID of minecart entity
* description: Item name / description
* longdesc: Long help text
* usagehelp: Usage help text
* mesh: Minecart mesh
* textures: Minecart textures table
* icon: Item icon
* drop: Dropped items after destroying minecart
* on_rightclick: Called after rightclick
* on_activate_by_rail: Called when above activator rail
* creative: If false, don't show in Creative Inventory
]]
function mod.register_minecart(def)
-- Make sure all required parameters are present
for _,name in pairs({"drop","itemstring","entity_id"}) do
assert( def[name], "def."..name..", a required parameter, is missing")
end
local entity_id = def.entity_id; def.entity_id = nil
local craft = def.craft; def.craft = nil
local itemstring = def.itemstring; def.itemstring = nil
-- Build cart definition
local cart = table.copy(DEFAULT_CART_DEF)
table_merge(cart, def)
minetest.register_entity(entity_id, cart)
-- Register item to entity mapping
entity_mapping[itemstring] = entity_id
register_minecart_craftitem(itemstring, def)
if minetest.get_modpath("doc_identifier") then
doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
end
if craft then
minetest.register_craft(craft)
end
end
local register_minecart = mod.register_minecart
dofile(modpath.."/carts/minecart.lua")
dofile(modpath.."/carts/with_chest.lua")
dofile(modpath.."/carts/with_commandblock.lua")
dofile(modpath.."/carts/with_hopper.lua")
dofile(modpath.."/carts/with_furnace.lua")
dofile(modpath.."/carts/with_tnt.lua")
if minetest.get_modpath("mcl_wip") then
mcl_wip.register_wip_item("mcl_minecarts:chest_minecart")
mcl_wip.register_wip_item("mcl_minecarts:furnace_minecart")
mcl_wip.register_wip_item("mcl_minecarts:command_block_minecart")
end
local function respawn_cart(cart)
local cart_type = cart.cart_type or "mcl_minecarts:minecart"
local pos = mod.get_cart_position(cart)
local players = minetest.get_connected_players()
local distance = nil
for _,player in pairs(players) do
local d = vector.distance(player:get_pos(), pos)
if not distance or d < distance then distance = d end
end
if not distance or distance > 90 then return end
mcl_log("Respawning cart #"..cart.uuid.." at "..tostring(pos)..",distance="..distance..",node="..minetest.get_node(pos).name)
-- Update sequence so that old cart entities get removed
cart.seq = (cart.seq or 1) + 1
save_cart_data(cart.uuid)
-- Create the new entity and refresh caches
local sd = minetest.serialize({ uuid=cart.uuid, seq=cart.seq })
local entity = minetest.add_entity(pos, cart_type, sd)
local le = entity:get_luaentity()
le._staticdata = cart
mcl_util.assign_uuid(entity)
-- We intentionally don't call the normal hooks because this minecart was already there
end
-- Try to respawn cart entities for carts that have moved into range of a player
local function try_respawn_carts()
-- Build a map of blocks near players
local block_map = {}
local players = minetest.get_connected_players()
for _,player in pairs(players) do
local pos = player:get_pos()
mod.add_blocks_to_map(
block_map,
vector.offset(pos,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE),
vector.offset(pos, CART_BLOCK_SIZE, CART_BLOCK_SIZE, CART_BLOCK_SIZE)
)
end
-- Find all cart data that are in these blocks
local carts = find_carts_by_block_map(block_map)
-- Check to see if any of these don't have an entity
for _,cart in pairs(carts) do
local le = mcl_util.get_luaentity_from_uuid(cart.uuid)
if not le then
respawn_cart(cart)
end
end
end
local timer = 0
minetest.register_globalstep(function(dtime)
-- Periodically respawn carts that come into range of a player
timer = timer - dtime
if timer <= 0 then
local start_time = minetest.get_us_time()
try_respawn_carts()
local stop_time = minetest.get_us_time()
local duration = (stop_time - start_time) / 1e6
timer = duration / 250e-6 -- Schedule 50us per second
if timer > 5 then timer = 5 end
end
-- Handle periodically updating out-of-range carts
-- TODO: change how often cart positions are updated based on velocity
local start_time
if DEBUG then start_time = minetest.get_us_time() end
for uuid,staticdata in mod.carts() do
local pos = mod.get_cart_position(staticdata)
--[[
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
print("cart# "..uuid..
",velocity="..tostring(staticdata.velocity)..
",pos="..tostring(pos)..
",le="..tostring(le)..
",connected_at="..tostring(staticdata.connected_at)
)]]
--- Non-entity code
if staticdata.connected_at then
do_movement(staticdata, dtime)
end
end
if DEBUG then
local stop_time = minetest.get_us_time()
print("Update took "..((stop_time-start_time)*1e-6).." seconds")
end
end)
minetest.register_on_joinplayer(function(player)
-- Try cart reattachment
local player_name = player:get_player_name()
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
local cart_uuid = player_meta.attached_to
if cart_uuid then
local cartdata = get_cart_data(cart_uuid)
-- Can't get into a cart that was destroyed
if not cartdata then
return
end
-- Don't reattach players if someone else got in the cart
if cartdata.last_player ~= player_name then
return
end
minetest.after(0.2,function(player_name, cart_uuid)
local player = minetest.get_player_by_name(player_name)
if not player then
return
end
local cart = mcl_util.get_luaentity_from_uuid(cart_uuid)
if not cart then
return
end
mod.attach_driver(cart, player)
end, player_name, cart_uuid)
end
end)

View File

@ -0,0 +1,101 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local mcl_log = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
local mod = mcl_minecarts
-- Imports
local PASSENGER_ATTACH_POSITION = mod.PASSENGER_ATTACH_POSITION
local function activate_normal_minecart(self)
detach_driver(self)
-- Detach passenger
if self._passenger then
local mob = self._passenger.object
mob:set_detach()
end
end
function mod.attach_driver(cart, player)
local staticdata = cart._staticdata
-- Make sure we have a player
if not player or not player:is_player() then return end
local player_name = player:get_player_name()
if cart._driver or player:get_player_control().sneak then return end
-- Update cart information
cart._driver = player_name
cart._start_pos = cart.object:get_pos()
-- Keep track of player attachment
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
player_meta.attached_to = cart._uuid
staticdata.last_player = player_name
-- Update player information
local uuid = staticdata.uuid
mcl_player.player_attached[player_name] = true
--minetest.log("action", player_name.." entered minecart #"..tostring(uuid).." at "..tostring(cart._start_pos))
-- Attach the player object to the minecart
player:set_attach(cart.object, "", vector.new(1,-1.75,-2), vector.new(0,0,0))
minetest.after(0.2, function(name)
local player = minetest.get_player_by_name(name)
if player then
mcl_player.player_set_animation(player, "sit" , 30)
player:set_eye_offset(vector.new(0,-5.5,0), vector.new(0,-4,0))
mcl_title.set(player, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
end
end, player_name)
end
mod.register_minecart({
itemstring = "mcl_minecarts:minecart",
craft = {
output = "mcl_minecarts:minecart",
recipe = {
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
},
},
entity_id = "mcl_minecarts:minecart",
description = S("Minecart"),
tt_helop = S("Vehicle for fast travel on rails"),
long_descp = S("Minecarts can be used for a quick transportion on rails.") .. "\n" ..
S("Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type."),
S("You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.") .. "\n" ..
S("To obtain the minecart, punch it while holding down the sneak key.") .. "\n" ..
S("If it moves over a powered activator rail, you'll get ejected."),
initial_properties = {
mesh = "mcl_minecarts_minecart.b3d",
textures = {"mcl_minecarts_minecart.png"},
},
icon = "mcl_minecarts_minecart_normal.png",
drop = {"mcl_minecarts:minecart"},
on_rightclick = mod.attach_driver,
on_activate_by_rail = activate_normal_minecart,
_mcl_minecarts_on_step = function(self, dtime)
-- Grab mob
if math.random(1,20) > 15 and not self._passenger then
local mobsnear = minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)
for n=1, #mobsnear do
local mob = mobsnear[n]
if mob then
local entity = mob:get_luaentity()
if entity and entity.is_mob then
self._passenger = entity
mob:set_attach(self.object, "", PASSENGER_ATTACH_POSITION, vector.zero())
break
end
end
end
elseif self._passenger then
local passenger_pos = self._passenger.object:get_pos()
if not passenger_pos then
self._passenger = nil
end
end
end
})

View File

@ -0,0 +1,35 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
-- Minecart with Chest
mcl_minecarts.register_minecart({
itemstring = "mcl_minecarts:chest_minecart",
craft = {
output = "mcl_minecarts:chest_minecart",
recipe = {
{"mcl_chests:chest"},
{"mcl_minecarts:minecart"},
},
},
entity_id = "mcl_minecarts:chest_minecart",
description = S("Minecart with Chest"),
tt_help = nil,
longdesc = nil,
usagehelp = nil,
initial_properties = {
mesh = "mcl_minecarts_minecart_chest.b3d",
textures = {
"mcl_chests_normal.png",
"mcl_minecarts_minecart.png"
},
},
icon = "mcl_minecarts_minecart_chest.png",
drop = {"mcl_minecarts:minecart", "mcl_chests:chest"},
groups = { container = 1 },
on_rightclick = nil,
on_activate_by_rail = nil,
creative = true
})
mcl_entity_invs.register_inv("mcl_minecarts:chest_minecart","Minecart",27,false,true)

View File

@ -0,0 +1,64 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
function table_metadata(table)
return {
table = table,
set_string = function(self, key, value)
--print("set_string("..tostring(key)..", "..tostring(value)..")")
self.table[key] = tostring(value)
end,
get_string = function(self, key)
if self.table[key] then
return tostring(self.table[key])
end
end
}
end
-- Minecart with Command Block
mod.register_minecart({
itemstring = "mcl_minecarts:command_block_minecart",
entity_id = "mcl_minecarts:command_block_minecart",
description = S("Minecart with Command Block"),
tt_help = nil,
loncdesc = nil,
usagehelp = nil,
initial_properties = {
mesh = "mcl_minecarts_minecart_block.b3d",
textures = {
"jeija_commandblock_off.png^[verticalframe:2:0",
"jeija_commandblock_off.png^[verticalframe:2:0",
"jeija_commandblock_off.png^[verticalframe:2:0",
"jeija_commandblock_off.png^[verticalframe:2:0",
"jeija_commandblock_off.png^[verticalframe:2:0",
"jeija_commandblock_off.png^[verticalframe:2:0",
"mcl_minecarts_minecart.png",
},
},
icon = "mcl_minecarts_minecart_command_block.png",
drop = {"mcl_minecarts:minecart"},
on_rightclick = function(self, clicker)
self._staticdata.meta = self._staticdata.meta or {}
local meta = table_metadata(self._staticdata.meta)
mesecon.commandblock.handle_rightclick(meta, clicker)
end,
_mcl_minecarts_on_place = function(self, placer)
-- Create a fake metadata object that stores into the cart's staticdata
self._staticdata.meta = self._staticdata.meta or {}
local meta = table_metadata(self._staticdata.meta)
mesecon.commandblock.initialize(meta)
mesecon.commandblock.place(meta, placer)
end,
on_activate_by_rail = function(self, timer)
self._staticdata.meta = self._staticdata.meta or {}
local meta = table_metadata(self._staticdata.meta)
mesecon.commandblock.action_on(meta, self.object:get_pos())
end,
creative = true
})

View File

@ -0,0 +1,96 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
-- Minecart with Furnace
mcl_minecarts.register_minecart({
itemstring = "mcl_minecarts:furnace_minecart",
craft = {
output = "mcl_minecarts:furnace_minecart",
recipe = {
{"mcl_furnaces:furnace"},
{"mcl_minecarts:minecart"},
},
},
entity_id = "mcl_minecarts:furnace_minecart",
description = S("Minecart with Furnace"),
tt_help = nil,
longdesc = S("A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel."),
usagehelp = S("Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving.") .. "\n" ..
S("To obtain the minecart and furnace, punch them while holding down the sneak key."),
initial_properties = {
mesh = "mcl_minecarts_minecart_block.b3d",
textures = {
"default_furnace_top.png",
"default_furnace_top.png",
"default_furnace_front.png",
"default_furnace_side.png",
"default_furnace_side.png",
"default_furnace_side.png",
"mcl_minecarts_minecart.png",
},
},
icon = "mcl_minecarts_minecart_furnace.png",
drop = {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
on_rightclick = function(self, clicker)
local staticdata = self._staticdata
-- Feed furnace with coal
if not clicker or not clicker:is_player() then
return
end
local held = clicker:get_wielded_item()
if minetest.get_item_group(held:get_name(), "coal") == 1 then
staticdata.fueltime = (staticdata.fueltime or 0) + 180
-- Trucate to 27 minutes (9 uses)
if staticdata.fueltime > 27*60 then
staticdata.fuel_time = 27*60
end
if not minetest.is_creative_enabled(clicker:get_player_name()) then
held:take_item()
local index = clicker:get_wield_index()
local inv = clicker:get_inventory()
inv:set_stack("main", index, held)
end
self.object:set_properties({textures =
{
"default_furnace_top.png",
"default_furnace_top.png",
"default_furnace_front_active.png",
"default_furnace_side.png",
"default_furnace_side.png",
"default_furnace_side.png",
"mcl_minecarts_minecart.png",
}})
end
end,
on_activate_by_rail = nil,
creative = true,
_mcl_minecarts_on_step = function(self, dtime)
local staticdata = self._staticdata
-- Update furnace stuff
if (staticdata.fueltime or 0) > 0 then
if staticdata.velocity < 0.25 then
staticdata.velocity = 0.25
end
staticdata.fueltime = (staticdata.fueltime or dtime) - dtime
if staticdata.fueltime <= 0 then
self.object:set_properties({textures =
{
"default_furnace_top.png",
"default_furnace_top.png",
"default_furnace_front.png",
"default_furnace_side.png",
"default_furnace_side.png",
"default_furnace_side.png",
"mcl_minecarts_minecart.png",
}})
staticdata.fueltime = 0
end
end
end,
})

View File

@ -0,0 +1,178 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
local function mcl_log(message)
if LOGGING_ON then
mcl_util.mcl_log(message, "[Minecarts]", true)
end
end
local function hopper_take_item(self, dtime)
local pos = self.object:get_pos()
if not pos then return end
if not self or self.name ~= "mcl_minecarts:hopper_minecart" then return end
if mcl_util.check_dtime_timer(self, dtime, "hoppermc_take", 0.15) then
--minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name)
else
--minetest.log("The check timer was not triggered")
return
end
local above_pos = vector.offset(pos, 0, 0.9, 0)
local objs = minetest.get_objects_inside_radius(above_pos, 1.25)
if objs then
mcl_log("there is an itemstring. Number of objs: ".. #objs)
for k, v in pairs(objs) do
local ent = v:get_luaentity()
if ent and not ent._removed and ent.itemstring and ent.itemstring ~= "" then
local taken_items = false
mcl_log("ent.name: " .. tostring(ent.name))
mcl_log("ent pos: " .. tostring(ent.object:get_pos()))
local inv = mcl_entity_invs.load_inv(self, 5)
if not inv then return false end
local current_itemstack = ItemStack(ent.itemstring)
mcl_log("inv. size: " .. self._inv_size)
if inv:room_for_item("main", current_itemstack) then
mcl_log("Room")
inv:add_item("main", current_itemstack)
ent.object:get_luaentity().itemstring = ""
ent.object:remove()
taken_items = true
else
mcl_log("no Room")
end
if not taken_items then
local items_remaining = current_itemstack:get_count()
-- This will take part of a floating item stack if no slot can hold the full amount
for i = 1, self._inv_size, 1 do
local stack = inv:get_stack("main", i)
mcl_log("i: " .. tostring(i))
mcl_log("Items remaining: " .. items_remaining)
mcl_log("Name: " .. tostring(stack:get_name()))
if current_itemstack:get_name() == stack:get_name() then
mcl_log("We have a match. Name: " .. tostring(stack:get_name()))
local room_for = stack:get_stack_max() - stack:get_count()
mcl_log("Room for: " .. tostring(room_for))
if room_for == 0 then
-- Do nothing
mcl_log("No room")
elseif room_for < items_remaining then
mcl_log("We have more items remaining than space")
items_remaining = items_remaining - room_for
stack:set_count(stack:get_stack_max())
inv:set_stack("main", i, stack)
taken_items = true
else
local new_stack_size = stack:get_count() + items_remaining
stack:set_count(new_stack_size)
mcl_log("We have more than enough space. Now holds: " .. new_stack_size)
inv:set_stack("main", i, stack)
items_remaining = 0
ent.object:get_luaentity().itemstring = ""
ent.object:remove()
taken_items = true
break
end
mcl_log("Count: " .. tostring(stack:get_count()))
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
--mcl_log("Is it empty: " .. stack:to_string())
end
if i == self._inv_size and taken_items then
mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining)
current_itemstack:set_count(items_remaining)
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
ent.itemstring = current_itemstack:to_string()
end
end
end
--Add in, and delete
if taken_items then
mcl_log("Saving")
mcl_entity_invs.save_inv(ent)
return taken_items
else
mcl_log("No need to save")
end
end
end
end
return false
end
-- Minecart with Hopper
mod.register_minecart({
itemstring = "mcl_minecarts:hopper_minecart",
craft = {
output = "mcl_minecarts:hopper_minecart",
recipe = {
{"mcl_hoppers:hopper"},
{"mcl_minecarts:minecart"},
},
},
entity_id = "mcl_minecarts:hopper_minecart",
description = S("Minecart with Hopper"),
tt_help = nil,
longdesc = nil,
usagehelp = nil,
initial_properties = {
mesh = "mcl_minecarts_minecart_hopper.b3d",
textures = {
"mcl_hoppers_hopper_inside.png",
"mcl_minecarts_minecart.png",
"mcl_hoppers_hopper_outside.png",
"mcl_hoppers_hopper_top.png",
},
},
icon = "mcl_minecarts_minecart_hopper.png",
drop = {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
groups = { container = 1 },
on_rightclick = nil,
on_activate_by_rail = nil,
_mcl_minecarts_on_enter = function(self, pos, staticdata)
if (staticdata.hopper_delay or 0) > 0 then
return
end
-- try to pull from containers into our inventory
if not self then return end
local inv = mcl_entity_invs.load_inv(self,5)
local above_pos = vector.offset(pos,0,1,0)
mcl_util.hopper_pull_to_inventory(inv, 'main', above_pos, pos)
staticdata.hopper_delay = (staticdata.hopper_delay or 0) + (1/20)
end,
_mcl_minecarts_on_step = function(self, dtime)
hopper_take_item(self, dtime)
end,
creative = true
})
mcl_entity_invs.register_inv("mcl_minecarts:hopper_minecart", "Hopper Minecart", 5, false, true)

View File

@ -0,0 +1,139 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local function detonate_tnt_minecart(self)
local pos = self.object:get_pos()
self.object:remove()
mcl_explosions.explode(pos, 6, { drop_chance = 1.0 })
end
local function activate_tnt_minecart(self, timer)
if self._boomtimer then
return
end
if timer then
self._boomtimer = timer
else
self._boomtimer = tnt.BOOMTIMER
end
self.object:set_properties({
textures = {
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_minecarts_minecart.png",
},
glow = 15,
})
self._blinktimer = tnt.BLINKTIMER
minetest.sound_play("tnt_ignite", {pos = self.object:get_pos(), gain = 1.0, max_hear_distance = 15}, true)
end
mod.register_minecart({
itemstring = "mcl_minecarts:tnt_minecart",
craft = {
output = "mcl_minecarts:tnt_minecart",
recipe = {
{"mcl_tnt:tnt"},
{"mcl_minecarts:minecart"},
},
},
entity_id = "mcl_minecarts:tnt_minecart",
description = S("Minecart with TNT"),
tt_help = S("Vehicle for fast travel on rails").."\n"..S("Can be ignited by tools or powered activator rail"),
longdesc = S("A minecart with TNT is an explosive vehicle that travels on rail."),
usagehelp = S("Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail.") .. "\n" ..
S("To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited."),
initial_properties = {
mesh = "mcl_minecarts_minecart_block.b3d",
textures = {
"default_tnt_top.png",
"default_tnt_bottom.png",
"default_tnt_side.png",
"default_tnt_side.png",
"default_tnt_side.png",
"default_tnt_side.png",
"mcl_minecarts_minecart.png",
},
},
icon = "mcl_minecarts_minecart_tnt.png",
drop = {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
on_rightclick = function(self, clicker)
-- Ingite
if not clicker or not clicker:is_player() then
return
end
if self._boomtimer then
return
end
local held = clicker:get_wielded_item()
if held:get_name() == "mcl_fire:flint_and_steel" then
if not minetest.is_creative_enabled(clicker:get_player_name()) then
held:add_wear(65535/65) -- 65 uses
local index = clicker:get_wield_index()
local inv = clicker:get_inventory()
inv:set_stack("main", index, held)
end
activate_tnt_minecart(self)
end
end,
on_activate_by_rail = activate_tnt_minecart,
creative = true,
_mcl_minecarts_on_step = function(self, dtime)
-- Impacts reduce the speed greatly. Use this to trigger explosions
local current_speed = vector.length(self.object:get_velocity())
if current_speed < (self._old_speed or 0) - 6 then
detonate_tnt_minecart(self)
end
self._old_speed = current_speed
if self._boomtimer then
-- Explode
self._boomtimer = self._boomtimer - dtime
if self._boomtimer <= 0 then
detonate_tnt_minecart(self)
return
else
local pos = mod.get_cart_position(self._staticdata) or self.object:get_pos()
if pos then
tnt.smoke_step(pos)
end
end
end
if self._blinktimer then
self._blinktimer = self._blinktimer - dtime
if self._blinktimer <= 0 then
self._blink = not self._blink
if self._blink then
self.object:set_properties({textures =
{
"default_tnt_top.png",
"default_tnt_bottom.png",
"default_tnt_side.png",
"default_tnt_side.png",
"default_tnt_side.png",
"default_tnt_side.png",
"mcl_minecarts_minecart.png",
}})
else
self.object:set_properties({textures =
{
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_tnt_blink.png",
"mcl_minecarts_minecart.png",
}})
end
self._blinktimer = tnt.BLINKTIMER
end
end
end,
})

View File

@ -1,4 +1,36 @@
local vector = vector
local mod = mcl_minecarts
local table_merge = mcl_util.table_merge
function get_path(base, first, ...)
if not first then return base end
if not base then return end
return get_path(base[first], ...)
end
local function force_get_node(pos)
local node = minetest.get_node(pos)
if node.name ~= "ignore" then return node end
--local time_start = minetest.get_us_time()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos, pos)
local area = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax,
}
local data = vm:get_data()
local param_data = vm:get_light_data()
local param2_data = vm:get_param2_data()
local vi = area:indexp(pos)
--minetest.log("force_get_node() voxel_manip section took "..((minetest.get_us_time()-time_start)*1e-6).." seconds")
return {
name = minetest.get_name_from_content_id(data[vi]),
param = param_data[vi],
param2 = param2_data[vi]
}
end
function mcl_minecarts:get_sign(z)
if z == 0 then
@ -10,158 +42,404 @@ end
function mcl_minecarts:velocity_to_dir(v)
if math.abs(v.x) > math.abs(v.z) then
return {x=mcl_minecarts:get_sign(v.x), y=mcl_minecarts:get_sign(v.y), z=0}
return vector.new(
mcl_minecarts:get_sign(v.x),
mcl_minecarts:get_sign(v.y),
0
)
else
return {x=0, y=mcl_minecarts:get_sign(v.y), z=mcl_minecarts:get_sign(v.z)}
return vector.new(
0,
mcl_minecarts:get_sign(v.y),
mcl_minecarts:get_sign(v.z)
)
end
end
function mcl_minecarts:is_rail(pos, railtype)
local node = minetest.get_node(pos).name
if node == "ignore" then
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos, pos)
local area = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax,
}
local data = vm:get_data()
local vi = area:indexp(pos)
node = minetest.get_name_from_content_id(data[vi])
function mcl_minecarts.is_rail(self, pos, railtype)
-- Compatibility with mcl_minecarts:is_rail() usage
if self ~= mcl_minecarts then
railtype = pos
pos = self
end
if minetest.get_item_group(node, "rail") == 0 then
local node_name = force_get_node(pos).name
if minetest.get_item_group(node_name, "rail") == 0 then
return false
end
if not railtype then
return true
end
return minetest.get_item_group(node, "connect_to_raillike") == railtype
return minetest.get_item_group(node_name, "connect_to_raillike") == railtype
end
function mcl_minecarts:check_front_up_down(pos, dir_, check_down, railtype)
local dir = vector.new(dir_)
-- Front
dir.y = 0
local cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
end
-- Up
if check_down then
dir.y = 1
cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
-- Directional constants
local north = vector.new( 0, 0, 1); local N = 1 -- 4dir = 0
local east = vector.new( 1, 0, 0); local E = 4 -- 4dir = 1
local south = vector.new( 0, 0,-1); local S = 2 -- 4dir = 2
local west = vector.new(-1, 0, 0); local W = 8 -- 4dir = 3
-- Share. Consider moving this to some shared location
mod.north = north
mod.south = south
mod.east = east
mod.west = west
--[[
mcl_minecarts.snap_direction(dir)
returns a valid cart direction that has the smallest angle difference to `dir'
]]
local VALID_DIRECTIONS = {
north, vector.offset(north, 0, 1, 0), vector.offset(north, 0, -1, 0),
south, vector.offset(south, 0, 1, 0), vector.offset(south, 0, -1, 0),
east, vector.offset(east, 0, 1, 0), vector.offset(east, 0, -1, 0),
west, vector.offset(west, 0, 1, 0), vector.offset(west, 0, -1, 0),
}
function mod.snap_direction(dir)
dir = vector.normalize(dir)
local best = nil
local diff = -1
for _,d in pairs(VALID_DIRECTIONS) do
local dot = vector.dot(dir,d)
if dot > diff then
best = d
diff = dot
end
end
-- Down
dir.y = -1
cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
end
return nil
return best
end
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
local pos = vector.round(pos_)
local cur
local left_check, right_check = true, true
local CONNECTIONS = { north, south, east, west }
local HORIZONTAL_STANDARD_RULES = {
[N] = { "", 0, mask = N, score = 1, can_slope = true },
[S] = { "", 0, mask = S, score = 1, can_slope = true },
[N+S] = { "", 0, mask = N+S, score = 2, can_slope = true },
-- Check left and right
local left = {x=0, y=0, z=0}
local right = {x=0, y=0, z=0}
if dir.z ~= 0 and dir.x == 0 then
left.x = -dir.z
right.x = dir.z
elseif dir.x ~= 0 and dir.z == 0 then
left.z = dir.x
right.z = -dir.x
end
[E] = { "", 1, mask = E, score = 1, can_slope = true },
[W] = { "", 1, mask = W, score = 1, can_slope = true },
[E+W] = { "", 1, mask = E+W, score = 2, can_slope = true },
}
mod.HORIZONTAL_STANDARD_RULES = HORIZONTAL_STANDARD_RULES
if ctrl then
if old_switch == 1 then
left_check = false
elseif old_switch == 2 then
right_check = false
end
if ctrl.left and left_check then
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
if cur then
return cur, 1
end
left_check = false
end
if ctrl.right and right_check then
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
if cur then
return cur, 2
end
right_check = true
end
end
local HORIZONTAL_CURVES_RULES = {
[N+E] = { "_corner", 3, name = "ne corner", mask = N+E, score = 3 },
[N+W] = { "_corner", 2, name = "nw corner", mask = N+W, score = 3 },
[S+E] = { "_corner", 0, name = "se corner", mask = S+E, score = 3 },
[S+W] = { "_corner", 1, name = "sw corner", mask = S+W, score = 3 },
-- Normal
cur = mcl_minecarts:check_front_up_down(pos, dir, true, railtype)
if cur then
return cur
end
[N+E+W] = { "_tee_off", 3, mask = N+E+W, score = 4 },
[S+E+W] = { "_tee_off", 1, mask = S+E+W, score = 4 },
[N+S+E] = { "_tee_off", 0, mask = N+S+E, score = 4 },
[N+S+W] = { "_tee_off", 2, mask = N+S+W, score = 4 },
-- Left, if not already checked
if left_check then
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
if cur then
return cur
end
end
[N+S+E+W] = { "_cross", 0, mask = N+S+E+W, score = 5 },
}
table_merge(HORIZONTAL_CURVES_RULES, HORIZONTAL_STANDARD_RULES)
mod.HORIZONTAL_CURVES_RULES = HORIZONTAL_CURVES_RULES
-- Right, if not already checked
if right_check then
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
if cur then
return cur
end
end
-- Backwards
if not old_switch then
cur = mcl_minecarts:check_front_up_down(pos, {
x = -dir.x,
y = dir.y,
z = -dir.z
}, true, railtype)
if cur then
return cur
end
end
return {x=0, y=0, z=0}
end
local plane_adjacents = {
vector.new(-1,0,0),
vector.new(1,0,0),
vector.new(0,0,-1),
vector.new(0,0,1),
local HORIZONTAL_RULES_BY_RAIL_GROUP = {
[1] = HORIZONTAL_STANDARD_RULES,
[2] = HORIZONTAL_CURVES_RULES,
}
function mcl_minecarts:get_start_direction(pos)
local dir
local i = 0
while (not dir and i < #plane_adjacents) do
i = i+1
local node = minetest.get_node_or_nil(vector.add(pos, plane_adjacents[i]))
if node ~= nil
and minetest.get_item_group(node.name, "rail") == 0
and minetest.get_item_group(node.name, "solid") == 1
and minetest.get_item_group(node.name, "opaque") == 1
then
dir = mcl_minecarts:check_front_up_down(pos, vector.multiply(plane_adjacents[i], -1), true)
end
local function check_connection_rule(pos, connections, rule)
-- All bits in the mask must be set for the connection to be possible
if bit.band(rule.mask,connections) ~= rule.mask then
--print("Mask mismatch ("..tostring(rule.mask)..","..tostring(connections)..")")
return false
end
return dir
-- If there is an allow filter, that mush also return true
if rule.allow and rule.allow(rule, connections, pos) then
return false
end
return true
end
function mcl_minecarts:set_velocity(obj, dir, factor)
obj._velocity = vector.multiply(dir, factor or 3)
obj._old_pos = nil
obj._punched = true
local function make_sloped_if_straight(pos, dir)
local node = minetest.get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
local param2 = 0
if dir == east then
param2 = 3
elseif dir == west then
param2 = 1
elseif dir == north then
param2 = 2
elseif dir == south then
param2 = 0
end
if get_path( nodedef, "_mcl_minecarts", "railtype" ) == "straight" then
minetest.swap_node(pos, {name = nodedef._mcl_minecarts.base_name .. "_sloped", param2 = param2})
end
end
local function is_connection(pos, dir)
local node = force_get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
local get_next_dir = get_path(nodedef, "_mcl_minecarts", "get_next_dir")
if not get_next_dir then return end
local next_dir = get_next_dir(pos, dir, node)
next_dir.y = 0
return vector.equals(next_dir, dir)
end
local function get_rail_connections(pos, opt)
local legacy = opt and opt.legacy
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
local connections = 0
for i = 1,#CONNECTIONS do
dir = CONNECTIONS[i]
local neighbor = vector.add(pos, dir)
local node = force_get_node(neighbor)
local nodedef = minetest.registered_nodes[node.name]
-- Only allow connections to the open ends of rails, as decribed by get_next_dir
if mcl_minecarts.is_rail(neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
local rev_dir = vector.direction(dir,vector.zero())
if ignore_neighbor_connections or is_connection(neighbor, rev_dir) then
connections = bit.bor(connections, bit.lshift(1,i - 1))
end
end
-- Check for sloped rail one block down
local below_neighbor = vector.offset(neighbor, 0, -1, 0)
local node = force_get_node(below_neighbor)
local nodedef = minetest.registered_nodes[node.name]
if mcl_minecarts.is_rail(below_neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
local rev_dir = vector.direction(dir, vector.zero())
if ignore_neighbor_connections or is_connection(below_neighbor, rev_dir) then
connections = bit.bor(connections, bit.lshift(1,i - 1))
end
end
end
return connections
end
mod.get_rail_connections = get_rail_connections
local function update_rail_connections(pos, opt)
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
local node = minetest.get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
if not nodedef or not nodedef._mcl_minecarts then return end
-- Get the mappings to use
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
if nodedef._mcl_minecarts and nodedef._mcl_minecarts.connection_rules then -- Custom connection rules
rules = nodedef._mcl_minecarts.connection_rules
end
if not rules then return end
-- Horizontal rules, Check for rails on each neighbor
local connections = get_rail_connections(pos, opt)
-- Check for rasing rails to slopes
for i = 1,#CONNECTIONS do
local dir = CONNECTIONS[i]
local neighbor = vector.add(pos, dir)
make_sloped_if_straight( vector.offset(neighbor, 0, -1, 0), dir )
end
-- Select the best allowed connection
local rule = nil
local score = 0
for k,r in pairs(rules) do
if check_connection_rule(pos, connections, r) then
if r.score > score then
--print("Best rule so far is "..dump(r))
score = r.score
rule = r
end
end
end
if rule then
-- Apply the mapping
local new_name = nodedef._mcl_minecarts.base_name..rule[1]
if new_name ~= node.name or node.param2 ~= rule[2] then
--print("swapping "..node.name.." for "..new_name..","..tostring(rule[2]).." at "..tostring(pos))
node.name = new_name
node.param2 = rule[2]
minetest.swap_node(pos, node)
end
if rule.after then
rule.after(rule, pos, connections)
end
end
local node_def = minetest.registered_nodes[node.name]
if get_path(node_def, "_mcl_minecarts", "can_slope") then
for i=1,#CONNECTIONS do
local dir = CONNECTIONS[i]
local higher_rail_pos = vector.offset(pos,dir.x,1,dir.z)
local rev_dir = vector.direction(dir,vector.zero())
if mcl_minecarts.is_rail(higher_rail_pos) and is_connection(higher_rail_pos, rev_dir) then
make_sloped_if_straight(pos, rev_dir)
end
end
end
end
mod.update_rail_connections = update_rail_connections
local north = vector.new(0,0,1)
local south = vector.new(0,0,-1)
local east = vector.new(1,0,0)
local west = vector.new(-1,0,0)
local function is_ahead_slope(pos, dir)
local ahead = vector.add(pos,dir)
if mcl_minecarts.is_rail(ahead) then return false end
local below = vector.offset(ahead,0,-1,0)
if not mcl_minecarts.is_rail(below) then return false end
local node_name = force_get_node(below).name
return minetest.get_item_group(node_name, "rail_slope") ~= 0
end
local function get_rail_direction_inner(pos, dir)
-- Handle new track types that have track-specific direction handler
local node = minetest.get_node(pos)
local node_def = minetest.registered_nodes[node.name]
local get_next_dir = get_path(node_def,"_mcl_minecarts","get_next_dir")
if not get_next_dir then return dir end
dir = node_def._mcl_minecarts.get_next_dir(pos, dir, node)
-- Handle reversing if there is a solid block in the next position
local next_pos = vector.add(pos, dir)
local next_node = minetest.get_node(next_pos)
local node_def = minetest.registered_nodes[next_node.name]
if node_def and node_def.groups and ( node_def.groups.solid or node_def.groups.stair ) then
-- Reverse the direction without giving -0 members
dir = vector.direction(next_pos, pos)
end
-- Handle going downhill
if is_ahead_slope(pos,dir) then
dir = vector.offset(dir,0,-1,0)
end
return dir
end
function mcl_minecarts.get_rail_direction(self, pos_, dir)
-- Compatibility with mcl_minecarts:get_rail_direction() usage
if self ~= mcl_minecarts then
dir = pos_
pos_ = self
end
local pos = vector.round(pos_)
-- diagonal direction handling
if dir.x ~= 0 and dir.z ~= 0 then
-- Check both possible diagonal movements
local dir_a = vector.new(dir.x,0,0)
local dir_b = vector.new(0,0,dir.z)
local new_dir_a = mcl_minecarts.get_rail_direction(pos, dir_a)
local new_dir_b = mcl_minecarts.get_rail_direction(pos, dir_b)
-- If either is the same diagonal direction, continue as you were
if vector.equals(dir,new_dir_a) or vector.equals(dir,new_dir_b) then
return dir
-- Otherwise, if either would try to move in the same direction as
-- what tried, move that direction
elseif vector.equals(dir_a, new_dir_a) then
return new_dir_a
elseif vector.equals(dir_b, new_dir_b) then
return new_dir_b
end
-- And if none of these were true, fall thru into standard behavior
end
local new_dir = get_rail_direction_inner(pos, dir)
-- Check four 45 degree movement
local next_rails_dir = get_rail_direction_inner(vector.add(pos, new_dir), new_dir)
if vector.equals(next_rails_dir, dir) and not vector.equals(new_dir, next_rails_dir) then
return vector.add(new_dir, next_rails_dir)
else
return new_dir
end
end
function mod.update_cart_orientation(self)
local staticdata = self._staticdata
-- constants
local _2_pi = math.pi * 2
local pi = math.pi
local dir = staticdata.dir
-- Calculate an angle from the x,z direction components
local rot_y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
if rot_y < 0 then
rot_y = rot_y + _2_pi
end
-- Check if the rotation is a 180 flip and don't change if so
local rot = self.object:get_rotation()
if not rot then return end
local diff = math.abs((rot_y - ( rot.y + pi ) % _2_pi) )
if diff < 0.001 or diff > _2_pi - 0.001 then
-- Update rotation adjust and recalculate the rotation
staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi
rot.y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
else
rot.y = rot_y
end
-- Forward/backwards tilt (pitch)
if dir.y < 0 then
rot.x = -0.25 * pi
elseif dir.y > 0 then
rot.x = 0.25 * pi
else
rot.x = 0
end
if ( staticdata.rot_adjust or 0 ) < 0.01 then
rot.x = -rot.x
end
if dir.z ~= 0 then
rot.x = -rot.x
end
self.object:set_rotation(rot)
end
function mod.get_cart_position(cart_staticdata)
local data = cart_staticdata
if not data then return nil end
if not data.connected_at then return nil end
return vector.add(data.connected_at, vector.multiply(data.dir or vector.zero(), data.distance or 0))
end
function mod.reverse_cart_direction(staticdata)
-- Complete moving thru this block into the next, reverse direction, and put us back at the same position we were at
local next_dir = -staticdata.dir
if not staticdata.connected_at then return end
staticdata.connected_at = staticdata.connected_at + staticdata.dir
staticdata.distance = 1 - (staticdata.distance or 0)
-- recalculate direction
local next_dir,_ = mod:get_rail_direction(staticdata.connected_at, next_dir)
staticdata.dir = next_dir
end

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
name = mcl_minecarts
author = Krock
description = Minecarts are vehicles to move players quickly on rails.
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
optional_depends = doc_identifier, mcl_wip
depends = mcl_title, mcl_explosions, mcl_core, mcl_util, mcl_sounds, mcl_player, mcl_playerinfo, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs, vl_legacy
optional_depends = doc_identifier, mcl_wip, mcl_physics, vl_physics

View File

@ -0,0 +1,15 @@
# hand-made Wavefront .OBJ file for sloped rail
mtllib mcl_minecarts_rail.mtl
o flat_track.001
v -0.500000 -0.437500 -0.500000
v -0.500000 -0.437500 0.500000
v 0.500000 -0.437500 0.500000
v 0.500000 -0.437500 -0.500000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vn 0.000000 1.000000 0.000000
usemtl None
s off
f 1/1/1 2/2/1 3/3/1 4/4/1

View File

@ -0,0 +1,15 @@
# hand-made Wavefront .OBJ file for sloped rail
mtllib mcl_minecarts_rail.mtl
o sloped_rail.001
v -0.500000 -0.437500 -0.500000
v -0.500000 0.562500 0.500000
v 0.500000 0.562500 0.500000
v 0.500000 -0.437500 -0.500000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vn 0.707106 0.707106 0.000000
usemtl None
s off
f 1/1/1 2/2/1 3/3/1 4/4/1

View File

@ -0,0 +1,579 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
-- Constants
local mcl_debug,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecart_debug", "Minecart Debug")
--DEBUG = false
--mcl_debug = function(msg) print(msg) end
-- Imports
local env_physics
if minetest.get_modpath("mcl_physics") then
env_physics = mcl_physics
elseif minetest.get_modpath("vl_physics") then
env_physics = vl_physics
end
local FRICTION = mcl_minecarts.FRICTION
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
local SPEED_MAX = mod.SPEED_MAX
local train_length = mod.train_length
local update_train = mod.update_train
local reverse_train = mod.reverse_train
local link_cart_ahead = mod.link_cart_ahead
local update_cart_orientation = mod.update_cart_orientation
local get_cart_data = mod.get_cart_data
local get_cart_position = mod.get_cart_position
local function reverse_direction(staticdata)
if staticdata.behind or staticdata.ahead then
reverse_train(staticdata)
return
end
mod.reverse_cart_direction(staticdata)
end
mod.reverse_direction = reverse_direction
--[[
Array of hooks { {u,v,w}, name }
Actual position is pos + u * dir + v * right + w * up
]]
local enter_exit_checks = {
{ 0, 0, 0, "" },
{ 0, 0, 1, "_above" },
{ 0, 0,-1, "_below" },
{ 0, 1, 0, "_side" },
{ 0,-1, 0, "_side" },
}
local function handle_cart_enter_exit(staticdata, pos, next_dir, event)
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
local dir = staticdata.dir
local right = vector.new( dir.z, dir.y, -dir.x)
local up = vector.new(0,1,0)
for i=1,#enter_exit_checks do
local check = enter_exit_checks[i]
local check_pos = pos + dir * check[1] + right * check[2] + up * check[3]
local node = minetest.get_node(check_pos)
local node_def = minetest.registered_nodes[node.name]
if node_def then
-- node-specific hook
local hook_name = "_mcl_minecarts_"..event..check[4]
local hook = node_def[hook_name]
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata) end
-- global minecart hook
hook = mcl_minecarts[event..check[4]]
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata, node_def) end
end
end
-- Handle cart-specific behaviors
if luaentity then
local hook = luaentity["_mcl_minecarts_"..event]
if hook then hook(luaentity, pos, staticdata) end
else
--minetest.log("warning", "TODO: change _mcl_minecarts_"..event.." calling so it is not dependent on the existence of a luaentity")
end
end
local function set_metadata_cart_status(pos, uuid, state)
local meta = minetest.get_meta(pos)
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
carts[uuid] = state
meta:set_string("_mcl_minecarts_carts", minetest.serialize(carts))
end
local function handle_cart_enter(staticdata, pos, next_dir)
--print("entering "..tostring(pos))
set_metadata_cart_status(pos, staticdata.uuid, 1)
handle_cart_enter_exit(staticdata, pos, next_dir, "on_enter" )
end
local function handle_cart_leave(staticdata, pos, next_dir)
--print("leaving "..tostring(pos))
set_metadata_cart_status(pos, staticdata.uuid, nil)
handle_cart_enter_exit(staticdata, pos, next_dir, "on_leave" )
end
local function handle_cart_node_watches(staticdata, dtime)
local watches = staticdata.node_watches or {}
local new_watches = {}
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
for i=1,#watches do
local node_pos = watches[i]
local node = minetest.get_node(node_pos)
local node_def = minetest.registered_nodes[node.name]
if node_def then
local hook = node_def._mcl_minecarts_node_on_step
if hook and hook(node_pos, luaentity, dtime, staticdata) then
new_watches[#new_watches+1] = node_pos
end
end
end
staticdata.node_watches = new_watches
end
local function detach_minecart(staticdata)
handle_cart_leave(staticdata, staticdata.connected_at, staticdata.dir)
staticdata.connected_at = nil
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
if luaentity then
luaentity.object:set_velocity(staticdata.dir * staticdata.velocity)
end
end
mod.detach_minecart = detach_minecart
local function try_detach_minecart(staticdata)
if not staticdata or not staticdata.connected_at then return end
if not mod:is_rail(staticdata.connected_at) then
mcl_debug("Detaching minecart #"..tostring(staticdata.uuid))
detach_minecart(staticdata)
end
end
local function handle_cart_collision(cart1_staticdata, prev_pos, next_dir)
if not cart1_staticdata then return end
-- Look ahead one block
local pos = vector.add(prev_pos, next_dir)
local meta = minetest.get_meta(pos)
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
local cart_uuid = nil
local dirty = false
for uuid,v in pairs(carts) do
-- Clean up dead carts
local data = get_cart_data(uuid)
if not data or not data.connected_at then
carts[uuid] = nil
dirty = true
uuid = nil
end
if uuid and uuid ~= cart1_staticdata.uuid then cart_uuid = uuid end
end
if dirty then
meta:set_string("_mcl_minecarts_carts",minetest.serialize(carts))
end
local meta = minetest.get_meta(vector.add(pos,next_dir))
if not cart_uuid then return end
-- Don't collide with the train car in front of you
if cart1_staticdata.ahead == cart_uuid then return end
--minetest.log("action","cart #"..cart1_staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos))
-- Standard Collision Handling
local cart2_staticdata = get_cart_data(cart_uuid)
local u1 = cart1_staticdata.velocity
local u2 = cart2_staticdata.velocity
local m1 = cart1_staticdata.mass
local m2 = cart2_staticdata.mass
--print("u1="..tostring(u1)..",u2="..tostring(u2))
if u2 == 0 and u1 < 4 and train_length(cart1_staticdata) < MAX_TRAIN_LENGTH then
link_cart_ahead(cart1_staticdata, cart2_staticdata)
cart2_staticdata.dir = mcl_minecarts.get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
cart2_staticdata.velocity = cart1_staticdata.velocity
return
end
-- Calculate new velocities according to https://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian
local c1 = m1 + m2
local d = m1 - m2
local v1 = ( d * u1 + 2 * m2 * u2 ) / c1
local v2 = ( 2 * m1 * u1 + d * u2 ) / c1
cart1_staticdata.velocity = v1
cart2_staticdata.velocity = v2
-- Force the other cart to move the same direction this one was
cart2_staticdata.dir = mcl_minecarts.get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
end
local function vector_away_from_players(cart, staticdata)
local function player_repel(obj)
-- Only repel from players
local player_name = obj:get_player_name()
if not player_name or player_name == "" then return false end
-- Don't repel away from players in minecarts
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
if player_meta.attached_to then return false end
return true
end
-- Get the cart position
local cart_pos = mod.get_cart_position(staticdata)
if cart then cart_pos = cart.object:get_pos() end
if not cart_pos then return nil end
for _,obj in pairs(minetest.get_objects_inside_radius(cart_pos, 1.1)) do
if player_repel(obj) then
return obj:get_pos() - cart_pos
end
end
return nil
end
local function direction_away_from_players(staticdata)
local diff = vector_away_from_players(nil,staticdata)
if not diff then return 0 end
local length = vector.distance(vector.zero(),diff)
local vec = diff / length
local force = vector.dot( vec, vector.normalize(staticdata.dir) )
-- Check if this would push past the end of the track and don't move it it would
-- This prevents an oscillation that would otherwise occur
local dir = staticdata.dir
if force > 0 then
dir = -dir
end
if mcl_minecarts.is_rail( staticdata.connected_at + dir ) then
if force > 0.5 then
return -length * 4
elseif force < -0.5 then
return length * 4
end
end
return 0
end
local function calculate_acceleration(staticdata)
local acceleration = 0
-- Fix up movement data
staticdata.velocity = staticdata.velocity or 0
-- Apply friction if moving
if staticdata.velocity > 0 then
acceleration = -FRICTION
end
local pos = staticdata.connected_at
local node_name = minetest.get_node(pos).name
local node_def = minetest.registered_nodes[node_name]
local ctrl = staticdata.controls or {}
local time_active = minetest.get_gametime() - 0.25
if (ctrl.forward or 0) > time_active then
if staticdata.velocity == 0 then
local look_dir = minetest.facedir_to_dir(ctrl.look or 0)
local dot = vector.dot(staticdata.dir, look_dir)
if dot < 0 then
reverse_direction(staticdata)
end
end
acceleration = 4
elseif (ctrl.brake or 0) > time_active then
acceleration = -1.5
elseif ctrl.impulse then
acceleration = vector.dot(staticdata.dir, ctrl.impulse)
ctrl.impulse = nil
elseif (staticdata.fueltime or 0) > 0 and staticdata.velocity <= 4 then
acceleration = 0.6
elseif staticdata.velocity >= ( node_def._max_acceleration_velocity or SPEED_MAX ) then
-- Standard friction
elseif node_def and node_def._rail_acceleration then
acceleration = node_def._rail_acceleration * 4
end
-- Factor in gravity after everything else
local gravity_strength = 2.45 --friction * 5
if staticdata.dir.y < 0 then
acceleration = gravity_strength - FRICTION
elseif staticdata.dir.y > 0 then
acceleration = -gravity_strength + FRICTION
end
return acceleration
end
local function do_movement_step(staticdata, dtime)
if not staticdata.connected_at then return 0 end
-- Calculate timestep remaiing in this block
local x_0 = staticdata.distance or 0
local remaining_in_block = 1 - x_0
-- Calculate acceleration
local v_0 = staticdata.velocity or 0
local a = 0
if staticdata.ahead or staticdata.behind then
-- Calculate acceleration of the entire train
local count = 0
for cart in mod.train_cars(staticdata) do
count = count + 1
if cart.behind then
a = a + calculate_acceleration(cart)
end
end
a = a / count
else
a = calculate_acceleration(staticdata)
end
-- Repel minecarts
local away = direction_away_from_players(staticdata)
if away > 0 then
v_0 = away
elseif away < 0 then
reverse_direction(staticdata)
v_0 = -away
end
if DEBUG and ( v_0 > 0 or a ~= 0 ) then
mcl_debug(" cart "..tostring(staticdata.uuid)..
": a="..tostring(a)..
",v_0="..tostring(v_0)..
",x_0="..tostring(x_0)..
",dtime="..tostring(dtime)..
",dir="..tostring(staticdata.dir)..
",connected_at="..tostring(staticdata.connected_at)..
",distance="..tostring(staticdata.distance)
)
end
-- Not moving
if a == 0 and v_0 == 0 then return 0 end
-- Movement equation with acceleration: x_1 = x_0 + v_0 * t + 0.5 * a * t*t
local timestep
local stops_in_block = false
local inside = v_0 * v_0 + 2 * a * remaining_in_block
if inside < 0 then
-- Would stop or reverse direction inside this block, calculate time to v_1 = 0
timestep = -v_0 / a
stops_in_block = true
elseif a ~= 0 then
-- Setting x_1 = x_0 + remaining_in_block, and solving for t gives:
timestep = ( math.sqrt( v_0 * v_0 + 2 * a * remaining_in_block) - v_0 ) / a
else
timestep = remaining_in_block / v_0
end
-- Truncate timestep to remaining time delta
if timestep > dtime then
timestep = dtime
end
-- Truncate timestep to prevent v_1 from being larger that speed_max
local v_max = SPEED_MAX
if (v_0 < v_max) and ( v_0 + a * timestep > v_max) then
timestep = ( v_max - v_0 ) / a
end
-- Prevent infinite loops
if timestep <= 0 then return 0 end
-- Calculate v_1 taking v_max into account
local v_1 = v_0 + a * timestep
if v_1 > v_max then
v_1 = v_max
elseif v_1 < FRICTION / 5 then
v_1 = 0
end
-- Calculate x_1
local x_1 = x_0 + (timestep * v_0 + 0.5 * a * timestep * timestep) / vector.length(staticdata.dir)
-- Update position and velocity of the minecart
staticdata.velocity = v_1
staticdata.distance = x_1
if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then
mcl_debug( "- cart #"..tostring(staticdata.uuid)..
": a="..tostring(a)..
",v_0="..tostring(v_0)..
",v_1="..tostring(v_1)..
",x_0="..tostring(x_0)..
",x_1="..tostring(x_1)..
",timestep="..tostring(timestep)..
",dir="..tostring(staticdata.dir)..
",connected_at="..tostring(staticdata.connected_at)..
",distance="..tostring(staticdata.distance)
)
end
-- Entity movement
local pos = staticdata.connected_at
-- Handle movement to next block, account for loss of precision in calculations
if x_1 >= 0.99 then
staticdata.distance = 0
-- Anchor at the next node
local old_pos = pos
pos = pos + staticdata.dir
staticdata.connected_at = pos
-- Get the next direction
local next_dir,_ = mcl_minecarts.get_rail_direction(pos, staticdata.dir, nil, nil, staticdata.railtype)
if DEBUG and next_dir ~= staticdata.dir then
mcl_debug( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir))
end
-- Handle cart collisions
handle_cart_collision(staticdata, pos, next_dir)
-- Leave the old node
handle_cart_leave(staticdata, old_pos, next_dir )
-- Enter the new node
handle_cart_enter(staticdata, pos, next_dir)
-- Handle end of track
if next_dir == staticdata.dir * -1 and next_dir.y == 0 then
if DEBUG then mcl_debug("Stopping cart at end of track at "..tostring(pos)) end
staticdata.velocity = 0
end
-- Update cart direction
staticdata.dir = next_dir
elseif stops_in_block and v_1 < (FRICTION/5) and a <= 0 and staticdata.dir.y > 0 then
-- Handle direction flip due to gravity
if DEBUG then mcl_debug("Gravity flipped direction") end
-- Velocity should be zero at this point
staticdata.velocity = 0
reverse_direction(staticdata)
-- Intermediate movement
pos = staticdata.connected_at + staticdata.dir * staticdata.distance
else
-- Intermediate movement
pos = pos + staticdata.dir * staticdata.distance
end
-- Debug reporting
if DEBUG and ( v_0 > 0 or v_1 > 0 ) then
mcl_debug( " cart #"..tostring(staticdata.uuid)..
": a="..tostring(a)..
",v_0="..tostring(v_0)..
",v_1="..tostring(v_1)..
",x_0="..tostring(x_0)..
",x_1="..tostring(x_1)..
",timestep="..tostring(timestep)..
",dir="..tostring(staticdata.dir)..
",pos="..tostring(pos)..
",connected_at="..tostring(staticdata.connected_at)..
",distance="..tostring(staticdata.distance)
)
end
-- Report the amount of time processed
return dtime - timestep
end
local function do_movement( staticdata, dtime )
assert(staticdata)
-- Allow the carts to be delay for the rest of the world to react before moving again
--[[
if ( staticdata.delay or 0 ) > dtime then
staticdata.delay = staticdata.delay - dtime
return
else
staticdata.delay = 0
end]]
-- Break long movements at block boundaries to make it
-- it impossible to jump across gaps due to server lag
-- causing large timesteps
while dtime > 0 do
local new_dtime = do_movement_step(staticdata, dtime)
try_detach_minecart(staticdata)
update_train(staticdata)
-- Handle node watches here in steps to prevent server lag from changing behavior
handle_cart_node_watches(staticdata, dtime - new_dtime)
dtime = new_dtime
end
end
local function do_detached_movement(self, dtime)
local staticdata = self._staticdata
-- Make sure the object is still valid before trying to move it
if not self.object or not self.object:get_pos() then return end
-- Apply physics
if env_physics then
env_physics.apply_entity_environmental_physics(self)
else
-- Simple physics
local friction = self.object:get_velocity() or vector.zero()
friction.y = 0
local accel = vector.new(0,-9.81,0) -- gravity
-- Don't apply friction in the air
local pos_rounded = vector.round(self.object:get_pos())
if minetest.get_node(vector.offset(pos_rounded,0,-1,0)).name ~= "air" then
accel = vector.add(accel, vector.multiply(friction,-0.9))
end
self.object:set_acceleration(accel)
end
local away = vector_away_from_players(self, staticdata)
if away then
local v = self.object:get_velocity()
self.object:set_velocity(v - away)
-- Boost the minecart vertically a bit to get over the edge of rails and things like carpets
local boost = vector.offset(vector.multiply(vector.normalize(away), 0.1), 0, 0.07, 0) -- 1/16th + 0.0075
local pos = self.object:get_pos()
self.object:set_pos(vector.add(pos,boost))
end
-- Try to reconnect to rail
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local yaw_dir = minetest.yaw_to_dir(yaw)
local test_positions = {
pos,
vector.offset(vector.add(pos, vector.multiply(yaw_dir, 0.5)),0,-0.55,0),
vector.offset(vector.add(pos, vector.multiply(yaw_dir,-0.5)),0,-0.55,0),
}
for i=1,#test_positions do
local test_pos = test_positions[i]
local pos_r = vector.round(test_pos)
local node = minetest.get_node(pos_r)
if minetest.get_item_group(node.name, "rail") ~= 0 then
staticdata.connected_at = pos_r
staticdata.railtype = node.name
local freebody_velocity = self.object:get_velocity()
staticdata.dir = mod:get_rail_direction(pos_r, mod.snap_direction(freebody_velocity))
-- Use vector projection to only keep the velocity in the new direction of movement on the rail
-- https://en.wikipedia.org/wiki/Vector_projection
staticdata.velocity = vector.dot(staticdata.dir,freebody_velocity)
--print("Reattached velocity="..tostring(staticdata.velocity)..", freebody_velocity="..tostring(freebody_velocity))
-- Clear freebody movement
self.object:set_velocity(vector.zero())
self.object:set_acceleration(vector.zero())
return
end
end
end
--return do_movement, do_detatched_movement
return do_movement,do_detached_movement,handle_cart_enter

View File

@ -1,53 +1,349 @@
local S = minetest.get_translator(minetest.get_current_modname())
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
mod.RAIL_GROUPS = {
STANDARD = 1,
CURVES = 2,
}
-- Template rail function
local function register_rail(itemstring, tiles, def_extras, creative)
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=0,destroy_by_lava_flow=0, transport=1}
if creative == false then
groups.not_in_creative_inventory = 1
end
local ndef = {
drawtype = "raillike",
tiles = tiles,
is_ground_content = false,
inventory_image = tiles[1],
wield_image = tiles[1],
paramtype = "light",
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
stack_max = 64,
groups = groups,
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 0.7,
_mcl_hardness = 0.7,
after_destruct = function(pos)
-- Scan for minecarts in this pos and force them to execute their "floating" check.
-- Normally, this will make them drop.
local objs = minetest.get_objects_inside_radius(pos, 1)
for o=1, #objs do
local le = objs[o]:get_luaentity()
if le then
-- All entities in this mod are minecarts, so this works
if string.sub(le.name, 1, 14) == "mcl_minecarts:" then
le._last_float_check = mcl_minecarts.check_float_time
end
end
end
end,
}
if def_extras then
for k,v in pairs(def_extras) do
ndef[k] = v
-- Inport functions and constants from elsewhere
local table_merge = mcl_util.table_merge
local check_connection_rules = mod.check_connection_rules
local update_rail_connections = mod.update_rail_connections
local minetest_fourdir_to_dir = minetest.fourdir_to_dir
local minetest_dir_to_fourdir = minetest.dir_to_fourdir
local vector_offset = vector.offset
local vector_equals = vector.equals
local north = mod.north
local south = mod.south
local east = mod.east
local west = mod.west
--- Rail direction Handleres
local function rail_dir_straight(pos, dir, node)
dir = vector.new(dir)
dir.y = 0
if node.param2 == 0 or node.param2 == 2 then
if vector_equals(dir, north) then
return north
else
return south
end
else
if vector_equals(dir,east) then
return east
else
return west
end
end
end
local function rail_dir_sloped(pos, dir, node)
local uphill = minetest_fourdir_to_dir(node.param2)
local downhill = minetest_fourdir_to_dir((node.param2+2)%4)
local up_uphill = vector_offset(uphill,0,1,0)
if vector_equals(dir, uphill) or vector_equals(dir, up_uphill) then
return up_uphill
else
return downhill
end
end
-- Fourdir to cardinal direction
-- 0 = north
-- 1 = east
-- 2 = south
-- 3 = west
-- This takes a table `dirs` that has one element for each cardinal direction
-- and which specifies the direction for a cart to continue in when entering
-- a rail node in the direction of the cardinal. This function takes node
-- rotations into account.
local function rail_dir_from_table(pos, dir, node, dirs)
dir = vector.new(dir)
dir.y = 0
local dir_fourdir = (minetest_dir_to_fourdir(dir) - node.param2 + 4) % 4
local new_fourdir = (dirs[dir_fourdir] + node.param2) % 4
return minetest_fourdir_to_dir(new_fourdir)
end
local CURVE_RAIL_DIRS = { [0] = 1, 1, 2, 2, }
local function rail_dir_curve(pos, dir, node)
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end
local function rail_dir_tee_off(pos, dir, node)
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end
local TEE_RAIL_ON_DIRS = { [0] = 0, 1, 1, 0 }
local function rail_dir_tee_on(pos, dir, node)
return rail_dir_from_table(pos, dir, node, TEE_RAIL_ON_DIRS)
end
local function rail_dir_cross(pos, dir, node)
dir = vector.new(dir)
dir.y = 0
-- Always continue in the same direction. No direction changes allowed
return dir
end
-- Setup shared text
local railuse = S(
"Place them on the ground to build your railway, the rails will automatically connect to each other and will"..
" turn into curves, T-junctions, crossings and slopes as needed."
)
mod.text = mod.text or {}
mod.text.railuse = railuse
local BASE_DEF = {
drawtype = "mesh",
mesh = "flat_track.obj",
paramtype = "light",
paramtype2 = "4dir",
stack_max = 64,
sounds = mcl_sounds.node_sound_metal_defaults(),
is_ground_content = true,
paramtype = "light",
use_texture_alpha = "clip",
collision_box = {
type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/15 }
},
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {
handy=1, pickaxey=1,
attached_node=1,
rail=1,
connect_to_raillike=minetest.raillike_group("rail"),
dig_by_water=0,destroy_by_lava_flow=0,
transport=1
},
description = S("New Rail"), -- Temporary name to make debugging easier
_tt_help = S("Track for minecarts"),
_doc_items_usagehelp = railuse,
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
on_place = function(itemstack, placer, pointed_thing)
local node_name = minetest.get_node(pointed_thing.under).name
-- Don't allow placing rail above rail
if minetest.get_item_group(node_name,"rail") == 0 then
return minetest.item_place_node(itemstack, placer, pointed_thing)
else
return itemstack
end
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
update_rail_connections(pos)
end,
_mcl_minecarts = {
get_next_dir = rail_dir_straight,
},
_mcl_blast_resistance = 0.7,
_mcl_hardness = 0.7,
}
local SLOPED_RAIL_DEF = table.copy(BASE_DEF)
table_merge(SLOPED_RAIL_DEF,{
drawtype = "mesh",
mesh = "sloped_track.obj",
groups = {
rail_slope = 1,
not_in_creative_inventory = 1,
},
collision_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
}
},
selection_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
}
},
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
},
})
function mod.register_rail(itemstring, ndef)
assert(ndef.tiles)
-- Extract out the craft recipe
local craft = ndef.craft
ndef.craft = nil
-- Add sensible defaults
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
--print("registering rail "..itemstring.." with definition: "..dump(ndef))
-- Make registrations
minetest.register_node(itemstring, ndef)
if craft then minetest.register_craft(craft) end
end
function mod.register_straight_rail(base_name, tiles, def)
def = def or {}
local base_def = table.copy(BASE_DEF)
local sloped_def = table.copy(SLOPED_RAIL_DEF)
local add = {
tiles = { tiles[1] },
drop = base_name,
groups = {
rail = mod.RAIL_GROUPS.STANDARD,
},
_mcl_minecarts = {
base_name = base_name,
can_slope = true,
},
}
table_merge(base_def, add); table_merge(sloped_def, add)
table_merge(base_def, def); table_merge(sloped_def, def)
-- Register the base node
mod.register_rail(base_name, base_def)
base_def.craft = nil; sloped_def.craft = nil
table_merge(base_def,{
_mcl_minecarts = {
railtype = "straight",
},
})
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
},
mesecons = def.mesecons_sloped,
tiles = { tiles[1] },
_mcl_minecarts = {
railtype = "sloped",
},
}))
end
function mod.register_curves_rail(base_name, tiles, def)
def = def or {}
local base_def = table.copy(BASE_DEF)
local sloped_def = table.copy(SLOPED_RAIL_DEF)
local add = {
_mcl_minecarts = { base_name = base_name },
groups = {
rail = mod.RAIL_GROUPS.CURVES
},
drop = base_name,
}
table_merge(base_def, add); table_merge(sloped_def, add)
table_merge(base_def, def); table_merge(sloped_def, def)
-- Register the base node
mod.register_rail(base_name, table_merge(table.copy(base_def),{
tiles = { tiles[1] },
_mcl_minecarts = {
get_next_dir = rail_dir_straight,
railtype = "straight",
can_slope = true,
},
}))
-- Update for other variants
base_def.craft = nil
table_merge(base_def, {
groups = {
not_in_creative_inventory = 1
}
})
-- Corner variants
mod.register_rail(base_name.."_corner", table_merge(table.copy(base_def),{
tiles = { tiles[2] },
_mcl_minecarts = {
get_next_dir = rail_dir_curve,
railtype = "corner",
},
}))
-- Tee variants
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
tiles = { tiles[3] },
mesecons = {
effector = {
action_on = function(pos, node)
local new_node = {name = base_name.."_tee_on", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
},
_mcl_minecarts = {
get_next_dir = rail_dir_tee_off,
railtype = "tee",
},
}))
mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
tiles = { tiles[4] },
_mcl_minecarts = {
get_next_dir = rail_dir_tee_on,
railtype = "tee",
},
mesecons = {
effector = {
action_off = function(pos, node)
local new_node = {name = base_name.."_tee_off", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
}
}))
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
description = S("Sloped Rail"), -- Temporary name to make debugging easier
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
railtype = "tee",
},
tiles = { tiles[1] },
}))
-- Cross variant
mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
tiles = { tiles[5] },
_mcl_minecarts = {
get_next_dir = rail_dir_cross,
railtype = "cross",
},
}))
end
function mod.register_rail_sloped(itemstring, def)
assert(def.tiles)
-- Build the node definition
local ndef = table.copy(SLOPED_RAIL_DEF)
table_merge(ndef, def)
-- Add sensible defaults
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))
-- Make registrations
minetest.register_node(itemstring, ndef)
end
-- Redstone rules
local rail_rules_long =
mod.rail_rules_long =
{{x=-1, y= 0, z= 0, spread=true},
{x= 1, y= 0, z= 0, spread=true},
{x= 0, y=-1, z= 0, spread=true},
@ -64,202 +360,73 @@ local rail_rules_long =
{x= 0, y= 1, z=-1},
{x= 0, y=-1, z=-1}}
local rail_rules_short = mesecon.rules.pplate
local railuse = S("Place them on the ground to build your railway, the rails will automatically connect to each other and will turn into curves, T-junctions, crossings and slopes as needed.")
-- Normal rail
register_rail("mcl_minecarts:rail",
{"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
{
description = S("Rail"),
_tt_help = S("Track for minecarts"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
_doc_items_usagehelp = railuse,
}
)
-- Powered rail (off = brake mode)
register_rail("mcl_minecarts:golden_rail",
{"mcl_minecarts_rail_golden.png", "mcl_minecarts_rail_golden_curved.png", "mcl_minecarts_rail_golden_t_junction.png", "mcl_minecarts_rail_golden_crossing.png"},
{
description = S("Powered Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
_doc_items_usagehelp = railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power."),
_rail_acceleration = -3,
mesecons = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:golden_rail",
onstate = "mcl_minecarts:golden_rail_on",
rules = rail_rules_long,
},
},
}
)
-- Powered rail (on = acceleration mode)
register_rail("mcl_minecarts:golden_rail_on",
{"mcl_minecarts_rail_golden_powered.png", "mcl_minecarts_rail_golden_curved_powered.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"},
{
_doc_items_create_entry = false,
_rail_acceleration = 4,
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:golden_rail",
onstate = "mcl_minecarts:golden_rail_on",
rules = rail_rules_long,
},
effector = {
action_on = function(pos, node)
local dir = mcl_minecarts:get_start_direction(pos)
if not dir then return end
local objs = minetest.get_objects_inside_radius(pos, 1)
for _, o in pairs(objs) do
local l = o:get_luaentity()
local v = o:get_velocity()
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:"
and v and vector.equals(v, vector.zero())
then
mcl_minecarts:set_velocity(l, dir)
end
end
end,
},
},
drop = "mcl_minecarts:golden_rail",
},
false
)
-- Activator rail (off)
register_rail("mcl_minecarts:activator_rail",
{"mcl_minecarts_rail_activator.png", "mcl_minecarts_rail_activator_curved.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"},
{
description = S("Activator Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
_doc_items_usagehelp = railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
mesecons = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:activator_rail",
onstate = "mcl_minecarts:activator_rail_on",
rules = rail_rules_long,
},
},
}
)
-- Activator rail (on)
register_rail("mcl_minecarts:activator_rail_on",
{"mcl_minecarts_rail_activator_powered.png", "mcl_minecarts_rail_activator_curved_powered.png", "mcl_minecarts_rail_activator_t_junction_powered.png", "mcl_minecarts_rail_activator_crossing_powered.png"},
{
_doc_items_create_entry = false,
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:activator_rail",
onstate = "mcl_minecarts:activator_rail_on",
rules = rail_rules_long,
},
effector = {
-- Activate minecarts
action_on = function(pos, node)
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
local objs = minetest.get_objects_inside_radius(pos2, 1)
for _, o in pairs(objs) do
local l = o:get_luaentity()
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
l:on_activate_by_rail()
end
end
end,
},
},
drop = "mcl_minecarts:activator_rail",
},
false
)
-- Detector rail (off)
register_rail("mcl_minecarts:detector_rail",
{"mcl_minecarts_rail_detector.png", "mcl_minecarts_rail_detector_curved.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"},
{
description = S("Detector Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
_doc_items_usagehelp = railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
mesecons = {
receptor = {
state = mesecon.state.off,
rules = rail_rules_short,
},
},
}
)
-- Detector rail (on)
register_rail("mcl_minecarts:detector_rail_on",
{"mcl_minecarts_rail_detector_powered.png", "mcl_minecarts_rail_detector_curved_powered.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"},
{
_doc_items_create_entry = false,
mesecons = {
receptor = {
state = mesecon.state.on,
rules = rail_rules_short,
},
},
drop = "mcl_minecarts:detector_rail",
},
false
)
-- Crafting
minetest.register_craft({
output = "mcl_minecarts:rail 16",
recipe = {
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
}
})
minetest.register_craft({
output = "mcl_minecarts:golden_rail 6",
recipe = {
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
}
})
minetest.register_craft({
output = "mcl_minecarts:activator_rail 6",
recipe = {
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
}
})
minetest.register_craft({
output = "mcl_minecarts:detector_rail 6",
recipe = {
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
}
})
dofile(modpath.."/rails/normal.lua")
dofile(modpath.."/rails/activator.lua")
dofile(modpath.."/rails/detector.lua")
dofile(modpath.."/rails/powered.lua")
-- Aliases
if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
end
local CURVY_RAILS_MAP = {
["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
}
local function convert_legacy_curvy_rails(pos, node)
node.name = CURVY_RAILS_MAP[node.name]
if node.name then
minetest.swap_node(pos, node)
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
end
end
for old,new in pairs(CURVY_RAILS_MAP) do
local new_def = minetest.registered_nodes[new]
minetest.register_node(old, {
drawtype = "raillike",
inventory_image = new_def.inventory_image,
groups = { rail = 1, legacy = 1 },
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
_vl_legacy_convert_node = convert_legacy_curvy_rails
})
vl_legacy.register_item_conversion(old, new)
end
local STRAIGHT_RAILS_MAP ={
["mcl_minecarts:golden_rail"] = "mcl_minecarts:golden_rail_v2",
["mcl_minecarts:golden_rail_on"] = "mcl_minecarts:golden_rail_v2_on",
["mcl_minecarts:activator_rail"] = "mcl_minecarts:activator_rail_v2",
["mcl_minecarts:activator_rail_on"] = "mcl_minecarts:activator_rail_v2_on",
["mcl_minecarts:detector_rail"] = "mcl_minecarts:detector_rail_v2",
["mcl_minecarts:detector_rail_on"] = "mcl_minecarts:detector_rail_v2_on",
}
local function convert_legacy_straight_rail(pos, node)
node.name = STRAIGHT_RAILS_MAP[node.name]
if node.name then
local connections = mod.get_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
if not mod.HORIZONTAL_STANDARD_RULES[connections] then
-- Drop an immortal object at this location
local item_entity = minetest.add_item(pos, ItemStack(node.name))
if item_entity then
item_entity:get_luaentity()._immortal = true
end
-- This is a configuration that doesn't exist in the new rail
-- Replace with a standard rail
node.name = "mcl_minecarts:rail_v2"
end
minetest.swap_node(pos, node)
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
end
end
for old,new in pairs(STRAIGHT_RAILS_MAP) do
local new_def = minetest.registered_nodes[new]
minetest.register_node(old, {
drawtype = "raillike",
inventory_image = new_def.inventory_image,
groups = { rail = 1, legacy = 1 },
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
_vl_legacy_convert_node = convert_legacy_straight_rail,
})
vl_legacy.register_item_conversion(old, new)
end

View File

@ -0,0 +1,87 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
-- Activator rail (off)
mod.register_straight_rail("mcl_minecarts:activator_rail_v2", {"mcl_minecarts_rail_activator.png"},{
description = S("Activator Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
mesecons = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:activator_rail_v2",
onstate = "mcl_minecarts:activator_rail_v2_on",
rules = mod.rail_rules_long,
},
},
mesecons_sloped = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:activator_rail_v2_sloped",
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
rules = mod.rail_rules_long,
},
},
craft = {
output = "mcl_minecarts:activator_rail_v2 6",
recipe = {
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
}
},
})
-- Activator rail (on)
local function activator_rail_action_on(pos, node)
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
local objs = minetest.get_objects_inside_radius(pos2, 1)
for _, o in pairs(objs) do
local l = o:get_luaentity()
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
l:on_activate_by_rail()
end
end
end
mod.register_straight_rail("mcl_minecarts:activator_rail_v2_on", {"mcl_minecarts_rail_activator_powered.png"},{
_doc_items_create_entry = false,
groups = {
not_in_creative_inventory = 1,
},
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:activator_rail_v2",
onstate = "mcl_minecarts:activator_rail_v2_on",
rules = mod.rail_rules_long,
},
effector = {
-- Activate minecarts
action_on = activator_rail_action_on,
},
},
mesecons_sloped = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:activator_rail_v2_sloped",
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
rules = mod.rail_rules_long,
},
effector = {
-- Activate minecarts
action_on = activator_rail_action_on,
},
},
_mcl_minecarts_on_enter = function(pos, cart)
if cart.on_activate_by_rail then
cart:on_activate_by_rail()
end
end,
drop = "mcl_minecarts:activator_rail_v2",
})

View File

@ -0,0 +1,64 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local rail_rules_short = mesecon.rules.pplate
-- Detector rail (off)
mod.register_straight_rail("mcl_minecarts:detector_rail_v2",{"mcl_minecarts_rail_detector.png"},{
description = S("Detector Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
mesecons = {
receptor = {
state = mesecon.state.off,
rules = rail_rules_short,
},
},
_mcl_minecarts_on_enter = function(pos, cart)
local node = minetest.get_node(pos)
local newnode = {
name = "mcl_minecarts:detector_rail_v2_on",
param2 = node.param2
}
minetest.swap_node( pos, newnode )
mesecon.receptor_on(pos)
end,
craft = {
output = "mcl_minecarts:detector_rail_v2 6",
recipe = {
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
}
}
})
-- Detector rail (on)
mod.register_straight_rail("mcl_minecarts:detector_rail_v2_on",{"mcl_minecarts_rail_detector_powered.png"},{
groups = {
not_in_creative_inventory = 1,
},
_doc_items_create_entry = false,
mesecons = {
receptor = {
state = mesecon.state.on,
rules = rail_rules_short,
},
},
_mcl_minecarts_on_leave = function(pos, cart)
local node = minetest.get_node(pos)
local newnode = {
name = "mcl_minecarts:detector_rail",
param2 = node.param2
}
minetest.swap_node( pos, newnode )
mesecon.receptor_off(pos)
end,
drop = "mcl_minecarts:detector_rail_v2",
})

View File

@ -0,0 +1,27 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
-- Normal rail
mod.register_curves_rail("mcl_minecarts:rail_v2", {
"default_rail.png",
"default_rail_curved.png",
"default_rail_t_junction.png",
"default_rail_t_junction_on.png",
"default_rail_crossing.png"
},{
description = S("Rail"),
_tt_help = S("Track for minecarts"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
_doc_items_usagehelp = mod.text.railuse,
craft = {
output = "mcl_minecarts:rail_v2 16",
recipe = {
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
}
},
})

View File

@ -0,0 +1,69 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
-- Powered rail (off = brake mode)
mod.register_straight_rail("mcl_minecarts:golden_rail_v2",{ "mcl_minecarts_rail_golden.png" },{
description = S("Powered Rail"),
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
_doc_items_usagehelp = mod.text.railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate"..
" minecarts, power it with redstone power."),
_doc_items_create_entry = false,
_rail_acceleration = -3,
_max_acceleration_velocity = 8,
mesecons = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:golden_rail_v2",
onstate = "mcl_minecarts:golden_rail_v2_on",
rules = mod.rail_rules_long,
},
},
mesecons_sloped = {
conductor = {
state = mesecon.state.off,
offstate = "mcl_minecarts:golden_rail_v2_sloepd",
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
rules = mod.rail_rules_long,
},
},
drop = "mcl_minecarts:golden_rail_v2",
craft = {
output = "mcl_minecarts:golden_rail_v2 6",
recipe = {
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
}
}
})
-- Powered rail (on = acceleration mode)
mod.register_straight_rail("mcl_minecarts:golden_rail_v2_on",{ "mcl_minecarts_rail_golden_powered.png" },{
_doc_items_create_entry = false,
_rail_acceleration = 4,
_max_acceleration_velocity = 8,
groups = {
not_in_creative_inventory = 1,
},
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:golden_rail_v2",
onstate = "mcl_minecarts:golden_rail_v2_on",
rules = mod.rail_rules_long,
},
},
mesecons_sloped = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:golden_rail_v2_sloped",
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
rules = mod.rail_rules_long,
},
},
drop = "mcl_minecarts:golden_rail_v2",
})

View File

@ -0,0 +1,92 @@
local storage = minetest.get_mod_storage()
local mod = mcl_minecarts
-- Imports
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
assert(CART_BLOCK_SIZE)
local cart_data = {}
local cart_data_fail_cache = {}
local cart_ids = storage:get_keys()
local function get_cart_data(uuid)
if cart_data[uuid] then return cart_data[uuid] end
if cart_data_fail_cache[uuid] then return nil end
local data = minetest.deserialize(storage:get_string("cart-"..uuid))
if not data then
cart_data_fail_cache[uuid] = true
return nil
else
-- Repair broken data
if not data.distance then data.distance = 0 end
if data.distance == 0/0 then data.distance = 0 end
if data.distance == -0/0 then data.distance = 0 end
data.dir = vector.new(data.dir)
data.connected_at = vector.new(data.connected_at)
end
cart_data[uuid] = data
return data
end
mod.get_cart_data = get_cart_data
-- Preload all cart data into memory
for _,id in pairs(cart_ids) do
local uuid = string.sub(id,6)
get_cart_data(uuid)
end
local function save_cart_data(uuid)
if not cart_data[uuid] then return end
storage:set_string("cart-"..uuid,minetest.serialize(cart_data[uuid]))
end
mod.save_cart_data = save_cart_data
function mod.update_cart_data(data)
local uuid = data.uuid
cart_data[uuid] = data
cart_data_fail_cache[uuid] = nil
save_cart_data(uuid)
end
function mod.destroy_cart_data(uuid)
storage:set_string("cart-"..uuid,"")
cart_data[uuid] = nil
cart_data_fail_cache[uuid] = true
end
function mod.carts()
return pairs(cart_data)
end
function mod.find_carts_by_block_map(block_map)
local cart_list = {}
for _,data in pairs(cart_data) do
if data and data.connected_at then
local pos = mod.get_cart_position(data)
local block = vector.floor(vector.divide(pos,CART_BLOCK_SIZE))
if block_map[vector.to_string(block)] then
cart_list[#cart_list + 1] = data
end
end
end
return cart_list
end
function mod.add_blocks_to_map(block_map, min_pos, max_pos)
local min = vector.floor(vector.divide(min_pos, CART_BLOCK_SIZE))
local max = vector.floor(vector.divide(max_pos, CART_BLOCK_SIZE)) + vector.new(1,1,1)
for z = min.z,max.z do
for y = min.y,max.y do
for x = min.x,max.x do
block_map[ vector.to_string(vector.new(x,y,z)) ] = true
end
end
end
end
minetest.register_on_shutdown(function()
for uuid,_ in pairs(cart_data) do
save_cart_data(uuid)
end
end)

View File

@ -0,0 +1,147 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
-- Imports
local get_cart_data = mod.get_cart_data
local save_cart_data = mod.save_cart_data
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
-- Follow .behind to the back end of a train
local function find_back(start)
assert(start)
while start.behind do
local nxt = get_cart_data(start.behind)
if not nxt then return start end
start = nxt
end
return start
end
-- Iterate across all the cars in a train
function mod.train_cars(staticdata)
assert(staticdata)
local back = find_back(staticdata)
local limit = MAX_TRAIN_LENGTH
return function()
if not back or limit <= 0 then return end
limit = limit - 1
local ret = back
if back.ahead then
back = get_cart_data(back.ahead)
else
back = nil
end
return ret
end
end
local train_cars = mod.train_cars
function mod.train_length(cart)
local count = 0
for cart in train_cars(cart) do
count = count + 1
end
return count
end
function mod.is_in_same_train(anchor, other)
for cart in train_cars(anchor) do
if cart.uuid == other.uuid then return true end
end
return false
end
function mod.distance_between_cars(car1, car2)
if not car1.connected_at then return nil end
if not car2.connected_at then return nil end
if not car1.dir then car1.dir = vector.zero() end
if not car2.dir then car2.dir = vector.zero() end
local pos1 = vector.add(car1.connected_at, vector.multiply(car1.dir, car1.distance))
local pos2 = vector.add(car2.connected_at, vector.multiply(car2.dir, car2.distance))
return vector.distance(pos1, pos2)
end
local distance_between_cars = mod.distance_between_cars
local function break_train_at(cart)
if cart.ahead then
local ahead = get_cart_data(cart.ahead)
if ahead then
ahead.behind = nil
cart.ahead = nil
save_cart_data(ahead.uuid)
end
end
if cart.behind then
local behind = get_cart_data(cart.behind)
if behind then
behind.ahead = nil
cart.behind = nil
save_cart_data(behind.uuid)
end
end
save_cart_data(cart.uuid)
end
mod.break_train_at = break_train_at
function mod.update_train(staticdata)
-- Only update from the back
if staticdata.behind or not staticdata.ahead then return end
-- Do no special processing if the cart is not part of a train
if not staticdata.ahead and not staticdata.behind then return end
-- Calculate the maximum velocity of all train cars
local velocity = staticdata.velocity
-- Set the entire train to the average velocity
local behind = nil
for c in train_cars(staticdata) do
local e = 0
local separation
local cart_velocity = velocity
if not c.connected_at then
break_train_at(c)
elseif behind then
separation = distance_between_cars(behind, c)
local e = 0
if not separation then
break_train_at(c)
elseif separation > 1.6 then
cart_velocity = velocity * 0.9
elseif separation > 2.5 then
break_train_at(c)
elseif separation < 1.15 then
cart_velocity = velocity * 1.1
end
end
--[[
print(tostring(c.behind).."->"..c.uuid.."->"..tostring(c.ahead).."("..tostring(separation)..") setting cart #"..
c.uuid.." velocity from "..tostring(c.velocity).." to "..tostring(cart_velocity))
--]]
c.velocity = cart_velocity
behind = c
end
end
function mod.link_cart_ahead(staticdata, ca_staticdata)
minetest.log("action","Linking cart #"..staticdata.uuid.." to cart #"..ca_staticdata.uuid)
staticdata.ahead = ca_staticdata.uuid
ca_staticdata.behind = staticdata.uuid
end
function mod.reverse_train(cart)
for c in train_cars(cart) do
mod.reverse_cart_direction(c)
c.behind,c.ahead = c.ahead,c.behind
end
end

View File

@ -388,7 +388,7 @@ end
local function on_step_work (self, dtime)
local function on_step_work(self, dtime, moveresult)
local pos = self.object:get_pos()
if not pos then return end
@ -402,7 +402,7 @@ local function on_step_work (self, dtime)
-- Do we abandon out of here now?
end
if self:falling(pos) then return end
if self:falling(pos, moveresult) then return end
if self:step_damage (dtime, pos) then return end
if self.state == "die" then return end
@ -502,11 +502,11 @@ end
-- main mob function
function mob_class:on_step(dtime)
function mob_class:on_step(dtime, moveresult)
if not DEVELOPMENT then
-- Removed as bundled Lua (5.1 doesn't support xpcall)
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
local status, retVal = pcall(on_step_work, self, dtime)
local status, retVal = pcall(on_step_work, self, dtime, moveresult)
if status then
return retVal
else
@ -521,7 +521,7 @@ function mob_class:on_step(dtime)
log_error (dump(retVal), dump(pos), dump(self))
end
else
return on_step_work (self, dtime)
return on_step_work (self, dtime, moveresult)
end
end

View File

@ -615,7 +615,7 @@ function mcl_mobs.register_egg(mob_id, desc, background_color, overlay_color, ad
pos.y = pos.y - 1
local mob = mcl_mobs.spawn(pos, mob_name)
if not object then
if not mob then
pos.y = pos.y + 1
mob = mcl_mobs.spawn(pos, mob_name)
if not mob then return end

View File

@ -927,7 +927,8 @@ end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos)
function mob_class:falling(pos, moveresult)
if moveresult and moveresult.touching_ground then return false end
if self.fly and self.state ~= "die" then
return

View File

@ -60,13 +60,15 @@ end
-- set skybox based on time (uses skycolor api)
function mcl_weather.rain.set_sky_box()
if mcl_weather.state == "rain" then
mcl_weather.skycolor.add_layer(
"weather-pack-rain-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=98},
{r=135, g=135, b=151},
{r=85, g=86, b=98},
{r=0, g=0, b=0}})
if mcl_weather.skycolor.current_layer_name() ~= "weather-pack-rain-sky" then
mcl_weather.skycolor.add_layer(
"weather-pack-rain-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=98},
{r=135, g=135, b=151},
{r=85, g=86, b=98},
{r=0, g=0, b=0}})
end
mcl_weather.skycolor.active = true
for _, player in pairs(get_connected_players()) do
player:set_clouds({color="#5D5D5FE8"})

View File

@ -386,7 +386,7 @@ local tab_icon = {
blocks = "mcl_core:brick_block",
deco = "mcl_flowers:peony",
redstone = "mesecons:redstone",
rail = "mcl_minecarts:golden_rail",
rail = "mcl_minecarts:golden_rail_v2",
misc = "mcl_buckets:bucket_lava",
nix = "mcl_compass:compass",
food = "mcl_core:apple",

View File

@ -129,9 +129,9 @@ local dropperdef = {
-- If they are containers - double down as hopper
mcl_util.hopper_push(pos, droppos)
end
if dropnodedef.walkable then
return
end
if dropnodedef.walkable then return end
-- Build a list of items in the dropper
local stacks = {}
for i = 1, inv:get_size("main") do
local stack = inv:get_stack("main", i)
@ -139,12 +139,32 @@ local dropperdef = {
table.insert(stacks, { stack = stack, stackpos = i })
end
end
-- Pick an item to drop
local dropitem = nil
local stack = nil
local r = nil
if #stacks >= 1 then
local r = math.random(1, #stacks)
local stack = stacks[r].stack
local dropitem = ItemStack(stack)
r = math.random(1, #stacks)
stack = stacks[r].stack
dropitem = ItemStack(stack)
dropitem:set_count(1)
local stack_id = stacks[r].stackpos
end
if not dropitem then return end
-- Flag for if the item was dropped. If true the item will be removed from
-- the inventory after dropping
local item_dropped = false
-- Check if the drop item has a custom handler
local itemdef = minetest.registered_craftitems[dropitem:get_name()]
if itemdef._mcl_dropper_on_drop then
item_dropped = itemdef._mcl_dropper_on_drop(dropitem, droppos)
end
-- If a custom handler wasn't successful then drop the item as an entity
if not item_dropped then
-- Drop as entity
local pos_variation = 100
droppos = vector.offset(droppos,
math.random(-pos_variation, pos_variation) / 1000,
@ -156,6 +176,12 @@ local dropperdef = {
local speed = 3
item_entity:set_velocity(vector.multiply(drop_vel, speed))
stack:take_item()
item_dropped = trie
end
-- Remove dropped items from inventory
if item_dropped then
local stack_id = stacks[r].stackpos
inv:set_stack("main", stack_id, stack)
end
end,

View File

@ -0,0 +1,227 @@
mesecon = mesecon or {}
local mod = {}
mesecon.commandblock = mod
local S = minetest.get_translator(minetest.get_current_modname())
local F = minetest.formspec_escape
local color_red = mcl_colors.RED
mod.initialize = function(meta)
meta:set_string("commands", "")
meta:set_string("commander", "")
end
mod.place = function(meta, placer)
if not placer then return end
meta:set_string("commander", placer:get_player_name())
end
mod.resolve_commands = function(commands, meta, pos)
local players = minetest.get_connected_players()
local commander = meta:get_string("commander")
-- A non-printable character used while replacing “@@”.
local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB
-- No players online: remove all commands containing
-- problematic placeholders.
if #players == 0 then
commands = commands:gsub("[^\r\n]+", function (line)
line = line:gsub("@@", SUBSTITUTE_CHARACTER)
if line:find("@n") then return "" end
if line:find("@p") then return "" end
if line:find("@f") then return "" end
if line:find("@r") then return "" end
line = line:gsub("@c", commander)
line = line:gsub(SUBSTITUTE_CHARACTER, "@")
return line
end)
return commands
end
local nearest, farthest = nil, nil
local min_distance, max_distance = math.huge, -1
for index, player in pairs(players) do
local distance = vector.distance(pos, player:get_pos())
if distance < min_distance then
min_distance = distance
nearest = player:get_player_name()
end
if distance > max_distance then
max_distance = distance
farthest = player:get_player_name()
end
end
local random = players[math.random(#players)]:get_player_name()
commands = commands:gsub("@@", SUBSTITUTE_CHARACTER)
commands = commands:gsub("@p", nearest)
commands = commands:gsub("@n", nearest)
commands = commands:gsub("@f", farthest)
commands = commands:gsub("@r", random)
commands = commands:gsub("@c", commander)
commands = commands:gsub(SUBSTITUTE_CHARACTER, "@")
return commands
end
local resolve_commands = mod.resolve_commands
mod.check_commands = function(commands, player_name)
for _, command in pairs(commands:split("\n")) do
local pos = command:find(" ")
local cmd = command
if pos then
cmd = command:sub(1, pos - 1)
end
local cmddef = minetest.chatcommands[cmd]
if not cmddef then
-- Invalid chat command
local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd)
if string.sub(cmd, 1, 1) == "/" then
msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd)
end
return false, minetest.colorize(color_red, msg)
end
if player_name then
local player_privs = minetest.get_player_privs(player_name)
for cmd_priv, _ in pairs(cmddef.privs) do
if player_privs[cmd_priv] ~= true then
local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv)
return false, minetest.colorize(color_red, msg)
end
end
end
end
return true
end
local check_commands = mod.check_commands
mod.action_on = function(meta, pos)
local commander = meta:get_string("commander")
local commands = resolve_commands(meta:get_string("commands"), meta, pos)
for _, command in pairs(commands:split("\n")) do
local cpos = command:find(" ")
local cmd, param = command, ""
if cpos then
cmd = command:sub(1, cpos - 1)
param = command:sub(cpos + 1)
end
local cmddef = minetest.chatcommands[cmd]
if not cmddef then
-- Invalid chat command
return
end
-- Execute command in the name of commander
cmddef.func(commander, param)
end
end
local formspec_metas = {}
mod.handle_rightclick = function(meta, player)
local can_edit = true
-- Only allow write access in Creative Mode
if not minetest.is_creative_enabled(player:get_player_name()) then
can_edit = false
end
local pname = player:get_player_name()
if minetest.is_protected(pos, pname) then
can_edit = false
end
local privs = minetest.get_player_privs(pname)
if not privs.maphack then
can_edit = false
end
local commands = meta:get_string("commands")
if not commands then
commands = ""
end
local commander = meta:get_string("commander")
local commanderstr
if commander == "" or commander == nil then
commanderstr = S("Error: No commander! Block must be replaced.")
else
commanderstr = S("Commander: @1", commander)
end
local textarea_name, submit, textarea
-- If editing is not allowed, only allow read-only access.
-- Player can still view the contents of the command block.
if can_edit then
textarea_name = "commands"
submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]"
else
textarea_name = ""
submit = ""
end
if not can_edit and commands == "" then
textarea = "label[0.5,0.5;"..F(S("No commands.")).."]"
else
textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]"
end
local formspec = "size[9,5;]" ..
textarea ..
submit ..
"image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" ..
"tooltip[doc;"..F(S("Help")).."]" ..
"label[0,4;"..F(commanderstr).."]"
-- Store the metadata object for later use
local fs_id = #formspec_metas + 1
formspec_metas[fs_id] = meta
print("using fs_id="..tostring(fs_id)..",meta="..tostring(meta)..",formspec_metas[fs_id]="..tostring(formspec_metas[fs_id]))
minetest.show_formspec(pname, "commandblock_"..tostring(fs_id), formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if string.sub(formname, 1, 13) == "commandblock_" then
-- Show documentation
if fields.doc and minetest.get_modpath("doc") then
doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true)
return
end
-- Validate form fields
if (not fields.submit and not fields.key_enter) or (not fields.commands) then
return
end
-- Check privileges
local privs = minetest.get_player_privs(player:get_player_name())
if not privs.maphack then
minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks."))
return
end
-- Check game mode
if not minetest.is_creative_enabled(player:get_player_name()) then
minetest.chat_send_player(player:get_player_name(),
S("Editing the command block has failed! You can only change the command block in Creative Mode!")
)
return
end
-- Retrieve the metadata object this formspec data belongs to
local index, _, fs_id = string.find(formname, "commandblock_(-?%d+)")
fs_id = tonumber(fs_id)
if not index or not fs_id or not formspec_metas[fs_id] then
print("index="..tostring(index)..", fs_id="..tostring(fs_id).."formspec_metas[fs_id]="..tostring(formspec_metas[fs_id]))
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone."))
return
end
local meta = formspec_metas[fs_id]
formspec_metas[fs_id] = nil
-- Verify the command
local check, error_message = check_commands(fields.commands, player:get_player_name())
if check == false then
-- Command block rejected
minetest.chat_send_player(player:get_player_name(), error_message)
return
end
-- Update the command in the metadata
meta:set_string("commands", fields.commands)
end
end)

View File

@ -1,104 +1,27 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(minetest.get_current_modname())
local F = minetest.formspec_escape
local tonumber = tonumber
local color_red = mcl_colors.RED
-- Initialize API
dofile(modpath.."/api.lua")
local api = mesecon.commandblock
local command_blocks_activated = minetest.settings:get_bool("mcl_enable_commandblocks", true)
local msg_not_activated = S("Command blocks are not enabled on this server")
local function construct(pos)
local meta = minetest.get_meta(pos)
meta:set_string("commands", "")
meta:set_string("commander", "")
api.initialize(meta)
end
local function after_place(pos, placer)
if placer then
local meta = minetest.get_meta(pos)
meta:set_string("commander", placer:get_player_name())
end
local meta = minetest.get_meta(pos)
api.place(meta, placer)
end
local function resolve_commands(commands, pos)
local players = minetest.get_connected_players()
local meta = minetest.get_meta(pos)
local commander = meta:get_string("commander")
-- A non-printable character used while replacing “@@”.
local SUBSTITUTE_CHARACTER = "\26" -- ASCII SUB
-- No players online: remove all commands containing
-- problematic placeholders.
if #players == 0 then
commands = commands:gsub("[^\r\n]+", function (line)
line = line:gsub("@@", SUBSTITUTE_CHARACTER)
if line:find("@n") then return "" end
if line:find("@p") then return "" end
if line:find("@f") then return "" end
if line:find("@r") then return "" end
line = line:gsub("@c", commander)
line = line:gsub(SUBSTITUTE_CHARACTER, "@")
return line
end)
return commands
end
local nearest, farthest = nil, nil
local min_distance, max_distance = math.huge, -1
for index, player in pairs(players) do
local distance = vector.distance(pos, player:get_pos())
if distance < min_distance then
min_distance = distance
nearest = player:get_player_name()
end
if distance > max_distance then
max_distance = distance
farthest = player:get_player_name()
end
end
local random = players[math.random(#players)]:get_player_name()
commands = commands:gsub("@@", SUBSTITUTE_CHARACTER)
commands = commands:gsub("@p", nearest)
commands = commands:gsub("@n", nearest)
commands = commands:gsub("@f", farthest)
commands = commands:gsub("@r", random)
commands = commands:gsub("@c", commander)
commands = commands:gsub(SUBSTITUTE_CHARACTER, "@")
return commands
end
local function check_commands(commands, player_name)
for _, command in pairs(commands:split("\n")) do
local pos = command:find(" ")
local cmd = command
if pos then
cmd = command:sub(1, pos - 1)
end
local cmddef = minetest.chatcommands[cmd]
if not cmddef then
-- Invalid chat command
local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd)
if string.sub(cmd, 1, 1) == "/" then
msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd)
end
return false, minetest.colorize(color_red, msg)
end
if player_name then
local player_privs = minetest.get_player_privs(player_name)
for cmd_priv, _ in pairs(cmddef.privs) do
if player_privs[cmd_priv] ~= true then
local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv)
return false, minetest.colorize(color_red, msg)
end
end
end
end
return true
return api.resolve_commands(commands, meta)
end
local function commandblock_action_on(pos, node)
@ -107,7 +30,6 @@ local function commandblock_action_on(pos, node)
end
local meta = minetest.get_meta(pos)
local commander = meta:get_string("commander")
if not command_blocks_activated then
--minetest.chat_send_player(commander, msg_not_activated)
@ -115,22 +37,7 @@ local function commandblock_action_on(pos, node)
end
minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"})
local commands = resolve_commands(meta:get_string("commands"), pos)
for _, command in pairs(commands:split("\n")) do
local cpos = command:find(" ")
local cmd, param = command, ""
if cpos then
cmd = command:sub(1, cpos - 1)
param = command:sub(cpos + 1)
end
local cmddef = minetest.chatcommands[cmd]
if not cmddef then
-- Invalid chat command
return
end
-- Execute command in the name of commander
cmddef.func(commander, param)
end
api.action_on(meta, pos)
end
local function commandblock_action_off(pos, node)
@ -144,54 +51,10 @@ local function on_rightclick(pos, node, player, itemstack, pointed_thing)
minetest.chat_send_player(player:get_player_name(), msg_not_activated)
return
end
local can_edit = true
-- Only allow write access in Creative Mode
if not minetest.is_creative_enabled(player:get_player_name()) then
can_edit = false
end
local pname = player:get_player_name()
if minetest.is_protected(pos, pname) then
can_edit = false
end
local privs = minetest.get_player_privs(pname)
if not privs.maphack then
can_edit = false
end
local meta = minetest.get_meta(pos)
local commands = meta:get_string("commands")
if not commands then
commands = ""
end
local commander = meta:get_string("commander")
local commanderstr
if commander == "" or commander == nil then
commanderstr = S("Error: No commander! Block must be replaced.")
else
commanderstr = S("Commander: @1", commander)
end
local textarea_name, submit, textarea
-- If editing is not allowed, only allow read-only access.
-- Player can still view the contents of the command block.
if can_edit then
textarea_name = "commands"
submit = "button_exit[3.3,4.4;2,1;submit;"..F(S("Submit")).."]"
else
textarea_name = ""
submit = ""
end
if not can_edit and commands == "" then
textarea = "label[0.5,0.5;"..F(S("No commands.")).."]"
else
textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]"
end
local formspec = "size[9,5;]" ..
textarea ..
submit ..
"image_button[8,4.4;1,1;doc_button_icon_lores.png;doc;]" ..
"tooltip[doc;"..F(S("Help")).."]" ..
"label[0,4;"..F(commanderstr).."]"
minetest.show_formspec(pname, "commandblock_"..pos.x.."_"..pos.y.."_"..pos.z, formspec)
api.handle_rightclick(meta, player)
end
local function on_place(itemstack, placer, pointed_thing)
@ -200,12 +63,10 @@ local function on_place(itemstack, placer, pointed_thing)
end
-- Use pointed node's on_rightclick function first, if present
local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
if new_stack then
return new_stack
end
--local node = minetest.get_node(pointed_thing.under)
local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
if new_stack then
return new_stack
end
local privs = minetest.get_player_privs(placer:get_player_name())
if not privs.maphack then
@ -280,44 +141,6 @@ minetest.register_node("mesecons_commandblock:commandblock_on", {
_mcl_hardness = -1,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if string.sub(formname, 1, 13) == "commandblock_" then
if fields.doc and minetest.get_modpath("doc") then
doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true)
return
end
if (not fields.submit and not fields.key_enter) or (not fields.commands) then
return
end
local privs = minetest.get_player_privs(player:get_player_name())
if not privs.maphack then
minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks."))
return
end
local index, _, x, y, z = string.find(formname, "commandblock_(-?%d+)_(-?%d+)_(-?%d+)")
if index and x and y and z then
local pos = {x = tonumber(x), y = tonumber(y), z = tonumber(z)}
local meta = minetest.get_meta(pos)
if not minetest.is_creative_enabled(player:get_player_name()) then
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! You can only change the command block in Creative Mode!"))
return
end
local check, error_message = check_commands(fields.commands, player:get_player_name())
if check == false then
-- Command block rejected
minetest.chat_send_player(player:get_player_name(), error_message)
return
else
meta:set_string("commands", fields.commands)
end
else
minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone."))
end
end
end)
-- Add entry alias for the Help
if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on")

View File

@ -74,7 +74,7 @@ function mcl_bamboo.break_orphaned(pos)
local node_name = node_below.name
-- short circuit checks.
if mcl_bamboo.is_dirt(node_name) or mcl_bamboo.is_bamboo(node_name) or mcl_bamboo.is_bamboo(minetest.get_node(pos).name) == false then
if node_name == "ignore" or mcl_bamboo.is_dirt(node_name) or mcl_bamboo.is_bamboo(node_name) or mcl_bamboo.is_bamboo(minetest.get_node(pos).name) == false then
return
end

View File

@ -79,7 +79,7 @@ minetest.register_lbm({
local node_name = node.name
node.name = node_name .. "_small"
minetest.swap_node(pos, node)
select_and_spawn_entity(pos, node)
mcl_chests.select_and_spawn_entity(pos, node)
if node_name == "mcl_chests:trapped_chest_on" then
minetest.log("action", "[mcl_chests] Disabled active trapped chest on load: " .. minetest.pos_to_string(pos))
mcl_chests.chest_update_after_close(pos)

View File

@ -161,7 +161,7 @@ minetest.register_abm({
action = function(pos, node, active_object_count, active_object_count_wider)
liquid_flow_action(pos, "water", function(pos)
drop_attached_node(pos)
minetest.dig_node(pos)
minetest.remove_node(pos)
end)
end,
})
@ -217,7 +217,8 @@ minetest.register_abm({
while true do
local node = minetest.get_node(lpos)
if not node or node.name ~= "mcl_core:cactus" then break end
minetest.dig_node(lpos)
-- minetest.dig_node ignores protected nodes and causes infinite drop (#4628)
minetest.remove_node(lpos)
dx = dx or ((math.random(0,1)-0.5) * math.sqrt(math.random())) * 1.5
dy = dy or ((math.random(0,1)-0.5) * math.sqrt(math.random())) * 1.5
local obj = minetest.add_item(vector.offset(lpos, dx, 0.25, dy), "mcl_core:cactus")

View File

@ -48,6 +48,11 @@ minetest.register_node("mcl_core:cactus", {
end),
_mcl_blast_resistance = 0.4,
_mcl_hardness = 0.4,
_mcl_minecarts_on_enter_side = function(pos, _, _, _, cart_data)
if mcl_minecarts then
mcl_minecarts.kill_cart(cart_data)
end
end,
})
minetest.register_node("mcl_core:reeds", {
@ -135,4 +140,4 @@ minetest.register_node("mcl_core:reeds", {
end,
_mcl_blast_resistance = 0,
_mcl_hardness = 0,
})
})

View File

@ -111,7 +111,7 @@ for _, p in pairs(deepslate_ores) do
end
if copper_mod then
register_deepslate_ore("Copper", "mcl_copper:raw_copper", "mcl_copper:copper_ingot", 4, 4)
register_deepslate_ore("Copper", "mcl_copper:raw_copper", "mcl_copper:copper_ingot", 3, 4)
end
local redstone_timer = 68.28

View File

@ -283,7 +283,7 @@ local function apply_bone_meal(pointed_thing, user)
if n.name == "mcl_farming:sweet_berry_bush_3" then
return minetest.add_item(vector.offset(pos,math.random()-0.5,math.random()-0.5,math.random()-0.5),"mcl_farming:sweet_berry")
else
return mcl_farming:grow_plant("plant_sweet_berry_bush", pos, n, 0, true)
return mcl_farming:grow_plant("plant_sweet_berry_bush", pos, n, 1, true)
end
elseif n.name == "mcl_cocoas:cocoa_1" or n.name == "mcl_cocoas:cocoa_2" then
mcl_dye.add_bone_meal_particle(pos)

View File

@ -691,7 +691,7 @@ mcl_enchanting.enchantments.soul_speed = {
disallow = {non_combat_armor = true},
incompatible = {frost_walker = true},
weight = 2,
description = S("Increases walking speed on soul sand."),
description = S("Increases walking speed on soul sand and soul soil."),
curse = false,
on_enchant = function() end,
requires_tool = false,

View File

@ -68,7 +68,7 @@ Mined blocks drop themselves.=Abgebaute Blöcke werfen sich selbst ab.
Smite=Qual
Increases damage to undead mobs.=Erhöht Schaden für untote Mobs.
Soul Speed=Schnelle Seele
Increases walking speed on soul sand.=Erhöht Gehgeschwindigkeit auf Seelensand.
Increases walking speed on soul sand and soul soil.=Erhöht Gehgeschwindigkeit auf Seelensand.
Sweeping Edge=Schwungklinge
Increases sweeping attack damage.=Erhöht Schwungangriffsschaden.
Thorns=Dornen

View File

@ -36,7 +36,7 @@ Increases mob loot.=Incrementa el botín de los enemigos.
Increases rate of good loot (enchanting books, etc.)=Incrementa la probabilidad de encontrar tesoros.
Increases sweeping attack damage.=Incrementa el daño de efecto area.
Increases underwater movement speed.=Incrementa la velocidad de nado bajo el agua.
Increases walking speed on soul sand.=Incrementa la velocidad al caminar sobre arena de Almas.
Increases walking speed on soul sand and soul soil.=Incrementa la velocidad al caminar sobre arena de Almas.
Infinity=Infinidad
Item destroyed on death.=El objeto se destruye tras tu muerte.
Knockback=Empuje

View File

@ -36,7 +36,7 @@ Increases mob loot.=Augmente le butin des mobs.
Increases rate of good loot (enchanting books, etc.)=Augmente le taux de bon butin (livres enchanteurs, etc.)
Increases sweeping attack damage.=Augmente les dégâts de l'épée
Increases underwater movement speed.=Augmente la vitesse de déplacement sous l'eau.
Increases walking speed on soul sand.=Augmente la vitesse de marche sur le sable des âmes.
Increases walking speed on soul sand and soul soil.=Augmente la vitesse de marche sur le sable des âmes.
Infinity=Infinité
Item destroyed on death.=Objet détruit à la mort.
Knockback=Recul

View File

@ -36,7 +36,7 @@ Increases mob loot.=MOBの戦利品が増加します。
Increases rate of good loot (enchanting books, etc.)=釣果の質が良くなります(エンチャントの本など)。
Increases sweeping attack damage.=なぎ払い攻撃のダメージが増加します。
Increases underwater movement speed.=水中での横移動速度が増加します。
Increases walking speed on soul sand.=ソウルサンドとソウルソイルの上を歩く速度が増加します。
Increases walking speed on soul sand and soul soil.=ソウルサンドとソウルソイルの上を歩く速度が増加します。
Infinity=無限
Item destroyed on death.=死亡時にアイテムが消滅します。
Knockback=ノックバック

View File

@ -36,7 +36,7 @@ Increases mob loot.=
Increases rate of good loot (enchanting books, etc.)=
Increases sweeping attack damage.=
Increases underwater movement speed.=
Increases walking speed on soul sand.=
Increases walking speed on soul sand and soul soil.=
Infinity=
Item destroyed on death.=
Knockback=

View File

@ -1,81 +1,79 @@
-- Possible future improvements:
-- * rewrite to use node timers instead of ABMs, but needs benchmarking
-- * redesign the catch-up logic
-- * switch to exponentially-weighted moving average for light instead using a single variable to conserve IO
--
local math = math
local tostring = tostring
mcl_farming.plant_lists = {}
local vector = vector
local plant_lists = {}
mcl_farming.plant_lists = plant_lists -- export
local plant_nodename_to_id_list = {}
local function get_intervals_counter(pos, interval, chance)
local meta = minetest.get_meta(pos)
local time_speed = tonumber(minetest.settings:get("time_speed") or 72)
local current_game_time
if time_speed == nil then
return 1
end
if (time_speed < 0.1) then
return 1
end
local time_multiplier = 86400 / time_speed
current_game_time = .0 + ((minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier)
local time_speed = tonumber(minetest.settings:get("time_speed")) or 72
local time_multiplier = time_speed > 0 and (86400 / time_speed) or 0
local function get_intervals_counter(pos, interval, chance)
if time_multiplier == 0 then return 0 end
-- "wall clock time", so plants continue to grow while sleeping
local current_game_time = (minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier
local approx_interval = math.max(interval, 1) * math.max(chance, 1)
local last_game_time = meta:get_string("last_gametime")
if last_game_time then
last_game_time = tonumber(last_game_time)
end
if not last_game_time or last_game_time < 1 then
last_game_time = current_game_time - approx_interval / 10
local meta = minetest.get_meta(pos)
local last_game_time = meta:get_float("last_gametime")
if last_game_time < 1 then
last_game_time = current_game_time - approx_interval * 0.5
elseif last_game_time == current_game_time then
current_game_time = current_game_time + approx_interval
end
local elapsed_game_time = .0 + current_game_time - last_game_time
meta:set_string("last_gametime", tostring(current_game_time))
return elapsed_game_time / approx_interval
meta:set_float("last_gametime", current_game_time)
return (current_game_time - last_game_time) / approx_interval
end
local function get_avg_light_level(pos)
local node_light = tonumber(minetest.get_node_light(pos) or 0)
local meta = minetest.get_meta(pos)
local counter = meta:get_int("avg_light_count")
-- EWMA would use a single variable:
-- local avg = meta:get_float("avg_light")
-- avg = avg + (node_light - avg) * 0.985
-- meta.set_float("avg_light", avg)
local summary = meta:get_int("avg_light_summary")
local counter = meta:get_int("avg_light_count")
if counter > 99 then
counter = 51
summary = math.ceil((summary + 0.0) / 2.0)
else
counter = counter + 1
summary, counter = math.ceil(summary * 0.5), 50
end
summary = summary + node_light
meta:set_int("avg_light_count", counter)
meta:set_int("avg_light_summary", summary)
return math.ceil((summary + 0.0) / counter)
local node_light = minetest.get_node_light(pos)
if node_light ~= nil then
summary, counter = summary + node_light, counter + 1
meta:set_int("avg_light_summary", summary)
meta:set_int("avg_light_count", counter)
end
return math.ceil(summary / counter)
end
function mcl_farming:add_plant(identifier, full_grown, names, interval, chance)
mcl_farming.plant_lists[identifier] = {}
mcl_farming.plant_lists[identifier].full_grown = full_grown
mcl_farming.plant_lists[identifier].names = names
mcl_farming.plant_lists[identifier].interval = interval
mcl_farming.plant_lists[identifier].chance = chance
plant_lists = mcl_farming.plant_lists --provide local copy of plant lists (performances)
local plant_info = {}
plant_info.full_grown = full_grown
plant_info.names = names
plant_info.interval = interval
plant_info.chance = chance
for _, nodename in pairs(names) do
plant_nodename_to_id_list[nodename] = identifier
end
plant_info.step_from_name = {}
for i, name in ipairs(names) do
plant_info.step_from_name[name] = i
end
plant_lists[identifier] = plant_info
minetest.register_abm({
label = string.format("Farming plant growth (%s)", identifier),
nodenames = names,
interval = interval,
chance = chance,
action = function(pos, node)
local low_speed = minetest.get_node({ x = pos.x, y = pos.y - 1, z = pos.z }).name ~= "mcl_farming:soil_wet"
mcl_farming:grow_plant(identifier, pos, node, false, false, low_speed)
local low_speed = minetest.get_node(vector.offset(pos, 0, -1, 0)).name ~= "mcl_farming:soil_wet"
mcl_farming:grow_plant(identifier, pos, node, 1, false, low_speed)
end,
})
for _, nodename in pairs(names) do
plant_nodename_to_id_list[nodename] = identifier
end
end
-- Attempts to advance a plant at pos by one or more growth stages (if possible)
@ -84,56 +82,36 @@ end
-- node: Node table
-- stages: Number of stages to advance (optional, defaults to 1)
-- ignore_light: if true, ignore light requirements for growing
-- low_speed: grow more slowly (not wet), default false
-- Returns true if plant has been grown by 1 or more stages.
-- Returns false if nothing changed.
function mcl_farming:grow_plant(identifier, pos, node, stages, ignore_light, low_speed)
local average_light_level = get_avg_light_level(pos)
stages = stages or 1
local plant_info = plant_lists[identifier]
local intervals_counter = get_intervals_counter(pos, plant_info.interval, plant_info.chance)
local low_speed = low_speed or false
if low_speed then
if intervals_counter < 1.01 and math.random(0, 9) > 0 then
return
else
intervals_counter = intervals_counter / 10
end
if stages > 0 then intervals_counters = intervals_counter - 1 end
if low_speed then -- 10% speed approximately
if intervals_counter < 1.01 and math.random(0, 9) > 0 then return false end
intervals_counter = intervals_counter / 10
end
if not minetest.get_node_light(pos) and not ignore_light and intervals_counter < 1.5 then
return false
end
if minetest.get_node_light(pos) < 10 and not ignore_light and intervals_counter < 1.5 then
return false
if not ignore_light and intervals_counter < 1.5 then
local light = minetest.get_node_light(pos)
if not light or light < 10 then return false end
end
if intervals_counter >= 1.5 then
if average_light_level < 0.1 then
return false
end
local average_light_level = get_avg_light_level(pos)
if average_light_level < 0.1 then return false end
if average_light_level < 10 then
intervals_counter = intervals_counter * average_light_level / 10
end
end
local step = nil
for i, name in ipairs(plant_info.names) do
if name == node.name then
step = i
break
end
end
if step == nil then
return false
end
if not stages then
stages = 1
end
stages = stages + math.ceil(intervals_counter)
local new_node = { name = plant_info.names[step + stages] }
if new_node.name == nil then
new_node.name = plant_info.full_grown
end
local step = plant_info.step_from_name[node.name]
if step == nil then return false end
stages = stages + math.floor(intervals_counter)
if stages == 0 then return false end
local new_node = { name = plant_info.names[step + stages] or plant_info.full_grown }
new_node.param = node.param
new_node.param2 = node.param2
minetest.set_node(pos, new_node)
@ -142,12 +120,7 @@ end
function mcl_farming:place_seed(itemstack, placer, pointed_thing, plantname)
local pt = pointed_thing
if not pt then
return
end
if pt.type ~= "node" then
return
end
if not pt or pt.type ~= "node" then return end
-- Use pointed node's on_rightclick function first, if present
local node = minetest.get_node(pt.under)
@ -157,22 +130,13 @@ function mcl_farming:place_seed(itemstack, placer, pointed_thing, plantname)
end
end
local pos = { x = pt.above.x, y = pt.above.y - 1, z = pt.above.z }
local farmland = minetest.get_node(pos)
pos = { x = pt.above.x, y = pt.above.y, z = pt.above.z }
local place_s = minetest.get_node(pos)
if minetest.get_node(pt.above).name ~= "air" then return end
local farmland = minetest.registered_nodes[minetest.get_node(vector.offset(pt.above, 0, -1, 0)).name]
if not farmland or (farmland.groups.soil or 0) < 2 then return end
minetest.sound_play(minetest.registered_nodes[plantname].sounds.place, { pos = pt.above }, true)
minetest.add_node(pt.above, { name = plantname, param2 = minetest.registered_nodes[plantname].place_param2 })
if string.find(farmland.name, "mcl_farming:soil") and string.find(place_s.name, "air") then
minetest.sound_play(minetest.registered_nodes[plantname].sounds.place, { pos = pos }, true)
minetest.add_node(pos, { name = plantname, param2 = minetest.registered_nodes[plantname].place_param2 })
--local intervals_counter = get_intervals_counter(pos, 1, 1)
else
return
end
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
if not minetest.is_creative_enabled(placer:get_player_name()) then itemstack:take_item() end
return itemstack
end
@ -189,48 +153,41 @@ end
- grow_interval: Will attempt to grow a gourd periodically at this interval in seconds
- grow_chance: Chance of 1/grow_chance to grow a gourd next to the full unconnected stem after grow_interval has passed. Must be a natural number
- connected_stem_texture: Texture of the connected stem
- gourd_on_construct_extra: Custom on_construct extra function for the gourd. Will be called after the stem check code
]]
function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, stem_itemstring, stem_def, stem_drop, gourd_itemstring, gourd_def, grow_interval, grow_chance, connected_stem_texture, gourd_on_construct_extra)
function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, stem_itemstring, stem_def, stem_drop, gourd_itemstring, gourd_def, grow_interval, grow_chance, connected_stem_texture)
local connected_stem_names = {
connected_stem_basename .. "_r",
connected_stem_basename .. "_l",
connected_stem_basename .. "_t",
connected_stem_basename .. "_b",
}
local neighbors = {
{ x = -1, y = 0, z = 0 },
{ x = 1, y = 0, z = 0 },
{ x = 0, y = 0, z = -1 },
{ x = 0, y = 0, z = 1 },
}
connected_stem_basename .. "_b" }
-- Connect the stem at stempos to the first neighboring gourd block.
-- No-op if not a stem or no gourd block found
local function try_connect_stem(stempos)
local stem = minetest.get_node(stempos)
if stem.name ~= full_unconnected_stem then
return false
if stem.name ~= full_unconnected_stem then return false end
-- four directions, but avoid table allocation
local neighbor = vector.offset(stempos, 1, 0, 0)
if minetest.get_node(neighbor).name == gourd_itemstring then
minetest.swap_node(stempos, { name = connected_stem_names[1] })
return true
end
for n = 1, #neighbors do
local offset = neighbors[n]
local blockpos = vector.add(stempos, offset)
local block = minetest.get_node(blockpos)
if block.name == gourd_itemstring then
if offset.x == 1 then
minetest.set_node(stempos, { name = connected_stem_names[1] })
elseif offset.x == -1 then
minetest.set_node(stempos, { name = connected_stem_names[2] })
elseif offset.z == 1 then
minetest.set_node(stempos, { name = connected_stem_names[3] })
elseif offset.z == -1 then
minetest.set_node(stempos, { name = connected_stem_names[4] })
end
return true
end
local neighbor = vector.offset(stempos, -1, 0, 0)
if minetest.get_node(neighbor).name == gourd_itemstring then
minetest.swap_node(stempos, { name = connected_stem_names[2] })
return true
end
local neighbor = vector.offset(stempos, 0, 0, 1)
if minetest.get_node(neighbor).name == gourd_itemstring then
minetest.swap_node(stempos, { name = connected_stem_names[3] })
return true
end
local neighbor = vector.offset(stempos, 0, 0, -1)
if minetest.get_node(neighbor).name == gourd_itemstring then
minetest.swap_node(stempos, { name = connected_stem_names[4] })
return true
end
end
@ -238,29 +195,37 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
if not gourd_def.after_destruct then
gourd_def.after_destruct = function(blockpos, oldnode)
-- Disconnect any connected stems, turning them back to normal stems
for n = 1, #neighbors do
local offset = neighbors[n]
local expected_stem = connected_stem_names[n]
local stempos = vector.add(blockpos, offset)
local stem = minetest.get_node(stempos)
if stem.name == expected_stem then
minetest.add_node(stempos, { name = full_unconnected_stem })
try_connect_stem(stempos)
end
-- four directions, but avoid using a table
-- opposite directions to above, as we go from groud to stem now!
local stempos = vector.offset(blockpos, -1, 0, 0)
if minetest.get_node(stempos).name == connected_stem_names[1] then
minetest.swap_node(stempos, { name = full_unconnected_stem })
try_connect_stem(stempos)
end
local stempos = vector.offset(blockpos, 1, 0, 0)
if minetest.get_node(stempos).name == connected_stem_names[2] then
minetest.swap_node(stempos, { name = full_unconnected_stem })
try_connect_stem(stempos)
end
local stempos = vector.offset(blockpos, 0, 0, -1)
if minetest.get_node(stempos).name == connected_stem_names[3] then
minetest.swap_node(stempos, { name = full_unconnected_stem })
try_connect_stem(stempos)
end
local stempos = vector.offset(blockpos, 0, 0, 1)
if minetest.get_node(stempos).name == connected_stem_names[4] then
minetest.swap_node(stempos, { name = full_unconnected_stem })
try_connect_stem(stempos)
end
end
end
if not gourd_def.on_construct then
function gourd_def.on_construct(blockpos)
-- Connect all unconnected stems at full size
for n = 1, #neighbors do
local stempos = vector.add(blockpos, neighbors[n])
try_connect_stem(stempos)
end
-- Call custom on_construct
if gourd_on_construct_extra then
gourd_on_construct_extra(blockpos)
end
try_connect_stem(vector.offset(blockpos, 1, 0, 0))
try_connect_stem(vector.offset(blockpos, -1, 0, 0))
try_connect_stem(vector.offset(blockpos, 0, 0, 1))
try_connect_stem(vector.offset(blockpos, 0, 0, -1))
end
end
minetest.register_node(gourd_itemstring, gourd_def)
@ -269,72 +234,47 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
-- Default values for the stem definition
if not stem_def.selection_box then
stem_def.selection_box = {
type = "fixed",
fixed = {
{ -0.15, -0.5, -0.15, 0.15, 0.5, 0.15 }
},
}
end
if not stem_def.paramtype then
stem_def.paramtype = "light"
end
if not stem_def.drawtype then
stem_def.drawtype = "plantlike"
end
if stem_def.walkable == nil then
stem_def.walkable = false
end
if stem_def.sunlight_propagates == nil then
stem_def.sunlight_propagates = true
end
if stem_def.drop == nil then
stem_def.drop = stem_drop
end
if stem_def.groups == nil then
stem_def.groups = { dig_immediate = 3, not_in_creative_inventory = 1, plant = 1, attached_node = 1, dig_by_water = 1, destroy_by_lava_flow = 1, }
end
if stem_def.sounds == nil then
stem_def.sounds = mcl_sounds.node_sound_leaves_defaults()
end
if not stem_def.on_construct then
function stem_def.on_construct(stempos)
-- Connect stem to gourd (if possible)
try_connect_stem(stempos)
end
stem_def.selection_box = { type = "fixed", fixed = { { -0.15, -0.5, -0.15, 0.15, 0.5, 0.15 } } }
end
stem_def.paramtype = stem_def.paramtype or "light"
stem_def.drawtype = stem_def.drawtype or "plantlike"
stem_def.walkable = stem_def.walkable or false
stem_def.sunlight_propagates = stem_def.sunlight_propagates == nil or stem_def.sunlight_propagates
stem_def.drop = stem_def.drop or stem_drop
stem_def.groups = stem_def.groups or { dig_immediate = 3, not_in_creative_inventory = 1, plant = 1, attached_node = 1, dig_by_water = 1, destroy_by_lava_flow = 1 }
stem_def.sounds = stem_def.sounds or mcl_sounds.node_sound_leaves_defaults()
stem_def.on_construct = stem_def.on_construct or try_connect_stem
minetest.register_node(stem_itemstring, stem_def)
-- Register connected stems
local connected_stem_tiles = {
{ "blank.png", --top
{ "blank.png", -- top
"blank.png", -- bottom
"blank.png", -- right
"blank.png", -- left
connected_stem_texture, -- back
connected_stem_texture .. "^[transformFX" --front
connected_stem_texture .. "^[transformFX" -- front
},
{ "blank.png", --top
{ "blank.png", -- top
"blank.png", -- bottom
"blank.png", -- right
"blank.png", -- left
connected_stem_texture .. "^[transformFX", --back
connected_stem_texture .. "^[transformFX", -- back
connected_stem_texture, -- front
},
{ "blank.png", --top
{ "blank.png", -- top
"blank.png", -- bottom
connected_stem_texture .. "^[transformFX", -- right
connected_stem_texture, -- left
"blank.png", --back
"blank.png", -- back
"blank.png", -- front
},
{ "blank.png", --top
{ "blank.png", -- top
"blank.png", -- bottom
connected_stem_texture, -- right
connected_stem_texture .. "^[transformFX", -- left
"blank.png", --back
"blank.png", -- back
"blank.png", -- front
}
}
@ -359,17 +299,11 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
walkable = false,
drop = stem_drop,
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = connected_stem_nodebox[i]
},
selection_box = {
type = "fixed",
fixed = connected_stem_selectionbox[i]
},
node_box = { type = "fixed", fixed = connected_stem_nodebox[i] },
selection_box = { type = "fixed", fixed = connected_stem_selectionbox[i] },
tiles = connected_stem_tiles[i],
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "clip" or true,
groups = { dig_immediate = 3, not_in_creative_inventory = 1, plant = 1, attached_node = 1, dig_by_water = 1, destroy_by_lava_flow = 1, },
groups = { dig_immediate = 3, not_in_creative_inventory = 1, plant = 1, attached_node = 1, dig_by_water = 1, destroy_by_lava_flow = 1 },
sounds = mcl_sounds.node_sound_leaves_defaults(),
_mcl_blast_resistance = 0,
})
@ -379,6 +313,16 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
end
end
-- Check for a suitable spot to grow
local function check_neighbor_soil(blockpos)
if minetest.get_node(blockpos).name ~= "air" then return false end
local floorpos = vector.offset(blockpos, 0, -1, 0)
local floorname = minetest.get_node(floorpos).name
if floorname == "mcl_core:dirt" then return true end
local floordef = minetest.registered_nodes[floorname]
return floordef.groups.grass_block or floordef.groups.dirt or (floordef.groups.soil or 0) >= 2
end
minetest.register_abm({
label = "Grow gourd stem to gourd (" .. full_unconnected_stem .. "" .. gourd_itemstring .. ")",
nodenames = { full_unconnected_stem },
@ -387,67 +331,50 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
chance = grow_chance,
action = function(stempos)
local light = minetest.get_node_light(stempos)
if light and light > 10 then
-- Check the four neighbors and filter out neighbors where gourds can't grow
local neighbors = {
{ x = -1, y = 0, z = 0 },
{ x = 1, y = 0, z = 0 },
{ x = 0, y = 0, z = -1 },
{ x = 0, y = 0, z = 1 },
}
local floorpos, floor
for n = #neighbors, 1, -1 do
local offset = neighbors[n]
local blockpos = vector.add(stempos, offset)
floorpos = vector.offset (blockpos, 0, -1,0) -- replaces { x = blockpos.x, y = blockpos.y - 1, z = blockpos.z }
floor = minetest.get_node(floorpos)
local block = minetest.get_node(blockpos)
local soilgroup = minetest.get_item_group(floor.name, "soil")
if not ((minetest.get_item_group(floor.name, "grass_block") == 1 or floor.name == "mcl_core:dirt" or soilgroup == 2 or soilgroup == 3) and block.name == "air") then
table.remove(neighbors, n)
end
if not light or light <= 10 then return end
-- Check the four neighbors and filter out neighbors where gourds can't grow
local neighbor, dir, nchance = nil, -1, 1 -- reservoir sampling
if nchance == 1 or math.random(1, nchance) == 1 then
local blockpos = vector.offset(stempos, 1, 0, 0)
if check_neighbor_soil(blockpos) then
neighbor, dir, nchance = blockpos, 1, nchance + 1
end
-- Gourd needs at least 1 free neighbor to grow
if #neighbors > 0 then
-- From the remaining neighbors, grow randomly
local r = math.random(1, #neighbors)
local offset = neighbors[r]
local blockpos = vector.add(stempos, offset)
local p2
if offset.x == 1 then
minetest.set_node(stempos, { name = connected_stem_names[1] })
p2 = 3
elseif offset.x == -1 then
minetest.set_node(stempos, { name = connected_stem_names[2] })
p2 = 1
elseif offset.z == 1 then
minetest.set_node(stempos, { name = connected_stem_names[3] })
p2 = 2
elseif offset.z == -1 then
minetest.set_node(stempos, { name = connected_stem_names[4] })
p2 = 0
end
-- Place the gourd
if gourd_def.paramtype2 == "facedir" then
minetest.add_node(blockpos, { name = gourd_itemstring, param2 = p2 })
else
minetest.add_node(blockpos, { name = gourd_itemstring })
end
-- Reset farmland, etc. to dirt when the gourd grows on top
-- FIXED: The following 2 lines were missing, and wasn't being set (outside of the above loop that
-- finds the neighbors.)
-- FYI - don't factor this out thinking that the loop above is setting the positions correctly.
floorpos = vector.offset (blockpos, 0, -1,0) -- replaces { x = blockpos.x, y = blockpos.y - 1, z = blockpos.z }
floor = minetest.get_node(floorpos)
-- END OF FIX -------------------------------------
if minetest.get_item_group(floor.name, "dirtifies_below_solid") == 1 then
minetest.set_node(floorpos, { name = "mcl_core:dirt" })
end
end
if nchance == 1 or math.random(1, nchance) == 1 then
local blockpos = vector.offset(stempos, -1, 0, 0)
if check_neighbor_soil(blockpos) then
neighbor, dir, nchance = blockpos, 2, nchance + 1
end
end
if nchance == 1 or math.random(1, nchance) == 1 then
local blockpos = vector.offset(stempos, 0, 0, 1)
if check_neighbor_soil(blockpos) then
neighbor, dir, nchance = blockpos, 3, nchance + 1
end
end
if nchance == 1 or math.random(1, nchance) == 1 then
local blockpos = vector.offset(stempos, 0, 0, -1)
if check_neighbor_soil(blockpos) then
neighbor, dir, nchance = blockpos, 4, nchance + 1
end
end
-- Gourd needs at least 1 free neighbor to grow
if not neighbor then return end
minetest.swap_node(stempos, { name = connected_stem_names[dir] })
-- Place the gourd
if gourd_def.paramtype2 == "facedir" then
local p2 = (dir == 1 and 3) or (dir == 2 and 1) or (dir == 3 and 2) or 0
minetest.add_node(neighbor, { name = gourd_itemstring, param2 = p2 })
else
minetest.add_node(neighbor, { name = gourd_itemstring })
end
-- Reset farmland, etc. to dirt when the gourd grows on top
local floorpos = vector.offset(neighbor, 0, -1, 0)
if minetest.get_item_group(minetest.get_node(floorpos).name, "dirtifies_below_solid") == 1 then
minetest.set_node(floorpos, { name = "mcl_core:dirt" })
end
end,
})
end
@ -458,15 +385,11 @@ end
-- * step: The nth growth step. Counting starts at 1
-- * step_count: The number of total growth steps
function mcl_farming:stem_color(startcolor, endcolor, step, step_count)
local color = {}
local function get_component(startt, endd, step, step_count)
return math.floor(math.max(0, math.min(255, (startt + (((step - 1) / step_count) * endd)))))
end
color.r = get_component(startcolor.r, endcolor.r, step, step_count)
color.g = get_component(startcolor.g, endcolor.g, step, step_count)
color.b = get_component(startcolor.b, endcolor.b, step, step_count)
local colorstring = string.format("#%02X%02X%02X", color.r, color.g, color.b)
return colorstring
local mix = (step - 1) / (step_count - 1)
return string.format("#%02X%02X%02X",
math.max(0, math.min(255, math.round((1 - mix) * startcolor.r + mix * endcolor.r))),
math.max(0, math.min(255, math.round((1 - mix) * startcolor.g + mix * endcolor.g))),
math.max(0, math.min(255, math.round((1 - mix) * startcolor.b + mix * endcolor.b))))
end
--[[Get a callback that either eats the item or plants it.
@ -475,12 +398,8 @@ Used for on_place callbacks for craft items which are seeds that can also be con
]]
function mcl_farming:get_seed_or_eat_callback(plantname, hp_change)
return function(itemstack, placer, pointed_thing)
local new = mcl_farming:place_seed(itemstack, placer, pointed_thing, plantname)
if new then
return new
else
return minetest.do_item_eat(hp_change, nil, itemstack, placer, pointed_thing)
end
return mcl_farming:place_seed(itemstack, placer, pointed_thing, plantname)
or minetest.do_item_eat(hp_change, nil, itemstack, placer, pointed_thing)
end
end
@ -489,12 +408,11 @@ minetest.register_lbm({
name = "mcl_farming:growth",
nodenames = { "group:plant" },
run_at_every_load = true,
action = function(pos, node)
action = function(pos, node, dtime_s)
local identifier = plant_nodename_to_id_list[node.name]
if not identifier then
return
end
local low_speed = minetest.get_node({ x = pos.x, y = pos.y - 1, z = pos.z }).name ~= "mcl_farming:soil_wet"
mcl_farming:grow_plant(identifier, pos, node, false, false, low_speed)
if not identifier then return end
local low_speed = minetest.get_node(vector.offset(pos, 0, -1, 0)).name ~= "mcl_farming:soil_wet"
mcl_farming:grow_plant(identifier, pos, node, 0, false, low_speed)
end,
})

View File

@ -9,6 +9,8 @@ local function mcl_log(message)
end
end
mcl_hoppers = {}
--[[ BEGIN OF NODE DEFINITIONS ]]
local mcl_hoppers_formspec = table.concat({
@ -52,7 +54,7 @@ local function straight_hopper_act(pos, node, active_object_count, active_count_
mcl_util.hopper_push(pos, dst_pos)
local src_pos = vector.offset(pos, 0, 1, 0)
mcl_util.hopper_pull(pos, src_pos)
mcl_util.hopper_pull_to_inventory(minetest.get_meta(pos):get_inventory(), "main", src_pos, pos)
end
local function bent_hopper_act(pos, node, active_object_count, active_object_count_wider)
@ -91,9 +93,102 @@ local function bent_hopper_act(pos, node, active_object_count, active_object_cou
end
local src_pos = vector.offset(pos, 0, 1, 0)
mcl_util.hopper_pull(pos, src_pos)
mcl_util.hopper_pull_to_inventory(inv, "main", src_pos, pos)
end
--[[
Returns true if an item was pushed to the minecart
]]
local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
if not mcl_util.metadata_last_act(minetest.get_meta(dest_pos), "hopper_push_timer", 1) then return false end
local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
if not dest_inv then
mcl_log("No inv")
return false
end
local meta = minetest.get_meta(dest_pos)
local inv = meta:get_inventory()
if not inv then
mcl_log("No dest inv")
return
end
mcl_log("inv. size: " .. mc_ent._inv_size)
for i = 1, mc_ent._inv_size, 1 do
local stack = inv:get_stack("main", i)
mcl_log("i: " .. tostring(i))
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
mcl_log("Count: " .. tostring(stack:get_count()))
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
if not stack:get_name() or stack:get_name() ~= "" then
if dest_inv:room_for_item("main", stack:peek_item()) then
mcl_log("Room so unload")
dest_inv:add_item("main", stack:take_item())
inv:set_stack("main", i, stack)
-- Take one item and stop until next time
return
else
mcl_log("no Room")
end
else
mcl_log("nothing there")
end
end
end
--[[
Returns true if an item was pulled from the minecart
]]
local function hopper_pull_from_mc(mc_ent, dest_pos, inv_size)
if not mcl_util.metadata_last_act(minetest.get_meta(dest_pos), "hopper_pull_timer", 1) then return false end
local inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
if not inv then
mcl_log("No inv")
return false
end
local dest_meta = minetest.get_meta(dest_pos)
local dest_inv = dest_meta:get_inventory()
if not dest_inv then
mcl_log("No dest inv")
return false
end
mcl_log("inv. size: " .. mc_ent._inv_size)
for i = 1, mc_ent._inv_size, 1 do
local stack = inv:get_stack("main", i)
mcl_log("i: " .. tostring(i))
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
mcl_log("Count: " .. tostring(stack:get_count()))
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
if not stack:get_name() or stack:get_name() ~= "" then
if dest_inv:room_for_item("main", stack:peek_item()) then
mcl_log("Room so unload")
dest_inv:add_item("main", stack:take_item())
inv:set_stack("main", i, stack)
-- Take one item and stop until next time, report that we took something
return true
else
mcl_log("no Room")
end
else
mcl_log("nothing there")
end
end
end
mcl_hoppers.pull_from_minecart = hopper_pull_from_mc
-- Downwards hopper (base definition)
---@type node_definition
@ -199,6 +294,46 @@ local def_hopper = {
minetest.log("action", player:get_player_name() ..
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
end,
_mcl_minecarts_on_enter_below = function(pos, cart, next_dir)
-- Only pull to containers
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
cart:add_node_watch(pos)
hopper_push_to_mc(cart, pos, 5)
end
end,
_mcl_minecarts_on_enter_above = function(pos, cart, next_dir)
-- Only push to containers
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
cart:add_node_watch(pos)
hopper_pull_from_mc(cart, pos, 5)
end
end,
_mcl_minecarts_on_leave_above = function(pos, cart, next_dir)
if not cart then return end
cart:remove_node_watch(pos)
end,
_mcl_minecarts_node_on_step = function(pos, cart, dtime, cartdata)
if not cart then
minetest.log("warning", "trying to process hopper-to-minecart movement without luaentity")
return
end
local cart_pos = mcl_minecarts.get_cart_position(cartdata)
if not cart_pos then return false end
if vector.distance(cart_pos, pos) > 1.5 then
cart:remove_node_watch(pos)
return
end
if vector.direction(pos,cart_pos).y > 0 then
-- The cart is above us, pull from minecart
hopper_pull_from_mc(cart, pos, 5)
else
hopper_push_to_mc(cart, pos, 5)
end
return true
end,
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 4.8,
@ -404,6 +539,66 @@ local def_hopper_side = {
on_rotate = on_rotate,
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_minecarts_on_enter_below = function(pos, cart, next_dir)
-- Only push to containers
if cart and cart.groups and (cart.groups.container or 0) ~= 0 then
cart:add_node_watch(pos)
hopper_pull_from_mc(cart, pos, 5)
end
end,
_mcl_minecarts_on_leave_below = function(pos, cart, next_dir)
if not cart then return end
cart:remove_node_watch(pos)
end,
_mcl_minecarts_on_enter_side = function(pos, cart, next_dir, rail_pos)
if not cart then return end
-- Only try to push to minecarts when the spout position is pointed at the rail
local face = minetest.get_node(pos).param2
local dst_pos = {}
if face == 0 then
dst_pos = vector.offset(pos, -1, 0, 0)
elseif face == 1 then
dst_pos = vector.offset(pos, 0, 0, 1)
elseif face == 2 then
dst_pos = vector.offset(pos, 1, 0, 0)
elseif face == 3 then
dst_pos = vector.offset(pos, 0, 0, -1)
end
if dst_pos ~= rail_pos then return end
-- Only push to containers
if cart.groups and (cart.groups.container or 0) ~= 0 then
cart:add_node_watch(pos)
end
hopper_push_to_mc(cart, pos, 5)
end,
_mcl_minecarts_on_leave_side = function(pos, cart, next_dir)
if not cart then return end
cart:remove_node_watch(pos)
end,
_mcl_minecarts_node_on_step = function(pos, cart, dtime, cartdata)
if not cart then return end
local cart_pos = mcl_minecarts.get_cart_position(cartdata)
if not cart_pos then return false end
if vector.distance(cart_pos, pos) > 1.5 then
cart:remove_node_watch(pos)
return false
end
if cart_pos.y == pos.y then
hopper_push_to_mc(cart, pos, 5)
elseif cart_pos.y > pos.y then
hopper_pull_from_mc(cart, pos, 5)
end
return true
end,
_mcl_blast_resistance = 4.8,
_mcl_hardness = 3,
}
@ -435,87 +630,6 @@ minetest.register_node("mcl_hoppers:hopper_side_disabled", def_hopper_side_disab
--[[ END OF NODE DEFINITIONS ]]
local function hopper_pull_from_mc(mc_ent, dest_pos, inv_size)
local inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
if not inv then
mcl_log("No inv")
return false
end
local dest_meta = minetest.get_meta(dest_pos)
local dest_inv = dest_meta:get_inventory()
if not dest_inv then
mcl_log("No dest inv")
return
end
mcl_log("inv. size: " .. mc_ent._inv_size)
for i = 1, mc_ent._inv_size, 1 do
local stack = inv:get_stack("main", i)
mcl_log("i: " .. tostring(i))
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
mcl_log("Count: " .. tostring(stack:get_count()))
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
if not stack:get_name() or stack:get_name() ~= "" then
if dest_inv:room_for_item("main", stack:peek_item()) then
mcl_log("Room so unload")
dest_inv:add_item("main", stack:take_item())
inv:set_stack("main", i, stack)
-- Take one item and stop until next time
return
else
mcl_log("no Room")
end
else
mcl_log("nothing there")
end
end
end
local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
if not dest_inv then
mcl_log("No inv")
return false
end
local meta = minetest.get_meta(dest_pos)
local inv = meta:get_inventory()
if not inv then
mcl_log("No dest inv")
return
end
mcl_log("inv. size: " .. mc_ent._inv_size)
for i = 1, mc_ent._inv_size, 1 do
local stack = inv:get_stack("main", i)
mcl_log("i: " .. tostring(i))
mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
mcl_log("Count: " .. tostring(stack:get_count()))
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
if not stack:get_name() or stack:get_name() ~= "" then
if dest_inv:room_for_item("main", stack:peek_item()) then
mcl_log("Room so unload")
dest_inv:add_item("main", stack:take_item())
inv:set_stack("main", i, stack)
-- Take one item and stop until next time
return
else
mcl_log("no Room")
end
else
mcl_log("nothing there")
end
end
end
--[[ BEGIN OF ABM DEFINITONS ]]
@ -555,7 +669,7 @@ minetest.register_abm({
and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
mcl_log("Minecart close enough")
if entity.name == "mcl_minecarts:hopper_minecart" then
hopper_pull_from_mc(entity, pos, 5)
--hopper_pull_from_mc(entity, pos, 5)
elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
hopper_pull_from_mc(entity, pos, 27)
end
@ -564,7 +678,7 @@ minetest.register_abm({
and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
mcl_log("Minecart close enough")
if entity.name == "mcl_minecarts:hopper_minecart" then
hopper_push_to_mc(entity, pos, 5)
--hopper_push_to_mc(entity, pos, 5)
elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
hopper_push_to_mc(entity, pos, 27)
end

View File

@ -196,7 +196,7 @@ function kelp.find_unsubmerged(pos, node, height)
for i=1,height do
walk_pos.y = y + i
local walk_node = mt_get_node(walk_pos)
if not kelp.is_submerged(walk_node) then
if walk_node.name ~= "ignore" and not kelp.is_submerged(walk_node) then
return walk_pos, walk_node, height, i
end
end

View File

@ -3,17 +3,38 @@
-- Adapted for MineClone 2!
-- Imports
local create_minecart = mcl_minecarts.create_minecart
local get_cart_data = mcl_minecarts.get_cart_data
local save_cart_data = mcl_minecarts.save_cart_data
-- Node names (Don't use aliases!)
tsm_railcorridors.nodes = {
dirt = "mcl_core:dirt",
chest = "mcl_chests:chest",
rail = "mcl_minecarts:rail",
rail = "mcl_minecarts:rail_v2",
torch_floor = "mcl_torches:torch",
torch_wall = "mcl_torches:torch_wall",
cobweb = "mcl_core:cobweb",
spawner = "mcl_mobspawners:spawner",
}
local update_rail_connections = mcl_minecarts.update_rail_connections
local rails_to_update = {}
tsm_railcorridors.on_place_node = {
[tsm_railcorridors.nodes.rail] = function(pos, node)
rails_to_update[#rails_to_update + 1] = pos
end,
}
tsm_railcorridors.on_start = function()
rails_to_update = {}
end
tsm_railcorridors.on_finish = function()
for _,pos in pairs(rails_to_update) do
update_rail_connections(pos, {legacy = true, ignore_neighbor_connections = true})
end
end
local mg_name = minetest.get_mapgen_setting("mg_name")
if mg_name == "v6" then
@ -36,7 +57,11 @@ else
end
end
tsm_railcorridors.carts = { "mcl_minecarts:chest_minecart" }
tsm_railcorridors.carts = { "mcl_minecarts:chest_minecart", "mcl_minecarts:hopper_minecart", "mcl_minecarts:minecart" }
local has_loot = {
["mcl_minecarts:chest_minecart"] = true,
["mcl_minecarts:hopper_minceart"] = true,
}
-- This is called after a spawner has been placed by the game.
-- Use this to properly set up the metadata and stuff.
@ -46,17 +71,32 @@ function tsm_railcorridors.on_construct_spawner(pos)
mcl_mobspawners.setup_spawner(pos, "mobs_mc:cave_spider", 0, 7)
end
-- This is called after a cart has been placed by the game.
-- Use this to properly set up entity metadata and stuff.
-- * entity_id - type of cart to create
-- * pos: Position of cart
-- * cart: Cart entity
function tsm_railcorridors.on_construct_cart(_, cart, pr_carts)
local l = cart:get_luaentity()
local inv = mcl_entity_invs.load_inv(l,27)
local items = tsm_railcorridors.get_treasures(pr_carts)
mcl_loot.fill_inventory(inv, "main", items, pr_carts)
mcl_entity_invs.save_inv(l)
-- * pr: pseudorandom
function tsm_railcorridors.create_cart_staticdata(entity_id, pos, pr)
local uuid = create_minecart(entity_id, pos, vector.new(1,0,0))
-- Fill the cart with loot
local cartdata = get_cart_data(uuid)
if cartdata and has_loot[entity_id] then
local items = tsm_railcorridors.get_treasures(pr)
-- TODO: determine if we should convert to use mcl_loot
-- mcl_loot.fill_inventory(inv, "main", items, pr_carts)
-- Convert from ItemStack to itemstrings
for k,item in pairs(items) do
items[k] = item:to_string()
end
cartdata.inventory = items
print("cartdata = "..dump(cartdata))
save_cart_data(uuid)
end
return minetest.serialize({ uuid=uuid, seq=1 })
end
-- Fallback function. Returns a random treasure. This function is called for chests
@ -104,11 +144,11 @@ function tsm_railcorridors.get_treasures(pr)
stacks_min = 3,
stacks_max = 3,
items = {
{ itemstring = "mcl_minecarts:rail", weight = 20, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_minecarts:rail_v2", weight = 20, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_torches:torch", weight = 15, amount_min = 1, amount_max = 16 },
{ itemstring = "mcl_minecarts:activator_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:detector_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:golden_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:activator_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:detector_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:golden_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
}
},
-- non-MC loot: 50% chance to add a minecart, offered as alternative to spawning minecarts on rails.

View File

@ -1,7 +1,9 @@
local pairs = pairs
local tonumber = tonumber
tsm_railcorridors = {}
tsm_railcorridors = {
after = {},
}
-- Load node names
dofile(minetest.get_modpath(minetest.get_current_modname()).."/gameconfig.lua")
@ -169,6 +171,10 @@ local function SetNodeIfCanBuild(pos, node, check_above, can_replace_rail)
(can_replace_rail and name == tsm_railcorridors.nodes.rail)
) then
minetest.set_node(pos, node)
local after = tsm_railcorridors.on_place_node[node.name]
if after then
after(pos, node)
end
return true
else
return false
@ -392,27 +398,6 @@ local function PlaceChest(pos, param2)
end
end
-- This function checks if a cart has ACTUALLY been spawned.
-- To be calld by minetest.after.
-- This is a workaround thanks to the fact that minetest.add_entity is unreliable as fuck
-- See: https://github.com/minetest/minetest/issues/4759
-- FIXME: Kill this horrible hack with fire as soon you can.
local function RecheckCartHack(params)
local pos = params[1]
local cart_id = params[2]
-- Find cart
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
if obj ~= nil and obj:get_luaentity().name == cart_id then
-- Cart found! We can now safely call the callback func.
-- (calling it earlier has the danger of failing)
minetest.log("info", "[tsm_railcorridors] Cart spawn succeeded: "..minetest.pos_to_string(pos))
tsm_railcorridors.on_construct_cart(pos, obj, pr_carts)
return
end
end
minetest.log("info", "[tsm_railcorridors] Cart spawn FAILED: "..minetest.pos_to_string(pos))
end
-- Try to place a cobweb.
-- pos: Position of cobweb
-- needs_check: If true, checks if any of the nodes above, below or to the side of the cobweb.
@ -935,13 +920,13 @@ local function spawn_carts()
-- See <https://github.com/minetest/minetest/issues/4759>
local cart_id = tsm_railcorridors.carts[cart_type]
minetest.log("info", "[tsm_railcorridors] Cart spawn attempt: "..minetest.pos_to_string(cpos))
minetest.add_entity(cpos, cart_id)
local cart_staticdata = nil
-- This checks if the cart is actually spawned, it's a giant hack!
-- Note that the callback function is also called there.
-- TODO: Move callback function to this position when the
-- minetest.add_entity bug has been fixed (supposedly in 5.9.0?)
minetest.after(3, RecheckCartHack, {cpos, cart_id})
-- Try to create cart staticdata
local hook = tsm_railcorridors.create_cart_staticdata
if hook then cart_staticdata = hook(cart_id, cpos, pr) end
minetest.add_entity(cpos, cart_id, cart_staticdata)
end
end
carts_table = {}
@ -950,7 +935,7 @@ end
-- Start generation of a rail corridor system
-- main_cave_coords is the center of the floor of the dirt room, from which
-- all corridors expand.
local function create_corridor_system(main_cave_coords)
local function create_corridor_system(main_cave_coords, pr)
-- Dirt room size
local maxsize = 6
@ -1111,7 +1096,15 @@ mcl_structures.register_structure("mineshaft",{
end
if p.y > -10 then return true end
InitRandomizer(blockseed)
create_corridor_system(p)
local hook = tsm_railcorridors.on_start
if hook then hook() end
create_corridor_system(p, pr)
local hook = tsm_railcorridors.on_finish
if hook then hook() end
return true
end,

View File

@ -1,7 +1,10 @@
local table = table
local storage = minetest.get_mod_storage()
-- Player state for public API
mcl_playerinfo = {}
local player_mod_metadata = {}
-- Get node but use fallback for nil or unknown
local function node_ok(pos, fallback)
@ -21,8 +24,6 @@ local function node_ok(pos, fallback)
return fallback
end
local time = 0
local function get_player_nodes(player_pos)
local work_pos = table.copy(player_pos)
@ -43,11 +44,10 @@ local function get_player_nodes(player_pos)
return node_stand, node_stand_below, node_head, node_feet, node_head_top
end
local time = 0
minetest.register_globalstep(function(dtime)
time = time + dtime
-- Run the rest of the code every 0.5 seconds
time = time + dtime
if time < 0.5 then
return
end
@ -76,6 +76,23 @@ minetest.register_globalstep(function(dtime)
end)
function mcl_playerinfo.get_mod_meta(player_name, modname)
-- Load the player's metadata
local meta = player_mod_metadata[player_name]
if not meta then
meta = minetest.deserialize(storage:get_string(player_name))
end
if not meta then
meta = {}
end
player_mod_metadata[player_name] = meta
-- Get the requested module's section of the metadata
local mod_meta = meta[modname] or {}
meta[modname] = mod_meta
return mod_meta
end
-- set to blank on join (for 3rd party mods)
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
@ -87,7 +104,6 @@ minetest.register_on_joinplayer(function(player)
node_stand_below = "",
node_head_top = "",
}
end)
-- clear when player leaves
@ -96,3 +112,9 @@ minetest.register_on_leaveplayer(function(player)
mcl_playerinfo[name] = nil
end)
minetest.register_on_shutdown(function()
for name,data in pairs(player_mod_metadata) do
storage:set_string(name, minetest.serialize(data))
end
end)

View File

@ -514,21 +514,29 @@ minetest.register_globalstep(function(dtime)
return
end
-- Standing on soul sand? If so, walk slower (unless player wears Soul Speed boots)
if node_stand == "mcl_nether:soul_sand" then
-- TODO: Tweak walk speed
-- TODO: Also slow down mobs
-- Slow down even more when soul sand is above certain block
local boots = player:get_inventory():get_stack("armor", 5)
local soul_speed = mcl_enchanting.get_enchantment(boots, "soul_speed")
if soul_speed > 0 then
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", soul_speed * 0.105 + 1.3)
else
if node_stand_below == "mcl_core:ice" or node_stand_below == "mcl_core:packed_ice" or node_stand_below == "mcl_core:slimeblock" or node_stand_below == "mcl_core:water_source" then
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", 0.1)
local boots = player:get_inventory():get_stack("armor", 5)
local soul_speed = mcl_enchanting.get_enchantment(boots, "soul_speed")
-- Standing on a soul block? If so, check for speed bonus / penalty
if get_item_group(node_stand, "soul_block") ~= 0 then
-- Standing on soul sand? If so, walk slower (unless player wears Soul Speed boots, then apply bonus)
if node_stand == "mcl_nether:soul_sand" then
-- TODO: Tweak walk speed
-- TODO: Also slow down mobs
-- Slow down even more when soul sand is above certain block
if soul_speed > 0 then
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", soul_speed * 0.105 + 1.3)
else
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", 0.4)
if node_stand_below == "mcl_core:ice" or node_stand_below == "mcl_core:packed_ice" or node_stand_below == "mcl_core:slimeblock" or node_stand_below == "mcl_core:water_source" then
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", 0.1)
else
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", 0.4)
end
end
elseif soul_speed > 0 then
-- Standing on a different soul block? If so, apply Soul Speed bonus unconditionally
playerphysics.add_physics_factor(player, "speed", "mcl_playerplus:soul_speed", soul_speed * 0.105 + 1.3)
end
else
playerphysics.remove_physics_factor(player, "speed", "mcl_playerplus:soul_speed")

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B