Compare commits

...

187 Commits

Author SHA1 Message Date
teknomunk 01f168a511 Flip powered special T-junction textures to match regular rails 2024-11-10 02:46:38 +01:00
teknomunk d1c4b3ddc2 Add in working curved variants of special rails 2024-11-10 02:46:38 +01:00
teknomunk bceb070849 Make powered rails always accelerate moving carts 2024-11-10 02:46:38 +01:00
teknomunk cd69597c6c Fix detector rail strangeness, prevent crash 2024-11-10 02:46:38 +01:00
teknomunk 4f913f4264 Use correct vectors for look directions east/west 2024-11-10 02:46:38 +01:00
teknomunk 4a88f976f9 Remove debug code 2024-11-10 02:46:38 +01:00
teknomunk 4e923219c7 Fix minecart entity rotation 2024-11-10 02:46:38 +01:00
teknomunk b653fab1ff Fix crash 2024-11-10 02:46:38 +01:00
teknomunk af24855d18 Make straight rails with a free end bend towards newly placed rail and form corners 2024-11-10 02:46:38 +01:00
teknomunk 38e5aada31 Finish restricting 45 degree movement code to horizontal plane 2024-11-10 02:46:38 +01:00
teknomunk 6ccfca2387 Fix vertical movement that was hitting 45 degree curve code, reset pitch on minecarts 2024-11-10 02:46:38 +01:00
teknomunk 1d10fdcac2 Fix friction on slopes, fix cart reversal when timestep is very small or zero 2024-11-10 02:46:38 +01:00
teknomunk a8314b1356 Allow functions for _rail_acceleration, make powered rail only accelerate carts when one side of the rail is a solid block or stairs 2024-11-10 02:46:38 +01:00
teknomunk 4e970a5ea1 Remove instance of debug logging, change movement.lua function export 2024-11-10 02:46:38 +01:00
teknomunk c7a829d59a Fix crash with minecart on activator rail 2024-11-10 02:46:38 +01:00
teknomunk e364722864 Fix cart pitch when detached 2024-11-10 02:46:38 +01:00
teknomunk e204e0fd3e Add workaround to prevent random detached inventory doesn't exist warning when right-clicking entities with inventory 2024-11-10 02:46:38 +01:00
teknomunk b39482fe32 Fix right-clicking nodes while holding rail 2024-11-10 02:46:38 +01:00
teknomunk e0e343d86b Add back shift+punch to immediately drop minecart 2024-11-10 02:46:38 +01:00
teknomunk 6183e71b12 Fix another crash, fix rail tee on texture 2024-11-10 02:46:38 +01:00
teknomunk e44ff54195 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-11-10 02:46:38 +01:00
teknomunk 517e5765ee Prevent trains from slowing on 45 degree track 2024-11-10 02:46:38 +01:00
teknomunk 08419ccacf Add guard that prevents crash when itemstack is nil 2024-11-10 02:46:38 +01:00
teknomunk 0f1208d7b4 Silence debug prints and logging 2024-11-10 02:46:38 +01:00
teknomunk d1452f44de Prevent removal of old minecarts 2024-11-10 02:46:38 +01:00
teknomunk 63edda42a7 Add legacy node conversion to vl_legacy and update rails.lua to use it 2024-11-10 02:46:38 +01:00
teknomunk 735ef088ce Make activated tnt minecarts glow in the dark, fix crash with lit tnt minecarts 2024-11-10 02:46:38 +01:00
teknomunk f99a3a8e26 Fix a couple of crashes (TNT minecart trying to update orientation after exploding, trying to punch/push a minecart not on track) 2024-11-10 02:46:38 +01:00
teknomunk b964fc3372 Expand mcl_util.hopper_pull() to mcl_util.hopper_pull_to_inventory() 2024-11-10 02:46:38 +01:00
teknomunk 66727aedda Add profiling code to force_get_node() 2024-11-10 02:46:38 +01:00
teknomunk 70bb65ac1c Address additional review comments 2024-11-10 02:46:38 +01:00
teknomunk c33fb223da Update API documentation to always use , add compatibility shim to mcl_minecarts.is_rail() and mcl_minecarts.ge_rail_direction() 2024-11-10 02:46:38 +01:00
teknomunk b6ca0e6af5 Rewrite mcl_util.hopper_pull in terms of mcl_util.hopper_pull_to_inventory 2024-11-10 02:46:38 +01:00
teknomunk 665e8fec52 Correct documentation per review 2024-11-10 02:46:38 +01:00
teknomunk 16889c5d38 Switch over to using vl_legacy for item conversion in player inventories 2024-11-10 02:46:38 +01:00
teknomunk 9615d44332 Fix typo 2024-11-10 02:46:38 +01:00
teknomunk fac22d53d4 Register rail conversions 2024-11-10 02:46:38 +01:00
teknomunk d568adb931 Implement vl_legacy deprecated function and item conversion APIs 2024-11-10 02:46:38 +01:00
teknomunk 45ce26124c Fix crashes 2024-11-10 02:46:38 +01:00
teknomunk 90c6fd9f75 Make old rails have a drawtype, make update lbm always run 2024-11-10 02:46:38 +01:00
teknomunk 8334ccf4aa Move the various rails to their own files, code cleanup 2024-11-10 02:46:38 +01:00
teknomunk 96f6624684 Remove undefined global for optional environmental physics 2024-11-10 02:46:38 +01:00
teknomunk 166c19e8f3 Remove Emerge-0 warning that occurs when placing mineshafts 2024-11-10 02:46:38 +01:00
teknomunk 90f51af90e Restore 45 degree cart movement, remove warning about unknown global 2024-11-10 02:46:38 +01:00
teknomunk f3d2c8b877 Complete rework of curve/tee rail direction functions 2024-11-10 02:46:38 +01:00
teknomunk 6849bcc518 Rework rail_dir_curve to significantly reduce code size 2024-11-10 02:46:38 +01:00
teknomunk b815f82de5 Convert curved rails direction code to use fourdir 2024-11-10 02:46:38 +01:00
teknomunk 3feadcb9f9 Change verticle offset for testing reattaching to rail to 0.55, which is a bit more than the stair step height 2024-11-10 02:46:38 +01:00
teknomunk a389ecebc4 Fix cart detaching without unregistering from everything 2024-11-10 02:46:38 +01:00
teknomunk e8b5c9f9de Fix typo, set use_texture_alpha = clip for all rail 2024-11-10 02:46:38 +01:00
teknomunk 166cd93298 Fix several undefined global warnings, fix cart movement when over maximum speed, fix cart reattachment to sloped track 2024-11-10 02:46:38 +01:00
teknomunk 0b5f6544ce Revert changed made to debug minecart-updates integration into tsm_railcorridors 2024-11-10 02:46:38 +01:00
teknomunk e6a0f01cde Make punch move minecarts a little, comment out more debug prints 2024-11-10 02:46:38 +01:00
teknomunk 2c460d5e30 Fix visual artifacts on the sides of rails 2024-11-10 02:46:38 +01:00
teknomunk c1398b52cc Stop carts from reversing when they stop, make stopped carts try to start moving in the direction the player is facing 2024-11-10 02:46:38 +01:00
teknomunk 125ca644b5 Fix crash after entering a minecart not on rails 2024-11-10 02:46:38 +01:00
teknomunk da5ab60545 Fix placed rail conversion, start automatic inventory rail conversion 2024-11-10 02:46:38 +01:00
teknomunk 7959108245 Fix players repelling carts with new player metadata system 2024-11-10 02:46:38 +01:00
teknomunk fdf673162a Cleanup debug prints 2024-11-10 02:46:38 +01:00
teknomunk 6dc721f9cf Add documentation for newly exposed attach_driver 2024-11-10 02:46:38 +01:00
teknomunk af0e4d12af 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-11-10 02:46:38 +01:00
teknomunk 46028b1fc0 More fixes for minecart-hopper movement 2024-11-10 02:46:38 +01:00
teknomunk 7be3659fe1 Get rail placement creating corners that lead into a downward sloped rail 2024-11-10 02:46:38 +01:00
teknomunk c6eb2f23d5 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-11-10 02:46:38 +01:00
teknomunk 578de9c398 Fix hopper-minecart interaction, convert ipairs(table) to use for i=1,#table instead 2024-11-10 02:46:38 +01:00
teknomunk 11d8e46e96 Update mineshafts for new rail and minecarts, add loot to generated chest and hopper minecarts (and remove notes about a hack) 2024-11-10 02:46:38 +01:00
teknomunk 9f8559a642 Give carts a small vertical lift when pushed to allow them to get back on rails 2024-11-10 02:46:38 +01:00
teknomunk 418efda348 Stop rail from being placed directly above rail (floating in air) 2024-11-10 02:46:38 +01:00
teknomunk a65d043e15 Fix sloped power,activator and detector rails, remove debug print 2024-11-10 02:46:38 +01:00
teknomunk ac44d93aa4 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-11-10 02:46:38 +01:00
teknomunk 68e7a6e02c Add documentation on the rail 2024-11-10 02:46:38 +01:00
teknomunk 46ec4db298 Add documentation on file structure and overviewes of each file 2024-11-10 02:46:38 +01:00
teknomunk 2b6dba20be Fix crashes, fix link in documentation 2024-11-10 02:46:38 +01:00
teknomunk 2610754ee6 More documentation, add myself to copyright list in README.txt 2024-11-10 02:46:38 +01:00
teknomunk 44875440eb More minor changes to API.md, start overall implementation documentation 2024-11-10 02:46:38 +01:00
teknomunk 60ef68b7c7 Fix table of contents 2024-11-10 02:46:38 +01:00
teknomunk c3c6d2861e 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-11-10 02:46:38 +01:00
teknomunk 474eed5e8f Nearly finish API documentation, create mcl_minecarts.add_blocks_to_map() 2024-11-10 02:46:38 +01:00
teknomunk 845d92aef6 Continue writing API documentation, update call signatures for a couple of API functions 2024-11-10 02:46:38 +01:00
teknomunk 17360f42de Change document formatting, finally move cactus cart dropping to node definition for mcl_core:cactus 2024-11-10 02:46:38 +01:00
teknomunk 2447632d57 Correct crashes/item duplication with dropping carts, start API documentation 2024-11-10 02:46:38 +01:00
teknomunk 200f72c2f1 Fix cart controls, cart pushing 2024-11-10 02:46:38 +01:00
teknomunk d8ad11781b Fix typo in rail replacement mapping, fix several crashes 2024-11-10 02:46:38 +01:00
teknomunk 2e20ea21bd Implement movement thru tee rails 2024-11-10 02:46:38 +01:00
teknomunk 0734ce73cd Tune respawn distance limit 2024-11-10 02:46:38 +01:00
teknomunk efd171252f Fix crashes 2024-11-10 02:46:38 +01:00
teknomunk d2f8676df2 Remove memory leak for cart data, check distance to players before respawning distant carts to prevent adding entities that are immediately inactivated 2024-11-10 02:46:38 +01:00
teknomunk f86ff41554 Implement offline/out of range minecart movement and fix minecart respawning, remove railtype tracking 2024-11-10 02:46:38 +01:00
teknomunk 252179c695 Remove do_movement dependency on the existence of a cart luaentity 2024-11-10 02:46:38 +01:00
teknomunk 6cf8c640ca 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-11-10 02:46:38 +01:00
teknomunk 12df6f37dc Make trains containing a player in a minecart function, minor cleanup in mcl_playerinfo 2024-11-10 02:46:38 +01:00
teknomunk b7ba0c8607 Fix crashes in train logic, allow breaking apart trains 2024-11-10 02:46:38 +01:00
teknomunk c7fd179a35 Implement train reversing 2024-11-10 02:46:38 +01:00
teknomunk c69ab674d3 Repair vectors in cart data, mostly fix train movement bugs (still possible to have a furnace minecart flip, without the train also flipping) 2024-11-10 02:46:38 +01:00
teknomunk 5f5ec96394 Add cart entity respawn/destroy to match cart data (partially working) 2024-11-10 02:46:38 +01:00
teknomunk 3320fe768b 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-11-10 02:46:38 +01:00
teknomunk 4e27774679 Fix rail detach crash, make tnt minecarts explode if they hit something hard (off rails) 2024-11-10 02:46:38 +01:00
teknomunk 4943fff0d5 Make sure carts get detatch if the rail under them is removed 2024-11-10 02:46:38 +01:00
teknomunk 7aa83ac15c Fixish reorganizing, initial train implementation 2024-11-10 02:46:38 +01:00
teknomunk 88ffa84dc0 Major reorganization, start setup for trains 2024-11-10 02:46:38 +01:00
teknomunk a4c60cfa4a Make sure carts that collide move in the same direction the colliding cart was 2024-11-10 02:46:38 +01:00
teknomunk b815253704 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-11-10 02:46:38 +01:00
teknomunk 233e253a5d Harden against unknown nodes 2024-11-10 02:46:38 +01:00
teknomunk 39f22e3769 Allow players to push minecarts that are not on track 2024-11-10 02:46:38 +01:00
teknomunk 556c29cf0c 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-11-10 02:46:38 +01:00
teknomunk 8702911da8 Fix rail movement regressions 2024-11-10 02:46:38 +01:00
teknomunk b02b68148c Move cart code to its own file, more code cleanup, add aliases for old track items 2024-11-10 02:46:38 +01:00
teknomunk 75304d2e75 Cleanup code, restore uphill/downhill cart movement, completely remove old rail 2024-11-10 02:46:38 +01:00
teknomunk 8478269dd9 Get rail reattachment (especially after jumps) working correctly 2024-11-10 02:46:38 +01:00
teknomunk 35af50bce0 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-11-10 02:46:38 +01:00
teknomunk d5c9a56e74 Silence unmaskable print statements 2024-11-10 02:46:38 +01:00
teknomunk 641626e505 Add immortal item entity support, add legacy rail conversion that uses immortal item drops for corners/tees/crosses that are no longer possible 2024-11-10 02:46:38 +01:00
teknomunk 64760f7d9d Fix more rail connection bugs 2024-11-10 02:46:38 +01:00
teknomunk 2259f5249b Get sloped connections working correctly 2024-11-10 02:46:38 +01:00
teknomunk 81c77f039e Re-enable rule for powering rail from underneath, have stairs block minecart movement, fix crash when lightning strikes a minecart 2024-11-10 02:46:38 +01:00
teknomunk 00dccd1823 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-11-10 02:46:38 +01:00
teknomunk f18bd9b8e7 Update all rail types to new version 2024-11-10 02:46:38 +01:00
teknomunk 8773e515d6 Reorganize 2024-11-10 02:46:38 +01:00
teknomunk d02d1b7b05 Finish reverting 08b41a3b39 2024-11-10 02:46:38 +01:00
teknomunk d7294d156d Enable new track with get_next_dir handlers 2024-11-10 02:46:38 +01:00
teknomunk 0b7a6c444f Change connection rules again to allow building parallel track, tees and crosses), start implementing rail rules callbacks 2024-11-10 02:46:38 +01:00
teknomunk 126b667527 Add sloped rail 2024-11-10 02:46:38 +01:00
teknomunk 95fc97e6b8 Fix rail visuals, add switch operation 2024-11-10 02:46:38 +01:00
teknomunk b8496d1ad3 Implement initial rail connection logic (no vertical track yet), experiment with texture modifiers and gravel underlay for display (not working) 2024-11-10 02:46:38 +01:00
teknomunk fc64ff5c41 Start implementing new rail nodes 2024-11-10 02:46:38 +01:00
teknomunk e9116457bc Implement minecart with command block 2024-11-10 02:46:38 +01:00
teknomunk 70e94d3745 Create mesecons command API and modify commandblock to use it 2024-11-10 02:46:38 +01:00
teknomunk baf58dbf21 Disable punch to move minecarts, implement punch to drop minecart, enable basic cart keyboard controls (accelerate and brake) 2024-11-10 02:46:38 +01:00
teknomunk 8a1170120c Remove cart oscillation when pushed 2024-11-10 02:46:38 +01:00
teknomunk f346b95620 Limit top speed of furnace minecarts to 4 blocks/second, limit total fuel time to 27 minutes 2024-11-10 02:46:38 +01:00
teknomunk 2468acb27b Fix bug with furnace minecart at max velocity (stopped until fuel ran out), move _fueltime into staticdata 2024-11-10 02:46:38 +01:00
teknomunk 44b46b0763 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-11-10 02:46:38 +01:00
teknomunk 408d17d2b1 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-11-10 02:46:38 +01:00
teknomunk ea6cb325aa Add API function to remove node watch 2024-11-10 02:46:38 +01:00
teknomunk 9365e16a6d 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-11-10 02:46:38 +01:00
teknomunk 5cc06160fb Start adding hooks for implpementing minecart with command block 2024-11-10 02:46:38 +01:00
teknomunk 09c3c5efe5 Make minecarts solid and add players pushing 2024-11-10 02:46:38 +01:00
teknomunk ed6ef684b2 Fix forwards/backwars tilt in all directions 2024-11-10 02:46:38 +01:00
teknomunk 74345fb555 Prevent players from entering minecarts when sneaking, prevents players from causing MineClone2/MineClone2#3188 2024-11-10 02:46:38 +01:00
teknomunk 8bd7efc25c Increase default track friction, disable right-click to exit minecarts 2024-11-10 02:46:38 +01:00
teknomunk 19a9e9de1b 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-11-10 02:46:38 +01:00
teknomunk b6d30919cf 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-11-10 02:46:38 +01:00
teknomunk 1e621ab0e7 Mostly fix carts stopping between powered rails (there is still some strangeness with acceleration physics) 2024-11-10 02:46:38 +01:00
teknomunk cd32b7865f Fix diagonal movement 2024-11-10 02:46:38 +01:00
teknomunk 0050b2dd62 Add diagonal track movement on zig-zag track, rewrite mcl_minecarts:get_rail_direction 2024-11-10 02:46:38 +01:00
teknomunk 397ad0c64f Make TNT minecarts available in creative menu 2024-11-10 02:46:38 +01:00
teknomunk 83ad76d3ba Implement custom item dropper handlers, implement droppers placing minecarts 2024-11-10 02:46:38 +01:00
teknomunk f122e38f3c Hopper minecarts pull from containers above rail 2024-11-10 02:46:38 +01:00
teknomunk 698d038e10 Rework in preparation to add code to pull from containers into the hopper minecart 2024-11-10 02:46:38 +01:00
teknomunk 9b89b200aa Move fiction constant to top of file, suppress cart flips when direction reverses due to gravity or end of track 2024-11-10 02:46:38 +01:00
teknomunk 1fb1865052 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-11-10 02:46:38 +01:00
teknomunk 66164e1f0e Remove dip into the ground that occured when gravity caused the cart to reverse directions 2024-11-10 02:46:38 +01:00
teknomunk 865a278554 Implement gravity, move orientation update to own function, fix cart stopping in process_acceleration 2024-11-10 02:46:38 +01:00
teknomunk c13c800cd8 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-11-10 02:46:38 +01:00
teknomunk bb8a2ee637 Make minecart always stop at correct location at end of track, fix crash when placing chest minecart after changing how staticdata is handled 2024-11-10 02:46:38 +01:00
teknomunk a67203c378 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-11-10 02:46:38 +01:00
teknomunk 0828372339 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-11-10 02:46:38 +01:00
teknomunk f476e43084 Fix initial_properties for minecarts 2024-11-10 02:46:38 +01:00
teknomunk d45bf2d65f Change left,right and back vectors to matrix math results with no branching 2024-11-10 02:46:38 +01:00
teknomunk 89867adbfe Remove now unused properties from minecart definition, convert more vectors to use vector.new syntax 2024-11-10 02:46:38 +01:00
teknomunk 7c898db3a2 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-11-10 02:46:38 +01:00
teknomunk afbe257bee Change staticdata serialization (with migration from old data), disable debugging code used to investigate MineClone2/MineClone2#2446 2024-11-10 02:46:38 +01:00
teknomunk c64d9eea02 Change to vector.new from {x=...}, relocate movement code to own function for future changes 2024-11-10 02:46:38 +01:00
kno10 b540e6c77b Improve head swivel code (#4622)
* Utilize the minetest 5.9.0 API that uses radians not degree.
* Simplify computations to make this more efficient, in particular by querying and updating the bone position less frequently.
* Resolves minetest warning `Deprecated call to set_bone_position, use set_bone_override instead` in this location, but other uses remain.
* `mcl_util.set_bone_position` not modified, because it redundantly compares to the previous rotation once more.

Reviewed-on: VoxeLibre/VoxeLibre#4622
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-11-10 02:41:55 +01:00
kno10 d49426d453 Cleanup of mcl_core/functions (#4592)
Cleanup of mods/ITEMS/mcl_core/functions.lua

This improves several further ABMs such as vine growing, and uses the `vector` API instead of tables.

Reviewed-on: VoxeLibre/VoxeLibre#4592
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-11-10 02:32:51 +01:00
the-real-herowl 2b7b7f1872 Merge pull request 'Improve plant growth system, add moisture level' (#4681) from kno10/VoxeLibre:pumpkin-melon-growth-1 into master
Reviewed-on: VoxeLibre/VoxeLibre#4681
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
2024-11-10 02:11:37 +01:00
kno10 b5afa34469 Remove "wet" metadata altogether 2024-11-10 02:11:37 +01:00
kno10 ebf6cf32e8 meta:set_private("wet"), require only walkable nodes 2024-11-10 02:11:37 +01:00
kno10 a8318f6600 simplify catch-up LBM logic 2024-11-10 02:11:37 +01:00
kno10 fa7a7f4e81 more fixes to plant growth 2024-11-10 02:11:37 +01:00
kno10 c097c65262 adjust growth rates again 2024-11-10 02:11:37 +01:00
kno10 220a7b06e6 code review feedback 2024-11-10 02:11:37 +01:00
kno10 540a070c59 always use day light level, more fixes 2024-11-10 02:11:37 +01:00
kno10 78a958db4e Double the odds, to halve the ABM frequencies. 2024-11-10 02:11:37 +01:00
kno10 e9453d6210 Add plant growth speed option, drop average light level
Closes: #4683 by removal
2024-11-10 02:11:37 +01:00
kno10 9376cf92b1 Adjust growth speeds 2024-11-10 02:11:37 +01:00
kno10 c4030115c4 improve moisture logic 2024-11-10 02:11:37 +01:00
kno10 e1ace4ad01 pumpkin/melon growth only tests one neighbor every time 2024-11-10 02:11:37 +01:00
the-real-herowl e3b7847df1 Merge pull request 'Shield improvements and bugfixes (fixes #2756)' (#4582) from shieldy_shields into master
Reviewed-on: VoxeLibre/VoxeLibre#4582
Reviewed-by: kno10 <kno10@noreply.git.minetest.land>
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
2024-11-10 01:34:50 +01:00
Mikita Wiśniewski f86a641dfa Improve shield block code and unhardcode offhand group 2024-11-10 01:34:50 +01:00
Mikita Wiśniewski 084741b733 Fix using shield on unknown nodes and cleanup 2024-11-10 01:34:50 +01:00
Mikita Wiśniewski d5bc0613d8 Make node itemstack check in mcl_shields less hacky 2024-11-10 01:34:50 +01:00
Loveaabb f26c34e65f Bugfix: Shield fails to block arrows 2024-11-10 01:34:50 +01:00
Loveaabb 04e29c5796 Several improvements to the Shield 2024-11-10 01:34:50 +01:00
Elias Åström 45ae170447 Deduplicate shield slowdown removal code 2024-11-10 01:34:50 +01:00
Elias Åström d0d1217dec Remove unused code in mcl_privs 2024-11-10 01:34:50 +01:00
Elias Åström cffc8e0145 Fix loosing interact bug in mcl_shields 2024-11-10 01:34:50 +01:00
61 changed files with 5251 additions and 2643 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
@ -108,7 +113,11 @@ function mcl_entity_invs.show_inv_form(ent,player,text)
local playername = player:get_player_name()
minetest.show_formspec(playername, ent._inv_id, load_default_formspec (ent, text))
-- Workaround: wait at least 50ms to ensure that the detached inventory exists before
-- the formspec attempts to use it. (See https://git.minetest.land/VoxeLibre/VoxeLibre/issues/4670#issuecomment-84875)
minetest.after(0.05, function()
minetest.show_formspec(playername, ent._inv_id, load_default_formspec (ent, text))
end)
end
local function drop_inv(ent)

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,666 @@
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 movement = dofile(modpath.."/movement.lua")
assert(movement.do_movement)
assert(movement.do_detached_movement)
assert(movement.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
mod.detach_driver = detach_driver
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()
self._removed = true
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
movement.do_detached_movement(self, dtime)
else
mod.update_cart_orientation(self)
end
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
movement.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
movement.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)
mod.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
local 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,471 @@ 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 apply_connection_rules(node, nodedef, pos, rules, connections)
-- 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
end
local function is_rail_end_connected(pos, dir)
-- Handle new track types that have track-specific direction handler
local node = force_get_node(pos)
local get_next_dir = get_path(minetest.registered_nodes,node.name,"_mcl_minecarts","get_next_dir")
if not get_next_dir then return false end
return get_next_dir(pos, dir, node) == dir
end
local function bend_straight_rail(pos, towards)
dir = CONNECTIONS[i]
local node = force_get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
-- Only bend rails
local rail_type = minetest.get_item_group(node.name, "rail")
if rail_type == 0 then return end
-- Only bend unbent rails
if not nodedef._mcl_minecarts then return end
if node.name ~= nodedef._mcl_minecarts.base_name then return end
-- only bend rails that have at least one free end
local dir1 = minetest.fourdir_to_dir(node.param2)
local dir2 = minetest.fourdir_to_dir((node.param2+2)%4)
local dir1_connected = is_rail_end_connected(pos + dir1, dir2)
local dir2_connected = is_rail_end_connected(pos + dir2, dir1)
if dir1_connected and dir2_connected then return end
-- TODO: bend the rail
local connections = {
vector.direction(pos, towards),
}
if dir1_connected then
connections[#connections+1] = dir1
end
if dir2_connected then
connections[#connections+1] = dir2
end
local connections_mask = 0
for i = 1,#CONNECTIONS do
for j = 1,#connections do
if CONNECTIONS[i] == connections[j] then
connections_mask = bit.bor(connections_mask, bit.lshift(1, i -1))
end
end
end
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
apply_connection_rules(node, nodedef, pos, rules, connections_mask)
end
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
if not (opt and opt.no_bend_straights) then
for i = 1,#CONNECTIONS do
bend_straight_rail(vector.add(pos, CONNECTIONS[i]), pos)
end
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
apply_connection_rules(node, nodedef, pos, rules, connections)
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 get_next_dir = get_path(minetest.registered_nodes,node.name,"_mcl_minecarts","get_next_dir")
if not get_next_dir then return dir end
dir = 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)
if new_dir.y ~= 0 then return new_dir end
-- Check four 45 degree movement
local next_rails_dir = get_rail_direction_inner(vector.add(pos, new_dir), new_dir)
if next_rails_dir.y == 0 and vector.equals(next_rails_dir, dir) and not vector.equals(new_dir, next_rails_dir) then
return vector.add(new_dir, next_rails_dir)
end
return new_dir
end
local _2_pi = math.pi * 2
local _half_pi = math.pi * 0.5
local _quart_pi = math.pi * 0.5
local pi = math.pi
local rot_debug = {}
function mod.update_cart_orientation(self)
local staticdata = self._staticdata
local dir = staticdata.dir
-- Calculate an angle from the x,z direction components
local rot_y = math.atan2( dir.z, dir.x ) + ( 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()
local old_rot = vector.new(rot)
rot.y = (rot.y - _half_pi + _2_pi) % _2_pi
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
staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi
else
rot.y = rot_y
end
-- Forward/backwards tilt (pitch)
if dir.y < 0 then
rot.x = -_quart_pi
elseif dir.y > 0 then
rot.x = _quart_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
rot.y = (rot.y + _half_pi) % _2_pi
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,605 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local submod = {}
-- Constants
local mcl_debug,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecart_debug", "Minecart Debug")
--DEBUG = false
--mcl_debug,DEBUG = function(msg) print(msg) end,true
-- 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
submod.handle_cart_enter = handle_cart_enter
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 look_directions = {
mod.north,
mod.west,
mod.south,
mod.east,
}
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 = look_directions[ctrl.look or 0] or mod.north
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
local rail_accel = node_def._rail_acceleration
if type(rail_accel) == "function" then
acceleration = (rail_accel(pos, staticdata) or 0) * 4
else
acceleration = rail_accel * 4
end
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
if timestep <= 0.01 then
reverse_direction(staticdata)
end
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 < 0.025 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 ( 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
function submod.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
function submod.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
-- Reset pitch
local rot = self.object:get_rotation()
rot.y = 0
self.object:set_rotation(rot)
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
-- Reset pitch if still not attached
local rot = self.object:get_rotation()
rot.x = 0
self.object:set_rotation(rot)
end
--return do_movement, do_detatched_movement
return submod

View File

@ -1,53 +1,407 @@
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 = minetest.get_node(pointed_thing.under)
local node_name = node.name
-- Don't allow placing rail above rail
if minetest.get_item_group(node_name,"rail") ~= 0 then
return itemstack
end
-- Handle right-clicking nodes with right-click handlers
if placer and not placer:get_player_control().sneak then
local node_def = minetest.registered_nodes[node_name] or {}
local on_rightclick = node_def and node_def.on_rightclick
if on_rightclick then
return on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing) or itemstack
end
end
-- Place the rail
return minetest.item_place_node(itemstack, placer, pointed_thing)
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
local function make_mesecons(base_name, suffix, base_mesecons)
if not base_mesecons then
if suffix == "_tee_off" or suffix == "_tee_on" then
base_mesecons = {}
else
return
end
end
local mesecons = table.copy(base_mesecons)
if suffix == "_tee_off" then
mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}
local old_action_on = base_mesecons.effector and base_mesecons.effector.action_on
mesecons.effector.action_on = function(pos, node)
if old_action_on then old_action_on(pos, node) end
node.name = base_name.."_tee_on"
minetest.set_node(pos, node)
end
mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
elseif suffix == "_tee_on" then
mesecons.effector = base_mesecons.effector and table.copy(base_mesecons.effector) or {}
local old_action_off = base_mesecons.effector and base_mesecons.effector.action_off
mesecons.effector.action_off = function(pos, node)
if old_action_off then old_action_off(pos, node) end
node.name = base_name.."_tee_off"
minetest.set_node(pos, node)
end
mesecons.effector.rules = mesecons.effector.rules or mesecon.rules.alldirs
end
if mesecons.conductor then
mesecons.conductor = table.copy(base_mesecons.conductor)
if mesecons.conductor.onstate then
mesecons.conductor.onstate = base_mesecons.conductor.onstate..suffix
end
if base_mesecons.conductor.offstate then
mesecons.conductor.offstate = base_mesecons.conductor.offstate..suffix
end
end
minetest.log("mesecons for "..base_name..suffix.." is "..dump(mesecons))
return mesecons
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",
suffix = "",
},
})
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
suffix = "_sloped",
},
mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
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,
suffix = "",
},
}))
-- 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",
suffix = "_corner",
},
mesecons = make_mesecons(base_name, "_corner", def.mesecons),
}))
-- Tee variants
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
tiles = { tiles[3] },
mesecons = make_mesecons(base_name, "_tee_off", def.mesecons),
_mcl_minecarts = {
get_next_dir = rail_dir_tee_off,
railtype = "tee",
suffix = "_tee_off",
},
}))
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",
suffix = "_tee_on",
},
mesecons = make_mesecons(base_name, "_tee_on", def.mesecons),
}))
-- 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",
suffix = "_sloped",
},
mesecons = make_mesecons(base_name, "_sloped", def.mesecons),
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",
suffix = "_cross",
},
mesecons = make_mesecons(base_name, "_cross", def.mesecons),
}))
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 +418,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,77 @@
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_curves_rail("mcl_minecarts:activator_rail_v2", {
"mcl_minecarts_rail_activator.png",
"mcl_minecarts_rail_activator_curved.png",
"mcl_minecarts_rail_activator_t_junction.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 = 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,
},
},
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_curves_rail("mcl_minecarts:activator_rail_v2_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_t_junction_powered.png",
"mcl_minecarts_rail_activator_crossing_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,
},
},
_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,70 @@
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_curves_rail("mcl_minecarts:detector_rail_v2",{
"mcl_minecarts_rail_detector.png",
"mcl_minecarts_rail_detector_curved.png",
"mcl_minecarts_rail_detector_t_junction.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 = 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 node_def = minetest.registered_nodes[node.name]
node.name = "mcl_minecarts:detector_rail_v2_on"..node_def._mcl_minecarts.suffix
minetest.set_node( pos, node )
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_curves_rail("mcl_minecarts:detector_rail_v2_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_t_junction_powered.png",
"mcl_minecarts_rail_detector_crossing_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 node_def = minetest.registered_nodes[node.name]
node.name = "mcl_minecarts:detector_rail_v2"..node_def._mcl_minecarts.suffix
minetest.set_node( pos, node )
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,83 @@
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_curves_rail("mcl_minecarts:golden_rail_v2",{
"mcl_minecarts_rail_golden.png",
"mcl_minecarts_rail_golden_curved.png",
"mcl_minecarts_rail_golden_t_junction.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 = 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,
},
},
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_curves_rail("mcl_minecarts:golden_rail_v2_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_t_junction_powered.png",
"mcl_minecarts_rail_golden_crossing_powered.png",
},{
_doc_items_create_entry = false,
_rail_acceleration = function(pos, staticdata)
if staticdata.velocity ~= 0 then
return 4
end
local dir = mod.get_rail_direction(pos, staticdata.dir, nil, nil, staticdata.railtype)
local node_a = minetest.get_node(vector.add(pos, dir))
local node_b = minetest.get_node(vector.add(pos, -dir))
local has_adjacent_solid = minetest.get_item_group(node_a.name, "solid") ~= 0 or
minetest.get_item_group(node_b.name, "solid") ~= 0 or
minetest.get_item_group(node_a.name, "stair") ~= 0 or
minetest.get_item_group(node_b.name, "stair") ~= 0
if has_adjacent_solid then
return 4
else
return 0
end
end,
_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,
},
},
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

@ -5,6 +5,7 @@ local validate_vector = mcl_util.validate_vector
local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
local DEFAULT_FALL_SPEED = -9.81*1.5
local PI_THIRD = math.pi / 3 -- 60 degrees
local PATHFINDING = "gowp"
@ -294,86 +295,66 @@ function mcl_mobs:set_animation(self, anim)
self:set_animation(anim)
end
local function dir_to_pitch(dir)
--local dir2 = vector.normalize(dir)
local xz = math.abs(dir.x) + math.abs(dir.z)
return -math.atan2(-dir.y, xz)
end
local function who_are_you_looking_at (self, dtime)
local pos = self.object:get_pos()
if self.order == "sleep" then
self._locked_object = nil
return
end
local stop_look_at_player_chance = math.random(833/self.curiosity)
-- was 10000 - div by 12 for avg entities as outside loop
local stop_look_at_player = stop_look_at_player_chance == 1
local stop_look_at_player = math.random() * 833 <= self.curiosity
if self.attack then
if not self.target_time_lost then
self._locked_object = self.attack
else
self._locked_object = nil
end
self._locked_object = not self.target_time_lost and self.attack or nil
elseif self.following then
self._locked_object = self.following
elseif self._locked_object then
if stop_look_at_player then
--minetest.log("Stop look: ".. self.name)
self._locked_object = nil
end
if stop_look_at_player then self._locked_object = nil end
elseif not self._locked_object then
if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then
--minetest.log("Change look check: ".. self.name)
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
local chance = 150/self.curiosity
if chance < 1 then chance = 1 end
local look_at_player_chance = math.random(chance)
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
-- then div by 20 as less freq lookup
local look_at_player = look_at_player_chance == 1
local pos = self.object:get_pos()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do
if obj:is_player() and vector.distance(pos,obj:get_pos()) < 4 then
--minetest.log("Change look to player: ".. self.name)
if obj:is_player() and vector.distance(pos, obj:get_pos()) < 4 then
self._locked_object = obj
break
elseif obj:is_player() or (obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity()) then
if look_at_player then
--minetest.log("Change look to mob: ".. self.name)
elseif obj:is_player() or (obj:get_luaentity() and self ~= obj:get_luaentity() and obj:get_luaentity().name == self.name) then
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
-- then div by 20 as less freq lookup
if math.random() * 150 <= self.curiosity then
self._locked_object = obj
break
end
end
end
end
end
end
function mob_class:check_head_swivel(dtime)
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
who_are_you_looking_at(self, dtime)
who_are_you_looking_at (self, dtime)
local newr, oldp, oldr = vector.zero(), nil, nil
if self.object.get_bone_override then -- minetest >= 5.9
local ov = self.object:get_bone_override(self.head_swivel)
oldp, oldr = ov.position.vec, ov.rotation.vec
else -- minetest < 5.9
oldp, oldr = self.object:get_bone_position(self.head_swivel)
oldr = vector.apply(oldr, math.rad) -- old API uses radians
end
local final_rotation = vector.zero()
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
local locked_object = self._locked_object
if locked_object and (locked_object:is_player() or locked_object:get_luaentity()) and locked_object:get_hp() > 0 then
local _locked_object_eye_height = 1.5
if self._locked_object:get_luaentity() then
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height
end
if self._locked_object:is_player() then
_locked_object_eye_height = self._locked_object:get_properties().eye_height
if locked_object:is_player() then
_locked_object_eye_height = locked_object:get_properties().eye_height
elseif locked_object:get_luaentity() then
_locked_object_eye_height = locked_object:get_luaentity().head_eye_height
end
if _locked_object_eye_height then
local self_rot = self.object:get_rotation()
-- If a mob is attached, should we really be messing with what they are looking at?
-- Should this be excluded?
@ -381,40 +362,48 @@ function mob_class:check_head_swivel(dtime)
self_rot = self.object:get_attach():get_rotation()
end
local player_pos = self._locked_object:get_pos()
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier
local ps = self.object:get_pos()
ps.y = ps.y + self.head_eye_height * .7
local pt = locked_object:get_pos()
pt.y = pt.y + _locked_object_eye_height
local dir = vector.direction(ps, pt)
local mob_yaw = self_rot.y + math.atan2(dir.x, dir.z) + self.head_yaw_offset
local mob_pitch = math.asin(-dir.y) * self.head_pitch_multiplier
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then
final_rotation = vector.multiply(oldr, 0.9)
if (mob_yaw < -PI_THIRD or mob_yaw > PI_THIRD) and not (self.attack and self.state == "attack" and not self.runaway) then
newr = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then
if self.head_yaw == "y" then
final_rotation = vector.new(mob_pitch, mob_yaw, 0)
newr = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then
final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
newr = vector.new(mob_pitch, 0, -mob_yaw)
end
else
if self.head_yaw == "y" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, (mob_yaw-oldr.y)*.3+oldr.y, 0)
elseif self.head_yaw == "z" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)
newr = vector.new((mob_pitch-oldr.x)*.3+oldr.x, 0, ((mob_yaw-oldr.y)*.3+oldr.y)*-3)
end
end
end
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
final_rotation = vector.multiply(oldr, 0.9)
else
--final_rotation = vector.new(0,0,0)
elseif not locked_object and math.abs(oldr.y) > 0.05 and math.abs(oldr.x) < 0.05 then
newr = vector.multiply(oldr, 0.9)
end
-- 0.02 is about 1.14 degrees tolerance, to update less often
local newp = vector.new(0, self.bone_eye_height, self.horizontal_head_height)
if math.abs(oldr.x-newr.x) + math.abs(oldr.y-newr.y) + math.abs(oldr.z-newr.z) < 0.02 and vector.equals(oldp, newp) then return end
if self.object.get_bone_override then -- minetest >= 5.9
self.object:set_bone_override(self.head_swivel, {
position = { vec = newp, absolute = true },
rotation = { vec = newr, absolute = true } })
else -- minetest < 5.9
-- old API uses degrees not radians
self.object:set_bone_position(self.head_swivel, newp, vector.apply(newr, math.deg))
end
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horizontal_head_height), final_rotation)
end
function mob_class:set_animation_speed()
local v = self.object:get_velocity()
if v then

View File

@ -141,7 +141,7 @@ function mcl_mobs.register_mob(name, def)
local final_def = {
use_texture_alpha = def.use_texture_alpha,
head_swivel = def.head_swivel or nil, -- bool to activate this function
head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones
head_yaw_offset = math.rad(def.head_yaw_offset or 0), -- for wonkey model bones
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player

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

@ -257,10 +257,10 @@ function ARROW_ENTITY.on_step(self, dtime)
mcl_burning.set_on_fire(obj, 5)
end
if not self._in_player and not self._blocked then
obj:punch(self.object, 1.0, {
full_punch_interval=1.0,
damage_groups={fleshy=self._damage},
}, self.object:get_velocity())
mcl_util.deal_damage(obj, self._damage, {type = "arrow", source = self._shooter, direct = self.object})
if self._extra_hit_func then
self._extra_hit_func(obj)
end
if obj:is_player() then
if not mcl_shields.is_blocking(obj) then
local placement

View File

@ -168,7 +168,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
itemstack:get_meta():set_string("active", "true")
return itemstack
end,
groups = {weapon=1,weapon_ranged=1,bow=1,enchantability=1},
groups = {weapon=1,weapon_ranged=1,bow=1,cannot_block=1,enchantability=1},
_mcl_uses = 385,
})
@ -216,7 +216,7 @@ for level=0, 2 do
wield_scale = mcl_vars.tool_wield_scale,
stack_max = 1,
range = 0, -- Pointing range to 0 to prevent punching with bow :D
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, cannot_block=1, enchantability=1},
-- Trick to disable digging as well
on_use = function() return end,
on_drop = function(itemstack, dropper, pos)

View File

@ -158,7 +158,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
itemstack:get_meta():set_string("active", "true")
return itemstack
end,
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1},
_mcl_uses = 326,
})
@ -193,7 +193,7 @@ S("The speed and damage of the arrow increases the longer you charge. The regula
itemstack:get_meta():set_string("active", "true")
return itemstack
end,
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1,not_in_creative_inventory=1},
groups = {weapon=1,weapon_ranged=1,crossbow=1,cannot_block=1,enchantability=1,not_in_creative_inventory=1},
_mcl_uses = 326,
})
@ -238,7 +238,7 @@ for level=0, 2 do
wield_scale = mcl_vars.tool_wield_scale,
stack_max = 1,
range = 0, -- Pointing range to 0 to prevent punching with bow :D
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, cannot_block=1, bow=1, enchantability=1},
-- Trick to disable digging as well
on_use = function() return end,
on_drop = function(itemstack, dropper, pos)

File diff suppressed because it is too large Load Diff

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

@ -171,7 +171,8 @@ minetest.register_craft({
},
})
mcl_farming:add_plant("plant_beetroot", "mcl_farming:beetroot", {"mcl_farming:beetroot_0", "mcl_farming:beetroot_1", "mcl_farming:beetroot_2"}, 68, 3)
-- beetroots grow at 2/3rd of the default speed
mcl_farming:add_plant("plant_beetroot", "mcl_farming:beetroot", {"mcl_farming:beetroot_0", "mcl_farming:beetroot_1", "mcl_farming:beetroot_2"}, 8.7012, 35)
if minetest.get_modpath("doc") then
for i = 1, 2 do

View File

@ -118,7 +118,7 @@ minetest.register_craft({
}
})
mcl_farming:add_plant("plant_carrot", "mcl_farming:carrot", {"mcl_farming:carrot_1", "mcl_farming:carrot_2", "mcl_farming:carrot_3", "mcl_farming:carrot_4", "mcl_farming:carrot_5", "mcl_farming:carrot_6", "mcl_farming:carrot_7"}, 25, 20)
mcl_farming:add_plant("plant_carrot", "mcl_farming:carrot", {"mcl_farming:carrot_1", "mcl_farming:carrot_2", "mcl_farming:carrot_3", "mcl_farming:carrot_4", "mcl_farming:carrot_5", "mcl_farming:carrot_6", "mcl_farming:carrot_7"}, 5.8013, 35)
if minetest.get_modpath("doc") then
for i=2,7 do

View File

@ -123,10 +123,10 @@ local stem_def = {
}
-- Register stem growth
mcl_farming:add_plant("plant_melon_stem", "mcl_farming:melontige_unconnect", {"mcl_farming:melontige_1", "mcl_farming:melontige_2", "mcl_farming:melontige_3", "mcl_farming:melontige_4", "mcl_farming:melontige_5", "mcl_farming:melontige_6", "mcl_farming:melontige_7"}, 30, 5)
mcl_farming:add_plant("plant_melon_stem", "mcl_farming:melontige_unconnect", {"mcl_farming:melontige_1", "mcl_farming:melontige_2", "mcl_farming:melontige_3", "mcl_farming:melontige_4", "mcl_farming:melontige_5", "mcl_farming:melontige_6", "mcl_farming:melontige_7"}, 5.8014, 35)
-- Register actual melon, connected stems and stem-to-melon growth
mcl_farming:add_gourd("mcl_farming:melontige_unconnect", "mcl_farming:melontige_linked", "mcl_farming:melontige_unconnect", stem_def, stem_drop, "mcl_farming:melon", melon_base_def, 25, 15, "mcl_farming_melon_stem_connected.png^[colorize:#FFA800:127")
mcl_farming:add_gourd("mcl_farming:melontige_unconnect", "mcl_farming:melontige_linked", "mcl_farming:melontige_unconnect", stem_def, stem_drop, "mcl_farming:melon", melon_base_def, 5.8015, 35, "mcl_farming_melon_stem_connected.png^[colorize:#FFA800:127")
-- Items and crafting
minetest.register_craftitem("mcl_farming:melon_item", {

View File

@ -135,7 +135,7 @@ minetest.register_craft({
cooktime = 10,
})
mcl_farming:add_plant("plant_potato", "mcl_farming:potato", {"mcl_farming:potato_1", "mcl_farming:potato_2", "mcl_farming:potato_3", "mcl_farming:potato_4", "mcl_farming:potato_5", "mcl_farming:potato_6", "mcl_farming:potato_7"}, 19.75, 20)
mcl_farming:add_plant("plant_potato", "mcl_farming:potato", {"mcl_farming:potato_1", "mcl_farming:potato_2", "mcl_farming:potato_3", "mcl_farming:potato_4", "mcl_farming:potato_5", "mcl_farming:potato_6", "mcl_farming:potato_7"}, 5.8016, 35)
minetest.register_on_item_eat(function (hp_change, replace_with_item, itemstack, user, pointed_thing)

View File

@ -180,10 +180,10 @@ if minetest.get_modpath("mcl_armor") then
end
-- Register stem growth
mcl_farming:add_plant("plant_pumpkin_stem", "mcl_farming:pumpkintige_unconnect", {"mcl_farming:pumpkin_1", "mcl_farming:pumpkin_2", "mcl_farming:pumpkin_3", "mcl_farming:pumpkin_4", "mcl_farming:pumpkin_5", "mcl_farming:pumpkin_6", "mcl_farming:pumpkin_7"}, 30, 5)
mcl_farming:add_plant("plant_pumpkin_stem", "mcl_farming:pumpkintige_unconnect", {"mcl_farming:pumpkin_1", "mcl_farming:pumpkin_2", "mcl_farming:pumpkin_3", "mcl_farming:pumpkin_4", "mcl_farming:pumpkin_5", "mcl_farming:pumpkin_6", "mcl_farming:pumpkin_7"}, 5.8017, 35)
-- Register actual pumpkin, connected stems and stem-to-pumpkin growth
mcl_farming:add_gourd("mcl_farming:pumpkintige_unconnect", "mcl_farming:pumpkintige_linked", "mcl_farming:pumpkintige_unconnect", stem_def, stem_drop, "mcl_farming:pumpkin", pumpkin_base_def, 30, 15, "mcl_farming_pumpkin_stem_connected.png^[colorize:#FFA800:127")
mcl_farming:add_gourd("mcl_farming:pumpkintige_unconnect", "mcl_farming:pumpkintige_linked", "mcl_farming:pumpkintige_unconnect", stem_def, stem_drop, "mcl_farming:pumpkin", pumpkin_base_def, 5.8018, 35, "mcl_farming_pumpkin_stem_connected.png^[colorize:#FFA800:127")
-- Steal function to properly disconnect a carved pumpkin
pumpkin_face_base_def.after_destruct = minetest.registered_nodes["mcl_farming:pumpkin"].after_destruct

View File

@ -5,73 +5,99 @@
--
local math = math
local vector = vector
local random = math.random
local floor = math.floor
local plant_lists = {}
mcl_farming.plant_lists = plant_lists -- export
local plant_nodename_to_id_list = {}
local plant_nodename_to_id = {} -- map nodes to plants
local plant_step_from_name = {} -- map nodes to growth steps
local time_speed = tonumber(minetest.settings:get("time_speed")) or 72
local time_multiplier = time_speed > 0 and (86400 / time_speed) or 0
local growth_factor = tonumber(minetest.settings:get("vl_plant_growth")) or 1.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 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
-- wetness of the surroundings
-- dry farmland = 1 point
-- wet farmland = 3 points
-- center point gives + 1 point, so 2 resp. 4
-- neighbors only 25%
local function get_moisture_level(pos)
local n = vector.offset(pos, 0, -1, 0)
local totalm = 1
for z = -1,1 do
n.z = pos.z + z
for x = -1,1 do
n.x = pos.x + x
local ndef = minetest.registered_nodes[minetest.get_node(n).name]
local soil = ndef and ndef.groups.soil
if soil and soil >= 2 then
local m = soil > 2 and 3 or 1
-- corners have less weight
if x ~= 0 or z ~= 0 then m = m * 0.25 end
totalm = totalm + m
end
end
end
meta:set_float("last_gametime", current_game_time)
return (current_game_time - last_game_time) / approx_interval
return totalm
end
local function get_avg_light_level(pos)
local meta = minetest.get_meta(pos)
-- 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
summary, counter = math.ceil(summary * 0.5), 50
-- moisture penalty function:
-- 0.5 if both on the x axis and the z axis at least one of the same plants grows
-- 0.5 if at least one diagonal neighbor is the same
-- 1.0 otherwise
-- we cannot use the names directly, because growth is encoded in the names
local function get_same_crop_penalty(pos)
local name = minetest.get_node(pos).name
local plant = plant_nodename_to_id[name]
if not plant then return 1 end
local n = vector.copy(pos)
-- check adjacent positions, avoid vector allocations and reduce node accesses
n.x = pos.x - 1
local dx = plant_nodename_to_id[minetest.get_node(n).name] == plant
n.x = pos.x + 1
dx = dx or plant_nodename_to_id[minetest.get_node(n).name] == plant
if dx then -- no need to check z otherwise
n.x = pos.x
n.z = pos.z - 1
local dz = plant_nodename_to_id[minetest.get_node(n).name] == plant
n.z = pos.z + 1
dz = dz or plant_nodename_to_id[minetest.get_node(n).name] == plant
if dz then return 0.5 end
end
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)
-- check diagonals, clockwise
n.x, n.z = pos.x - 1, pos.z - 1
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
n.x = pos.x + 1
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
n.z = pos.z + 1
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
n.x = pos.x - 1
if plant_nodename_to_id[minetest.get_node(n).name] == plant then return 0.5 end
return 1
end
function mcl_farming:add_plant(identifier, full_grown, names, interval, chance)
interval = growth_factor > 0 and (interval / growth_factor) or 0
local plant_info = {}
plant_info.full_grown = full_grown
plant_info.names = names
plant_info.interval = interval
plant_info.chance = chance
plant_nodename_to_id[full_grown] = identifier
for _, nodename in pairs(names) do
plant_nodename_to_id_list[nodename] = identifier
plant_nodename_to_id[nodename] = identifier
end
plant_info.step_from_name = {}
for i, name in ipairs(names) do
plant_info.step_from_name[name] = i
plant_step_from_name[name] = i
end
plant_lists[identifier] = plant_info
if interval == 0 then return end -- growth disabled
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(vector.offset(pos, 0, -1, 0)).name ~= "mcl_farming:soil_wet"
mcl_farming:grow_plant(identifier, pos, node, 1, false, low_speed)
mcl_farming:grow_plant(identifier, pos, node, 1, false)
end,
})
end
@ -81,40 +107,30 @@ end
-- pos: Position
-- 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
-- ignore_light_water: if true, ignore light and water requirements for growing
-- 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)
stages = stages or 1
function mcl_farming:grow_plant(identifier, pos, node, stages, ignore_light_water)
-- number of missed interval ticks, for catch-up in block loading
local plant_info = plant_lists[identifier]
local intervals_counter = get_intervals_counter(pos, plant_info.interval, plant_info.chance)
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 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
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
if not plant_info then return end
if not ignore_light_water then
if (minetest.get_node_light(pos, 0.5) or 0) < 0 then return false end -- day light
local odds = floor(25 / (get_moisture_level(pos) * get_same_crop_penalty(pos))) + 1
for i = 1,stages do
-- compared to info from the MC wiki, our ABM runs a third as often, hence we use triple the chance
if random() * odds >= 3 then stages = stages - 1 end
end
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)
local step = plant_step_from_name[node.name]
if step == nil then return false end
minetest.set_node(pos, {
name = plant_info.names[step + stages] or plant_info.full_grown,
param = node.param,
param2 = node.param2,
})
return true
end
@ -157,40 +173,13 @@ end
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)
grow_interval = growth_factor > 0 and (grow_interval / growth_factor) or 0
local connected_stem_names = {
connected_stem_basename .. "_r",
connected_stem_basename .. "_l",
connected_stem_basename .. "_t",
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 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
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
-- Register gourd
if not gourd_def.after_destruct then
gourd_def.after_destruct = function(blockpos, oldnode)
@ -200,34 +189,21 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
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
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)
-- Register unconnected stem
@ -243,8 +219,8 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
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)
plant_nodename_to_id[stem_itemstring] = stem_itemstring
-- Register connected stems
@ -307,22 +283,14 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
sounds = mcl_sounds.node_sound_leaves_defaults(),
_mcl_blast_resistance = 0,
})
plant_nodename_to_id[connected_stem_names[i]] = stem_itemstring
if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", full_unconnected_stem, "nodes", connected_stem_names[i])
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
if grow_interval == 0 then return end
minetest.register_abm({
label = "Grow gourd stem to gourd (" .. full_unconnected_stem .. "" .. gourd_itemstring .. ")",
nodenames = { full_unconnected_stem },
@ -330,39 +298,27 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
interval = grow_interval,
chance = grow_chance,
action = function(stempos)
local light = minetest.get_node_light(stempos)
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
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
local light = minetest.get_node_light(stempos, 0.5)
if not light or light < 9 then return end
-- Pick one neighbor and check if it can be used to grow
local dir = random(1, 4) -- pick direction at random
local neighbor = (dir == 1 and vector.offset(stempos, 1, 0, 0))
or (dir == 2 and vector.offset(stempos, -1, 0, 0))
or (dir == 3 and vector.offset(stempos, 0, 0, 1))
or vector.offset(stempos, 0, 0, -1)
if minetest.get_node(neighbor).name ~= "air" then return end -- occupied
-- check for suitable floor -- in contrast to MC, we think everything solid is fine
local floorpos = vector.offset(neighbor, 0, -1, 0)
local floorname = minetest.get_node(floorpos).name
local floordef = minetest.registered_nodes[floorname]
if not floordef or not floordef.walkable then return end
-- check moisture level
local odds = floor(25 / (get_moisture_level(stempos) * get_same_crop_penalty(stempos))) + 1
-- we triple the odds, and rather call the ABM less often
if random() * odds >= 3 then return 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 })
@ -371,8 +327,7 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
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
if (floordef.groups.dirtifies_below_solid or 0) > 0 then
minetest.set_node(floorpos, { name = "mcl_core:dirt" })
end
end,
@ -409,10 +364,21 @@ minetest.register_lbm({
nodenames = { "group:plant" },
run_at_every_load = true,
action = function(pos, node, dtime_s)
local identifier = plant_nodename_to_id_list[node.name]
local identifier = plant_nodename_to_id[node.name]
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)
local plant_info = plant_lists[identifier]
if not plant_info then return end
local rolls = floor(dtime_s / plant_info.interval)
if rolls <= 0 then return end
-- simulate how often the block will be ticked
local stages = 0
for i = 1,rolls do
if random(1, plant_info.chance) == 1 then stages = stages + 1 end
end
if stages > 0 then
mcl_farming:grow_plant(identifier, pos, node, stages, false)
end
end,
})

View File

@ -15,10 +15,6 @@ minetest.register_node("mcl_farming:soil", {
{-0.5, -0.5, -0.5, 0.5, 0.4375, 0.5},
}
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_int("wet", 0)
end,
groups = {handy=1,shovely=1, dirtifies_below_solid=1, dirtifier=1, soil=2, soil_sapling=1, deco_block=1 },
sounds = mcl_sounds.node_sound_dirt_defaults(),
_mcl_blast_resistance = 0.6,
@ -38,10 +34,6 @@ minetest.register_node("mcl_farming:soil_wet", {
{-0.5, -0.5, -0.5, 0.5, 0.4375, 0.5},
}
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_int("wet", 7)
end,
groups = {handy=1,shovely=1, not_in_creative_inventory=1, dirtifies_below_solid=1, dirtifier=1, soil=3, soil_sapling=1 },
sounds = mcl_sounds.node_sound_dirt_defaults(),
_mcl_blast_resistance = 0.6,
@ -51,75 +43,54 @@ minetest.register_node("mcl_farming:soil_wet", {
minetest.register_abm({
label = "Farmland hydration",
nodenames = {"mcl_farming:soil", "mcl_farming:soil_wet"},
interval = 15,
chance = 4,
interval = 2.73,
chance = 25,
action = function(pos, node)
-- Get wetness value
local meta = minetest.get_meta(pos)
local wet = meta:get_int("wet")
if not wet then
if node.name == "mcl_farming:soil" then
wet = 0
else
wet = 7
end
end
-- Turn back into dirt when covered by solid node
local above_node = minetest.get_node_or_nil({x=pos.x,y=pos.y+1,z=pos.z})
if above_node then
if minetest.get_item_group(above_node.name, "solid") ~= 0 then
node.name = "mcl_core:dirt"
minetest.set_node(pos, node)
return
end
local above_node = minetest.get_node_or_nil(vector.offset(pos, 0, 1, 0))
if above_node and minetest.get_item_group(above_node.name, "solid") ~= 0 then
node.name = "mcl_core:dirt"
minetest.set_node(pos, node)
return
end
-- Check an area of 9×2×9 around the node for nodename (9×9 on same level and 9×9 below)
local function check_surroundings(pos, nodename)
local nodes = minetest.find_nodes_in_area({x=pos.x-4,y=pos.y,z=pos.z-4}, {x=pos.x+4,y=pos.y+1,z=pos.z+4}, {nodename})
return #nodes > 0
end
if check_surroundings(pos, "group:water") then
if node.name ~= "mcl_farming:soil_wet" then
-- Make it wet
-- in rain, become wet, do not decay
if mcl_weather and mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
if node.name == "mcl_farming:soil" then
node.name = "mcl_farming:soil_wet"
minetest.set_node(pos, node)
end
else -- No water nearby.
-- The decay branch (make farmland dry or turn back to dirt)
return
end
-- Don't decay while it's raining
if mcl_weather.rain.raining then
if mcl_weather.is_outdoor(pos) then
return
end
end
-- No decay near unloaded areas since these might include water.
if not check_surroundings(pos, "ignore") then
if wet <= 0 then
--local n_def = minetest.registered_nodes[node.name] or nil
local nn = minetest.get_node_or_nil({x=pos.x,y=pos.y+1,z=pos.z})
if not nn or not nn.name then
return
end
local nn_def = minetest.registered_nodes[nn.name] or nil
-- Check an area of 9x2x9 around the node for nodename (9x9 on same level and 9x9 above)
-- include "ignore" to detect unloaded blocks
local nodes, counts = minetest.find_nodes_in_area(vector.offset(pos, -4, 0, -4), vector.offset(pos, 4, 1, 4), {"group:water", "ignore"})
local ignore = counts.ignore or 0
local has_water, fully_loaded = #nodes > ignore, ignore == 0
if nn_def and minetest.get_item_group(nn.name, "plant") == 0 then
node.name = "mcl_core:dirt"
minetest.set_node(pos, node)
return
end
else
if wet == 7 then
node.name = "mcl_farming:soil"
minetest.swap_node(pos, node)
end
-- Slowly count down wetness
meta:set_int("wet", wet-1)
end
-- Hydrate by rain or water, do not decay
if has_water then
if node.name == "mcl_farming:soil" then
node.name = "mcl_farming:soil_wet"
minetest.set_node(pos, node)
end
return
end
-- No decay near unloaded areas (ignore) since these might include water.
if not fully_loaded then return end
-- Decay: make wet farmland dry up
if node.name == "mcl_farming:soil_wet" then
node.name = "mcl_farming:soil"
minetest.set_node(pos, node)
return
end
-- Revert to dirt if wetness is 0, and no plant above
local above = minetest.get_node_or_nil(vector.offset(pos, 0, 1, 0))
if minetest.get_item_group(above.name, "plant") == 0 then
node.name = "mcl_core:dirt"
minetest.set_node(pos, node)
end
end,
})

View File

@ -99,8 +99,8 @@ minetest.register_craftitem("mcl_farming:sweet_berry", {
})
minetest.register_alias("mcl_sweet_berry:sweet_berry", "mcl_farming:sweet_berry")
-- TODO: Find proper interval and chance values for sweet berry bushes. Current interval and chance values are copied from mcl_farming:beetroot which has similar growth stages.
mcl_farming:add_plant("plant_sweet_berry_bush", "mcl_farming:sweet_berry_bush_3", {"mcl_farming:sweet_berry_bush_0", "mcl_farming:sweet_berry_bush_1", "mcl_farming:sweet_berry_bush_2"}, 68, 3)
-- TODO: Find proper interval and chance values for sweet berry bushes. Current interval and chance values are copied from mcl_farming:beetroot which has similar growth stages, 2/3rd of the default.
mcl_farming:add_plant("plant_sweet_berry_bush", "mcl_farming:sweet_berry_bush_3", {"mcl_farming:sweet_berry_bush_0", "mcl_farming:sweet_berry_bush_1", "mcl_farming:sweet_berry_bush_2"}, 8.7019, 35)
local function berry_damage_check(obj)
local p = obj:get_pos()

View File

@ -99,7 +99,7 @@ minetest.register_node("mcl_farming:wheat", {
}
})
mcl_farming:add_plant("plant_wheat", "mcl_farming:wheat", {"mcl_farming:wheat_1", "mcl_farming:wheat_2", "mcl_farming:wheat_3", "mcl_farming:wheat_4", "mcl_farming:wheat_5", "mcl_farming:wheat_6", "mcl_farming:wheat_7"}, 25, 20)
mcl_farming:add_plant("plant_wheat", "mcl_farming:wheat", {"mcl_farming:wheat_1", "mcl_farming:wheat_2", "mcl_farming:wheat_3", "mcl_farming:wheat_4", "mcl_farming:wheat_5", "mcl_farming:wheat_6", "mcl_farming:wheat_7"}, 5.8020, 35)
minetest.register_craftitem("mcl_farming:wheat_item", {
description = S("Wheat"),

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

@ -211,16 +211,37 @@ local function set_interact(player, interact)
return
end
local meta = player:get_meta()
if meta:get_int("mcl_privs:interact_revoked") ~= 1 then
privs.interact = interact
minetest.set_player_privs(player_name, privs)
meta:set_int("mcl_privs:interact_revoked",0)
if interact and meta:get_int("mcl_shields:interact_revoked") ~= 0 then
meta:set_int("mcl_shields:interact_revoked", 0)
privs.interact = true
elseif not interact then
meta:set_int("mcl_shields:interact_revoked", privs.interact and 1 or 0)
privs.interact = nil
end
minetest.set_player_privs(player_name, privs)
end
-- Prevent player from being able to circumvent interact privilage removal by
-- using shield.
minetest.register_on_priv_revoke(function(name, revoker, priv)
if priv == "interact" and revoker then
local player = minetest.get_player_by_name(name)
if not player then
return
end
local meta = player:get_meta()
meta:set_int("mcl_shields:interact_revoked", 0)
end
end)
local shield_hud = {}
local function remove_shield_hud(player)
set_interact(player, true)
playerphysics.remove_physics_factor(player, "speed", "shield_speed")
if not shield_hud[player] then return end --this function takes a long time. only run it when necessary
player:hud_remove(shield_hud[player])
shield_hud[player] = nil
@ -231,9 +252,6 @@ local function remove_shield_hud(player)
if not hf.wielditem then
player:hud_set_flags({wielditem = true})
end
playerphysics.remove_physics_factor(player, "speed", "shield_speed")
set_interact(player, true)
end
local function add_shield_entity(player, i)
@ -251,6 +269,11 @@ local function remove_shield_entity(player, i)
end
end
local function is_rmb_conflicting_node(nodename)
local nodedef = minetest.registered_nodes[nodename] or {}
return nodedef.on_rightclick
end
local function handle_blocking(player)
local player_shield = mcl_shields.players[player]
local rmb = player:get_player_control().RMB
@ -259,14 +282,25 @@ local function handle_blocking(player)
return
end
local pointed_thing = mcl_util.get_pointed_thing(player, true)
local wielded_stack = player:get_wielded_item()
local shield_in_offhand = mcl_shields.wielding_shield(player, 1)
local shield_in_hand = mcl_shields.wielding_shield(player)
local not_blocking = player_shield.blocking == 0
local pos = player:get_pos()
if pointed_thing and pointed_thing.type == "node" then
local pointed_node = minetest.get_node(pointed_thing.under)
if minetest.get_item_group(pointed_node.name, "container") > 1
or is_rmb_conflicting_node(pointed_node.name)
or wielded_stack:get_definition().type == "node" then
return
end
end
if shield_in_hand then
if not_blocking then
minetest.after(0.25, function()
minetest.after(0.05, function()
if (not_blocking or not shield_in_offhand) and shield_in_hand and rmb then
player_shield.blocking = 2
set_shield(player, true, 2)
@ -276,22 +310,15 @@ local function handle_blocking(player)
player_shield.blocking = 2
end
elseif shield_in_offhand then
local pointed_thing = mcl_util.get_pointed_thing(player, true)
local offhand_can_block = (wielded_item(player) == "" or not pointed_thing)
and (minetest.get_item_group(wielded_item(player), "bow") ~= 1 and minetest.get_item_group(wielded_item(player), "crossbow") ~= 1)
if pointed_thing and pointed_thing.type == "node" then
if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "container") > 1 then
return
end
end
local offhand_can_block = minetest.get_item_group(wielded_item(player), "cannot_block") ~= 1
if not offhand_can_block then
return
end
if not_blocking then
minetest.after(0.25, function()
if (not_blocking or not shield_in_hand) and shield_in_offhand and rmb and offhand_can_block then
minetest.after(0.05, function()
if (not_blocking or not shield_in_hand) and shield_in_offhand
and rmb and offhand_can_block then
player_shield.blocking = 1
set_shield(player, true, 1)
end
@ -344,7 +371,7 @@ local function add_shield_hud(shieldstack, player, blocking)
z_index = -200,
})
playerphysics.add_physics_factor(player, "speed", "shield_speed", 0.5)
set_interact(player, nil)
set_interact(player, false)
end
local function update_shield_hud(player, blocking, shieldstack)

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

@ -30,17 +30,5 @@ for _, action in pairs({"grant", "revoke"}) do
if priv == "fly" then
meta:set_int("mcl_privs:fly_changed", 1)
end
--[[
so e.g. hackers who have been revoked of the interact privilege
will not automatically get the interact privilege through the mcl shields code back
]]
if priv == "interact" then
if action == "revoke" then
meta:set_int("mcl_privs:interact_revoked", 1)
else
meta:set_int("mcl_privs:interact_revoked", 0)
end
end
end)
end
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

@ -45,6 +45,9 @@ mcl_disabled_structures (Disabled structures) string
# Comma separated list of disabled event names
mcl_disabled_events (Disabled events) string
# Control the relative plant growth speed (default: 1)
vl_plant_growth (Plant growth factor) float 1.0 0 100
[Players]
# If enabled, players respawn at the bed they last lay on instead of normal
# spawn.

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 488 B