Compare commits

...

145 Commits

Author SHA1 Message Date
teknomunk b6a05421d2 Add back shift+punch to immediately drop minecart 2024-10-10 12:48:27 +02:00
teknomunk 3805c3775a Fix another crash, fix rail tee on texture 2024-10-10 12:48:27 +02:00
teknomunk d3b4a579c6 Calculate acceleration of trains based on average of acceleration for all carts in the train, make velocity not change on slopes and 45 degree track 2024-10-10 12:48:27 +02:00
teknomunk 2281710d35 Prevent trains from slowing on 45 degree track 2024-10-10 12:48:27 +02:00
teknomunk 71783a7efb Add guard that prevents crash when itemstack is nil 2024-10-10 12:48:27 +02:00
teknomunk b57fda9f01 Silence debug prints and logging 2024-10-10 12:48:27 +02:00
teknomunk f92b9bc60e Prevent removal of old minecarts 2024-10-10 12:48:27 +02:00
teknomunk 7838a9773b Add legacy node conversion to vl_legacy and update rails.lua to use it 2024-10-10 12:48:27 +02:00
teknomunk 7f56ff81ab Make activated tnt minecarts glow in the dark, fix crash with lit tnt minecarts 2024-10-10 12:48:27 +02:00
teknomunk 1abff60427 Fix a couple of crashes (TNT minecart trying to update orientation after exploding, trying to punch/push a minecart not on track) 2024-10-10 12:48:27 +02:00
teknomunk 2aeb51c467 Expand mcl_util.hopper_pull() to mcl_util.hopper_pull_to_inventory() 2024-10-10 12:48:27 +02:00
teknomunk 703d154de8 Add profiling code to force_get_node() 2024-10-10 12:48:27 +02:00
teknomunk e816cee311 Address additional review comments 2024-10-10 12:48:27 +02:00
teknomunk 0b95d86769 Update API documentation to always use , add compatibility shim to mcl_minecarts.is_rail() and mcl_minecarts.ge_rail_direction() 2024-10-10 12:48:27 +02:00
teknomunk 6e94537322 Rewrite mcl_util.hopper_pull in terms of mcl_util.hopper_pull_to_inventory 2024-10-10 12:48:27 +02:00
teknomunk 10797f0d5f Correct documentation per review 2024-10-10 12:48:27 +02:00
teknomunk 42c0b1b0d9 Switch over to using vl_legacy for item conversion in player inventories 2024-10-10 12:48:27 +02:00
teknomunk 8767821716 Fix typo 2024-10-10 12:48:27 +02:00
teknomunk ed60bbdee1 Register rail conversions 2024-10-10 12:48:27 +02:00
teknomunk 4ff7445fc1 Implement vl_legacy deprecated function and item conversion APIs 2024-10-10 12:48:27 +02:00
teknomunk 6936a86754 Fix crashes 2024-10-10 12:48:27 +02:00
teknomunk 741f8ece32 Make old rails have a drawtype, make update lbm always run 2024-10-10 12:48:27 +02:00
teknomunk 9bb9dc8f3d Move the various rails to their own files, code cleanup 2024-10-10 12:48:27 +02:00
teknomunk b63fd08702 Remove undefined global for optional environmental physics 2024-10-10 12:48:27 +02:00
teknomunk fc2cd47fd7 Remove Emerge-0 warning that occurs when placing mineshafts 2024-10-10 12:48:27 +02:00
teknomunk f3b988163b Restore 45 degree cart movement, remove warning about unknown global 2024-10-10 12:48:27 +02:00
teknomunk 83cf20b5ac Complete rework of curve/tee rail direction functions 2024-10-10 12:48:27 +02:00
teknomunk fd9cbaf813 Rework rail_dir_curve to significantly reduce code size 2024-10-10 12:48:27 +02:00
teknomunk 6a0ee72080 Convert curved rails direction code to use fourdir 2024-10-10 12:48:27 +02:00
teknomunk 266268db2a Change verticle offset for testing reattaching to rail to 0.55, which is a bit more than the stair step height 2024-10-10 12:48:27 +02:00
teknomunk 88333b152f Fix cart detaching without unregistering from everything 2024-10-10 12:48:27 +02:00
teknomunk bbef011de1 Fix typo, set use_texture_alpha = clip for all rail 2024-10-10 12:48:27 +02:00
teknomunk 34a0b06acb Fix several undefined global warnings, fix cart movement when over maximum speed, fix cart reattachment to sloped track 2024-10-10 12:48:27 +02:00
teknomunk 4908fe23a6 Revert changed made to debug minecart-updates integration into tsm_railcorridors 2024-10-10 12:48:27 +02:00
teknomunk 3ab0bc0ac4 Make punch move minecarts a little, comment out more debug prints 2024-10-10 12:48:27 +02:00
teknomunk ccd555a5a0 Fix visual artifacts on the sides of rails 2024-10-10 12:48:27 +02:00
teknomunk a0d29b6405 Stop carts from reversing when they stop, make stopped carts try to start moving in the direction the player is facing 2024-10-10 12:48:27 +02:00
teknomunk 141c9b8635 Fix crash after entering a minecart not on rails 2024-10-10 12:48:27 +02:00
teknomunk da231f917e Fix placed rail conversion, start automatic inventory rail conversion 2024-10-10 12:48:27 +02:00
teknomunk 29efd9c539 Fix players repelling carts with new player metadata system 2024-10-10 12:48:27 +02:00
teknomunk b9755e976d Cleanup debug prints 2024-10-10 12:48:27 +02:00
teknomunk 58448de614 Add documentation for newly exposed attach_driver 2024-10-10 12:48:27 +02:00
teknomunk 7c6c050ff2 Add persistent player-specific metadata into mcl_playerinfo, simple cart reattachment (only exists if the luaentity for the cart exists when the player logs in) 2024-10-10 12:48:27 +02:00
teknomunk 21d74965d7 More fixes for minecart-hopper movement 2024-10-10 12:48:27 +02:00
teknomunk 7f99b0890b Get rail placement creating corners that lead into a downward sloped rail 2024-10-10 12:48:27 +02:00
teknomunk 6cb7f5e839 Create mcl_util.metadata_timer, fix crashes, add checks to prevent hoppers from pulling from carts that are not in the square above it 2024-10-10 12:48:27 +02:00
teknomunk cb4c8db97e Fix hopper-minecart interaction, convert ipairs(table) to use for i=1,#table instead 2024-10-10 12:48:27 +02:00
teknomunk 45068cf8a9 Update mineshafts for new rail and minecarts, add loot to generated chest and hopper minecarts (and remove notes about a hack) 2024-10-10 12:48:27 +02:00
teknomunk a9c47bca72 Give carts a small vertical lift when pushed to allow them to get back on rails 2024-10-10 12:48:27 +02:00
teknomunk f0e20b88eb Stop rail from being placed directly above rail (floating in air) 2024-10-10 12:48:27 +02:00
teknomunk a2b371e9a3 Fix sloped power,activator and detector rails, remove debug print 2024-10-10 12:48:27 +02:00
teknomunk 3c8a8249ad Modify mcl_entity_invs to add support for save/load items hooks in entities, add save/load hooks to minecarts to store item list in the minecart data and not in the entity data so that respawn doesn't destroy items 2024-10-10 12:48:27 +02:00
teknomunk 2c4e960625 Add documentation on the rail 2024-10-10 12:48:27 +02:00
teknomunk 25ebfe7a8b Add documentation on file structure and overviewes of each file 2024-10-10 12:48:27 +02:00
teknomunk 6fdeddeba1 Fix crashes, fix link in documentation 2024-10-10 12:48:27 +02:00
teknomunk e969f97de6 More documentation, add myself to copyright list in README.txt 2024-10-10 12:48:27 +02:00
teknomunk 619db26d65 More minor changes to API.md, start overall implementation documentation 2024-10-10 12:48:27 +02:00
teknomunk 2d2c2e212d Fix table of contents 2024-10-10 12:48:27 +02:00
teknomunk a3275f46f6 Finish writing API documentation, remove drop_railcarts (replaced by try_detach_minecart), rename constants to ALL CAPS for consistency, change mcl_minecarts. to mod. for API function definitions 2024-10-10 12:48:27 +02:00
teknomunk 1c06aff46a Nearly finish API documentation, create mcl_minecarts.add_blocks_to_map() 2024-10-10 12:48:27 +02:00
teknomunk 9888b674c0 Continue writing API documentation, update call signatures for a couple of API functions 2024-10-10 12:48:27 +02:00
teknomunk 937b32be1b Change document formatting, finally move cactus cart dropping to node definition for mcl_core:cactus 2024-10-10 12:48:27 +02:00
teknomunk 0c2e876d8b Correct crashes/item duplication with dropping carts, start API documentation 2024-10-10 12:48:27 +02:00
teknomunk dd374e7331 Fix cart controls, cart pushing 2024-10-10 12:48:27 +02:00
teknomunk 8101299ed8 Fix typo in rail replacement mapping, fix several crashes 2024-10-10 12:48:27 +02:00
teknomunk 568529e4b4 Implement movement thru tee rails 2024-10-10 12:48:27 +02:00
teknomunk 841f4deb77 Tune respawn distance limit 2024-10-10 12:48:27 +02:00
teknomunk b74342a2d0 Fix crashes 2024-10-10 12:48:27 +02:00
teknomunk 06cfd11843 Remove memory leak for cart data, check distance to players before respawning distant carts to prevent adding entities that are immediately inactivated 2024-10-10 12:48:27 +02:00
teknomunk 44ae3cb174 Implement offline/out of range minecart movement and fix minecart respawning, remove railtype tracking 2024-10-10 12:48:27 +02:00
teknomunk 5b33e55482 Remove do_movement dependency on the existence of a cart luaentity 2024-10-10 12:48:27 +02:00
teknomunk b97c5908ec Fix undefined global warning, move player off to the side of a cart when dismounting so trains don't get pushed apart when getting out 2024-10-10 12:48:27 +02:00
teknomunk 2ebb01a17c Make trains containing a player in a minecart function, minor cleanup in mcl_playerinfo 2024-10-10 12:48:27 +02:00
teknomunk b7ce059471 Fix crashes in train logic, allow breaking apart trains 2024-10-10 12:48:27 +02:00
teknomunk 7edc024db2 Implement train reversing 2024-10-10 12:48:27 +02:00
teknomunk 0ad75e8fc1 Repair vectors in cart data, mostly fix train movement bugs (still possible to have a furnace minecart flip, without the train also flipping) 2024-10-10 12:48:27 +02:00
teknomunk 2a4bd6923e Add cart entity respawn/destroy to match cart data (partially working) 2024-10-10 12:48:27 +02:00
teknomunk 2a3ccf63fd Give furnace minecart minimum velocity when lit, add train separation code, update logging code, add sequence number to entity staticdata to allow respawn/despawn when carts move when the entity is unloaded 2024-10-10 12:48:27 +02:00
teknomunk 602bddf650 Fix rail detach crash, make tnt minecarts explode if they hit something hard (off rails) 2024-10-10 12:48:27 +02:00
teknomunk 4eca9f234f Make sure carts get detatch if the rail under them is removed 2024-10-10 12:48:27 +02:00
teknomunk 967fa0e25c Fixish reorganizing, initial train implementation 2024-10-10 12:48:27 +02:00
teknomunk 67932e35c9 Major reorganization, start setup for trains 2024-10-10 12:48:27 +02:00
teknomunk b39a9cbf00 Make sure carts that collide move in the same direction the colliding cart was 2024-10-10 12:48:27 +02:00
teknomunk cb478fc1c8 Add utilities to convert between an ObjectRef, it's active object id and a 128bit uuid, move minecart data from entity staticdata to mod storage to eventually allow updating carts when out of range of players and also track what carts are alive, implement on-rail cart collisions 2024-10-10 12:48:27 +02:00
teknomunk d57cce2338 Harden against unknown nodes 2024-10-10 12:48:27 +02:00
teknomunk 62e7bba815 Allow players to push minecarts that are not on track 2024-10-10 12:48:27 +02:00
teknomunk 11dc7215f7 Fix rails in creative inventory, make minecart with tnt not crash server when exploding, make minecart with tnt slightly more powerful than regular tnt 2024-10-10 12:48:27 +02:00
teknomunk 249edf91ec Fix rail movement regressions 2024-10-10 12:48:27 +02:00
teknomunk e4a67a0fdc Move cart code to its own file, more code cleanup, add aliases for old track items 2024-10-10 12:48:27 +02:00
teknomunk da533456f2 Cleanup code, restore uphill/downhill cart movement, completely remove old rail 2024-10-10 12:48:27 +02:00
teknomunk d56979d268 Get rail reattachment (especially after jumps) working correctly 2024-10-10 12:48:27 +02:00
teknomunk 0eb21c88c9 Make legacy rail update apply to all old rail types, add basic detached railcart physics with a stub to use mcl_physics when it gets merged 2024-10-10 12:48:27 +02:00
teknomunk 568c3d6857 Silence unmaskable print statements 2024-10-10 12:48:27 +02:00
teknomunk 54eb88c2c3 Add immortal item entity support, add legacy rail conversion that uses immortal item drops for corners/tees/crosses that are no longer possible 2024-10-10 12:48:27 +02:00
teknomunk b6d8bf7093 Fix more rail connection bugs 2024-10-10 12:48:27 +02:00
teknomunk 557d592988 Get sloped connections working correctly 2024-10-10 12:48:27 +02:00
teknomunk b71e6bad81 Re-enable rule for powering rail from underneath, have stairs block minecart movement, fix crash when lightning strikes a minecart 2024-10-10 12:48:27 +02:00
teknomunk 990e017d56 Fix mcl_util.table_merge where a standard value overwrites a table, fix base definition usage, implement behavior difference when there is a solid block after a straight piece of track (this will eventually allow minecarts to fly off the end of the track) 2024-10-10 12:48:27 +02:00
teknomunk ae42e03550 Update all rail types to new version 2024-10-10 12:48:27 +02:00
teknomunk 6b203da581 Reorganize 2024-10-10 12:48:27 +02:00
teknomunk e81330a843 Finish reverting 08b41a3b39 2024-10-10 12:48:27 +02:00
teknomunk 0259787c97 Enable new track with get_next_dir handlers 2024-10-10 12:48:27 +02:00
teknomunk c59c5b6a15 Change connection rules again to allow building parallel track, tees and crosses), start implementing rail rules callbacks 2024-10-10 12:48:27 +02:00
teknomunk 92d29a0ac4 Add sloped rail 2024-10-10 12:48:27 +02:00
teknomunk 5921e3f963 Fix rail visuals, add switch operation 2024-10-10 12:48:27 +02:00
teknomunk 96a937b10f Implement initial rail connection logic (no vertical track yet), experiment with texture modifiers and gravel underlay for display (not working) 2024-10-10 12:48:27 +02:00
teknomunk 621bbfb65e Start implementing new rail nodes 2024-10-10 12:48:27 +02:00
teknomunk 4e349502ac Implement minecart with command block 2024-10-10 12:48:27 +02:00
teknomunk 33564d7dd6 Create mesecons command API and modify commandblock to use it 2024-10-10 12:48:27 +02:00
teknomunk 7362e200c3 Disable punch to move minecarts, implement punch to drop minecart, enable basic cart keyboard controls (accelerate and brake) 2024-10-10 12:48:27 +02:00
teknomunk 22974946c1 Remove cart oscillation when pushed 2024-10-10 12:48:27 +02:00
teknomunk 3e93ef1fd9 Limit top speed of furnace minecarts to 4 blocks/second, limit total fuel time to 27 minutes 2024-10-10 12:48:27 +02:00
teknomunk 7957a6f2db Fix bug with furnace minecart at max velocity (stopped until fuel ran out), move _fueltime into staticdata 2024-10-10 12:48:27 +02:00
teknomunk 94e13e3f70 Fix call signature of mcl_util.hopper_pull_to_inventory, move cart-specific behaviors to _mcl_minecarts_on_step handlers, fix typo, change distance used in rail reattach code, move cart_id generation 2024-10-10 12:48:27 +02:00
teknomunk 505d732e06 Add groups to minecart entities (for containers), fix cart node watch handling, relocate hopper_push_to_mc in mcl_hopper/init.lua, implement hopper-to-minecart push using enter/leave hooks for both straight and bent hoppers 2024-10-10 12:48:27 +02:00
teknomunk 3f7815b729 Add API function to remove node watch 2024-10-10 12:48:27 +02:00
teknomunk 3142d37572 Refactor enter/leave hook processing, add node watches for implementing hopper-to-minecart functionality (should properly handle heavy server lag without missing any time), temporarily disable hopper push/pull to minecart in mcl_hoppers, prepare to move minecart-specific on_step behavior out of main on_step function and to a minecart-specific handler 2024-10-10 12:48:27 +02:00
teknomunk 34b315bd5e Start adding hooks for implpementing minecart with command block 2024-10-10 12:48:27 +02:00
teknomunk 2b685fdaa2 Make minecarts solid and add players pushing 2024-10-10 12:48:27 +02:00
teknomunk a5063976d8 Fix forwards/backwars tilt in all directions 2024-10-10 12:48:27 +02:00
teknomunk 82b21aef2a Prevent players from entering minecarts when sneaking, prevents players from causing MineClone2/MineClone2#3188 2024-10-10 12:48:27 +02:00
teknomunk 6ff564ab1e Increase default track friction, disable right-click to exit minecarts 2024-10-10 12:48:27 +02:00
teknomunk 2a6277c17e Initial tuning of acceleration/gravity, fix crash when entering an activator rail, detach mobs from cart on active activator rail, remove commented out code no longer needed 2024-10-10 12:48:27 +02:00
teknomunk dc0b74e730 Move code that handles below-rail hoppers to handle_cart_enter, implement timestep-independent cart physics (will need tuning punch, power rail and gravityaccelerations to make game fun) 2024-10-10 12:48:27 +02:00
teknomunk cdccd6d6db Mostly fix carts stopping between powered rails (there is still some strangeness with acceleration physics) 2024-10-10 12:48:27 +02:00
teknomunk 781994fc2b Fix diagonal movement 2024-10-10 12:48:27 +02:00
teknomunk 89a46e75aa Add diagonal track movement on zig-zag track, rewrite mcl_minecarts:get_rail_direction 2024-10-10 12:48:27 +02:00
teknomunk f07eb2fdef Make TNT minecarts available in creative menu 2024-10-10 12:48:27 +02:00
teknomunk 88a9814387 Implement custom item dropper handlers, implement droppers placing minecarts 2024-10-10 12:48:27 +02:00
teknomunk d558760c1e Hopper minecarts pull from containers above rail 2024-10-10 12:48:27 +02:00
teknomunk 4e74b1da62 Rework in preparation to add code to pull from containers into the hopper minecart 2024-10-10 12:48:27 +02:00
teknomunk 51cf2be609 Move fiction constant to top of file, suppress cart flips when direction reverses due to gravity or end of track 2024-10-10 12:48:27 +02:00
teknomunk d2ed8aa136 Add code to reattach carts to rail when data corruption occurs, fix bug in last commit that caused carts to bury into the ground 2024-10-10 12:48:27 +02:00
teknomunk 29c1aa61d4 Remove dip into the ground that occured when gravity caused the cart to reverse directions 2024-10-10 12:48:27 +02:00
teknomunk b45c179732 Implement gravity, move orientation update to own function, fix cart stopping in process_acceleration 2024-10-10 12:48:27 +02:00
teknomunk ccb76309c0 Change connected railcar behavior to fix unreliable end of track stopping, set maximum acceleration of powered rails to 8 blocks per second (per https://minecraft.fandom.com/wiki/Powered_Rail), stop powered rails from powering the block underneath it (allows below rail hopper to work while the rail is powered like in https://www.youtube.com/watch?v=szjO0-duTAk), modify mcl_hoppers to allow triggering a hopper pull once the minecart is stopped on top of the hopper and wait before allowing the cart to move to allow redstone circuits time to process 2024-10-10 12:48:27 +02:00
teknomunk d8a8d6be64 Make minecart always stop at correct location at end of track, fix crash when placing chest minecart after changing how staticdata is handled 2024-10-10 12:48:27 +02:00
teknomunk e91aa4618b Modify do_movement_step to move to always move to the edge of the current rail segment before updating the direction to prevent oscillations at corners, fix end of track stop location with new movement behavior, disable experimental controls, swap code to detach the driver on float with a call to detach_driver() 2024-10-10 12:48:27 +02:00
teknomunk 10f6e25359 Add DEBUG flag, stop small do_movement_step's from occuring (this improves but doesn't eliminate the bug I with the a6be179ed commit), add recovery when staticdata field gets lost 2024-10-10 12:48:27 +02:00
teknomunk 65b0dfba70 Fix initial_properties for minecarts 2024-10-10 12:48:27 +02:00
teknomunk d1aeb75d9b Change left,right and back vectors to matrix math results with no branching 2024-10-10 12:48:27 +02:00
teknomunk 4cab0fc2cb Remove now unused properties from minecart definition, convert more vectors to use vector.new syntax 2024-10-10 12:48:27 +02:00
teknomunk a4d48e360c Complete rewrite of minecart movement that resolves MineClone2/MineClone2#2446 and MineClone2/MineClone2#247 (comment) but has a bug where carts will synchronize movements that I am still investigating 2024-10-10 12:48:27 +02:00
teknomunk 7f697ff371 Change staticdata serialization (with migration from old data), disable debugging code used to investigate MineClone2/MineClone2#2446 2024-10-10 12:48:27 +02:00
teknomunk 71cc52619e Change to vector.new from {x=...}, relocate movement code to own function for future changes 2024-10-10 12:48:27 +02:00
40 changed files with 4417 additions and 1742 deletions

View File

@ -73,6 +73,15 @@ function mcl_util.mcl_log(message, module, bypass_default_logger)
minetest.log(selected_module .. " " .. message) minetest.log(selected_module .. " " .. message)
end end
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 = {} local player_timers = {}
@ -359,13 +368,7 @@ function mcl_util.hopper_push(pos, dst_pos)
return ok return ok
end end
-- Try pulling from source inventory to hopper inventory function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
---@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'
-- Get node pos' for item transfer -- Get node pos' for item transfer
local src = minetest.get_node(src_pos) local src = minetest.get_node(src_pos)
if not minetest.registered_nodes[src.name] then return end if not minetest.registered_nodes[src.name] then return end
@ -381,7 +384,7 @@ function mcl_util.hopper_pull(pos, src_pos)
else else
local src_meta = minetest.get_meta(src_pos) local src_meta = minetest.get_meta(src_pos)
src_inv = src_meta:get_inventory() 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 end
if stack_id ~= nil then if stack_id ~= nil then
@ -391,6 +394,12 @@ function mcl_util.hopper_pull(pos, src_pos)
end end
end 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) local function drop_item_stack(pos, stack)
if not stack or stack:is_empty() then return end 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) (pos.z >= min.z) and (pos.z <= max.z)
end end
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) function mcl_entity_invs.load_inv(ent,size)
mcl_log("load_inv")
if not ent._inv_id then return end if not ent._inv_id then return end
mcl_log("load_inv 2")
local inv = minetest.get_inventory({type="detached", name=ent._inv_id}) local inv = minetest.get_inventory({type="detached", name=ent._inv_id})
if not inv then if not inv then
mcl_log("load_inv 3")
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks) inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
inv:set_size("main", size) 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) inv:set_list("main",ent._items)
end end
else
mcl_log("load_inv 4")
end end
return inv return inv
end end
function mcl_entity_invs.save_inv(ent) function mcl_entity_invs.save_inv(ent)
if ent._inv then if ent._inv then
ent._items = {} local items = {}
for i,it in ipairs(ent._inv:get_list("main")) do 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 end
minetest.remove_detached_inventory(ent._inv_id) minetest.remove_detached_inventory(ent._inv_id)
ent._inv = nil ent._inv = nil

View File

@ -1,3 +1,3 @@
name = mcl_entity_invs name = mcl_entity_invs
author = cora 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, _insta_collect = self._insta_collect,
_flowing = self._flowing, _flowing = self._flowing,
_removed = self._removed, _removed = self._removed,
_immortal = self._immortal,
}) })
-- sfan5 guessed that the biggest serializable item -- sfan5 guessed that the biggest serializable item
-- entity would have a size of 65530 bytes. This has -- 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._insta_collect = data._insta_collect
self._flowing = data._flowing self._flowing = data._flowing
self._removed = data._removed self._removed = data._removed
self._immortal = data._immortal
end end
else else
self.itemstring = staticdata self.itemstring = staticdata
@ -985,7 +987,7 @@ minetest.register_entity(":__builtin:item", {
if self._collector_timer then if self._collector_timer then
self._collector_timer = self._collector_timer + dtime self._collector_timer = self._collector_timer + dtime
end 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._removed = true
self.object:remove() self.object:remove()
return 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) 2012-2016 PilzAdam
Copyright (C) 2014-2016 SmallJoker Copyright (C) 2014-2016 SmallJoker
Copyright (C) 2012-2016 Various Minetest developers and contributors Copyright (C) 2012-2016 Various Minetest developers and contributors
Copyright (C) 2024 teknomunk
Authors/licenses of media files: Authors/licenses of media files:
----------------------- -----------------------

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
name = mcl_minecarts name = mcl_minecarts
author = Krock author = Krock
description = Minecarts are vehicles to move players quickly on rails. 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 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 optional_depends = doc_identifier, mcl_wip, mcl_physics, vl_physics

View File

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

View File

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

View File

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

View File

@ -1,53 +1,349 @@
local S = minetest.get_translator(minetest.get_current_modname()) local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
mod.RAIL_GROUPS = {
STANDARD = 1,
CURVES = 2,
}
-- Template rail function -- Inport functions and constants from elsewhere
local function register_rail(itemstring, tiles, def_extras, creative) local table_merge = mcl_util.table_merge
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} local check_connection_rules = mod.check_connection_rules
if creative == false then local update_rail_connections = mod.update_rail_connections
groups.not_in_creative_inventory = 1 local minetest_fourdir_to_dir = minetest.fourdir_to_dir
end local minetest_dir_to_fourdir = minetest.dir_to_fourdir
local ndef = { local vector_offset = vector.offset
drawtype = "raillike", local vector_equals = vector.equals
tiles = tiles, local north = mod.north
is_ground_content = false, local south = mod.south
inventory_image = tiles[1], local east = mod.east
wield_image = tiles[1], local west = mod.west
paramtype = "light",
walkable = false, --- Rail direction Handleres
selection_box = { local function rail_dir_straight(pos, dir, node)
type = "fixed", dir = vector.new(dir)
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2}, dir.y = 0
},
stack_max = 64, if node.param2 == 0 or node.param2 == 2 then
groups = groups, if vector_equals(dir, north) then
sounds = mcl_sounds.node_sound_metal_defaults(), return north
_mcl_blast_resistance = 0.7, else
_mcl_hardness = 0.7, return south
after_destruct = function(pos) end
-- Scan for minecarts in this pos and force them to execute their "floating" check. else
-- Normally, this will make them drop. if vector_equals(dir,east) then
local objs = minetest.get_objects_inside_radius(pos, 1) return east
for o=1, #objs do else
local le = objs[o]:get_luaentity() return west
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
end end
end end
end
local function rail_dir_sloped(pos, dir, node)
local uphill = minetest_fourdir_to_dir(node.param2)
local downhill = minetest_fourdir_to_dir((node.param2+2)%4)
local up_uphill = vector_offset(uphill,0,1,0)
if vector_equals(dir, uphill) or vector_equals(dir, up_uphill) then
return up_uphill
else
return downhill
end
end
-- Fourdir to cardinal direction
-- 0 = north
-- 1 = east
-- 2 = south
-- 3 = west
-- This takes a table `dirs` that has one element for each cardinal direction
-- and which specifies the direction for a cart to continue in when entering
-- a rail node in the direction of the cardinal. This function takes node
-- rotations into account.
local function rail_dir_from_table(pos, dir, node, dirs)
dir = vector.new(dir)
dir.y = 0
local dir_fourdir = (minetest_dir_to_fourdir(dir) - node.param2 + 4) % 4
local new_fourdir = (dirs[dir_fourdir] + node.param2) % 4
return minetest_fourdir_to_dir(new_fourdir)
end
local CURVE_RAIL_DIRS = { [0] = 1, 1, 2, 2, }
local function rail_dir_curve(pos, dir, node)
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end
local function rail_dir_tee_off(pos, dir, node)
return rail_dir_from_table(pos, dir, node, CURVE_RAIL_DIRS)
end
local TEE_RAIL_ON_DIRS = { [0] = 0, 1, 1, 0 }
local function rail_dir_tee_on(pos, dir, node)
return rail_dir_from_table(pos, dir, node, TEE_RAIL_ON_DIRS)
end
local function rail_dir_cross(pos, dir, node)
dir = vector.new(dir)
dir.y = 0
-- Always continue in the same direction. No direction changes allowed
return dir
end
-- Setup shared text
local railuse = S(
"Place them on the ground to build your railway, the rails will automatically connect to each other and will"..
" turn into curves, T-junctions, crossings and slopes as needed."
)
mod.text = mod.text or {}
mod.text.railuse = railuse
local BASE_DEF = {
drawtype = "mesh",
mesh = "flat_track.obj",
paramtype = "light",
paramtype2 = "4dir",
stack_max = 64,
sounds = mcl_sounds.node_sound_metal_defaults(),
is_ground_content = true,
paramtype = "light",
use_texture_alpha = "clip",
collision_box = {
type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/15 }
},
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
groups = {
handy=1, pickaxey=1,
attached_node=1,
rail=1,
connect_to_raillike=minetest.raillike_group("rail"),
dig_by_water=0,destroy_by_lava_flow=0,
transport=1
},
description = S("New Rail"), -- Temporary name to make debugging easier
_tt_help = S("Track for minecarts"),
_doc_items_usagehelp = railuse,
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
on_place = function(itemstack, placer, pointed_thing)
local node_name = minetest.get_node(pointed_thing.under).name
-- Don't allow placing rail above rail
if minetest.get_item_group(node_name,"rail") == 0 then
return minetest.item_place_node(itemstack, placer, pointed_thing)
else
return itemstack
end
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
update_rail_connections(pos)
end,
_mcl_minecarts = {
get_next_dir = rail_dir_straight,
},
_mcl_blast_resistance = 0.7,
_mcl_hardness = 0.7,
}
local SLOPED_RAIL_DEF = table.copy(BASE_DEF)
table_merge(SLOPED_RAIL_DEF,{
drawtype = "mesh",
mesh = "sloped_track.obj",
groups = {
rail_slope = 1,
not_in_creative_inventory = 1,
},
collision_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
}
},
selection_box = {
type = "fixed",
fixed = {
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
}
},
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
},
})
function mod.register_rail(itemstring, ndef)
assert(ndef.tiles)
-- Extract out the craft recipe
local craft = ndef.craft
ndef.craft = nil
-- Add sensible defaults
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
--print("registering rail "..itemstring.." with definition: "..dump(ndef))
-- Make registrations
minetest.register_node(itemstring, ndef)
if craft then minetest.register_craft(craft) end
end
function mod.register_straight_rail(base_name, tiles, def)
def = def or {}
local base_def = table.copy(BASE_DEF)
local sloped_def = table.copy(SLOPED_RAIL_DEF)
local add = {
tiles = { tiles[1] },
drop = base_name,
groups = {
rail = mod.RAIL_GROUPS.STANDARD,
},
_mcl_minecarts = {
base_name = base_name,
can_slope = true,
},
}
table_merge(base_def, add); table_merge(sloped_def, add)
table_merge(base_def, def); table_merge(sloped_def, def)
-- Register the base node
mod.register_rail(base_name, base_def)
base_def.craft = nil; sloped_def.craft = nil
table_merge(base_def,{
_mcl_minecarts = {
railtype = "straight",
},
})
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
},
mesecons = def.mesecons_sloped,
tiles = { tiles[1] },
_mcl_minecarts = {
railtype = "sloped",
},
}))
end
function mod.register_curves_rail(base_name, tiles, def)
def = def or {}
local base_def = table.copy(BASE_DEF)
local sloped_def = table.copy(SLOPED_RAIL_DEF)
local add = {
_mcl_minecarts = { base_name = base_name },
groups = {
rail = mod.RAIL_GROUPS.CURVES
},
drop = base_name,
}
table_merge(base_def, add); table_merge(sloped_def, add)
table_merge(base_def, def); table_merge(sloped_def, def)
-- Register the base node
mod.register_rail(base_name, table_merge(table.copy(base_def),{
tiles = { tiles[1] },
_mcl_minecarts = {
get_next_dir = rail_dir_straight,
railtype = "straight",
can_slope = true,
},
}))
-- Update for other variants
base_def.craft = nil
table_merge(base_def, {
groups = {
not_in_creative_inventory = 1
}
})
-- Corner variants
mod.register_rail(base_name.."_corner", table_merge(table.copy(base_def),{
tiles = { tiles[2] },
_mcl_minecarts = {
get_next_dir = rail_dir_curve,
railtype = "corner",
},
}))
-- Tee variants
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
tiles = { tiles[3] },
mesecons = {
effector = {
action_on = function(pos, node)
local new_node = {name = base_name.."_tee_on", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
},
_mcl_minecarts = {
get_next_dir = rail_dir_tee_off,
railtype = "tee",
},
}))
mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
tiles = { tiles[4] },
_mcl_minecarts = {
get_next_dir = rail_dir_tee_on,
railtype = "tee",
},
mesecons = {
effector = {
action_off = function(pos, node)
local new_node = {name = base_name.."_tee_off", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
}
}))
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
description = S("Sloped Rail"), -- Temporary name to make debugging easier
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
railtype = "tee",
},
tiles = { tiles[1] },
}))
-- Cross variant
mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
tiles = { tiles[5] },
_mcl_minecarts = {
get_next_dir = rail_dir_cross,
railtype = "cross",
},
}))
end
function mod.register_rail_sloped(itemstring, def)
assert(def.tiles)
-- Build the node definition
local ndef = table.copy(SLOPED_RAIL_DEF)
table_merge(ndef, def)
-- Add sensible defaults
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))
-- Make registrations
minetest.register_node(itemstring, ndef) minetest.register_node(itemstring, ndef)
end end
-- Redstone rules -- 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= 1, y= 0, z= 0, spread=true}, {x= 1, y= 0, z= 0, spread=true},
{x= 0, y=-1, z= 0, spread=true}, {x= 0, y=-1, z= 0, spread=true},
@ -64,202 +360,73 @@ local rail_rules_long =
{x= 0, y= 1, z=-1}, {x= 0, y= 1, z=-1},
{x= 0, y=-1, z=-1}} {x= 0, y=-1, z=-1}}
local rail_rules_short = mesecon.rules.pplate dofile(modpath.."/rails/normal.lua")
dofile(modpath.."/rails/activator.lua")
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.") dofile(modpath.."/rails/detector.lua")
dofile(modpath.."/rails/powered.lua")
-- 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"},
}
})
-- Aliases -- Aliases
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on") doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
end end
local CURVY_RAILS_MAP = {
["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
}
local function convert_legacy_curvy_rails(pos, node)
node.name = CURVY_RAILS_MAP[node.name]
if node.name then
minetest.swap_node(pos, node)
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
end
end
for old,new in pairs(CURVY_RAILS_MAP) do
local new_def = minetest.registered_nodes[new]
minetest.register_node(old, {
drawtype = "raillike",
inventory_image = new_def.inventory_image,
groups = { rail = 1, legacy = 1 },
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
_vl_legacy_convert_node = convert_legacy_curvy_rails
})
vl_legacy.register_item_conversion(old, new)
end
local STRAIGHT_RAILS_MAP ={
["mcl_minecarts:golden_rail"] = "mcl_minecarts:golden_rail_v2",
["mcl_minecarts:golden_rail_on"] = "mcl_minecarts:golden_rail_v2_on",
["mcl_minecarts:activator_rail"] = "mcl_minecarts:activator_rail_v2",
["mcl_minecarts:activator_rail_on"] = "mcl_minecarts:activator_rail_v2_on",
["mcl_minecarts:detector_rail"] = "mcl_minecarts:detector_rail_v2",
["mcl_minecarts:detector_rail_on"] = "mcl_minecarts:detector_rail_v2_on",
}
local function convert_legacy_straight_rail(pos, node)
node.name = STRAIGHT_RAILS_MAP[node.name]
if node.name then
local connections = mod.get_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
if not mod.HORIZONTAL_STANDARD_RULES[connections] then
-- Drop an immortal object at this location
local item_entity = minetest.add_item(pos, ItemStack(node.name))
if item_entity then
item_entity:get_luaentity()._immortal = true
end
-- This is a configuration that doesn't exist in the new rail
-- Replace with a standard rail
node.name = "mcl_minecarts:rail_v2"
end
minetest.swap_node(pos, node)
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
end
end
for old,new in pairs(STRAIGHT_RAILS_MAP) do
local new_def = minetest.registered_nodes[new]
minetest.register_node(old, {
drawtype = "raillike",
inventory_image = new_def.inventory_image,
groups = { rail = 1, legacy = 1 },
tiles = { new_def.tiles[1], new_def.tiles[1], new_def.tiles[1], new_def.tiles[1] },
_vl_legacy_convert_node = convert_legacy_straight_rail,
})
vl_legacy.register_item_conversion(old, new)
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -129,9 +129,9 @@ local dropperdef = {
-- If they are containers - double down as hopper -- If they are containers - double down as hopper
mcl_util.hopper_push(pos, droppos) mcl_util.hopper_push(pos, droppos)
end end
if dropnodedef.walkable then if dropnodedef.walkable then return end
return
end -- Build a list of items in the dropper
local stacks = {} local stacks = {}
for i = 1, inv:get_size("main") do for i = 1, inv:get_size("main") do
local stack = inv:get_stack("main", i) local stack = inv:get_stack("main", i)
@ -139,12 +139,32 @@ local dropperdef = {
table.insert(stacks, { stack = stack, stackpos = i }) table.insert(stacks, { stack = stack, stackpos = i })
end end
end end
-- Pick an item to drop
local dropitem = nil
local stack = nil
local r = nil
if #stacks >= 1 then if #stacks >= 1 then
local r = math.random(1, #stacks) r = math.random(1, #stacks)
local stack = stacks[r].stack stack = stacks[r].stack
local dropitem = ItemStack(stack) dropitem = ItemStack(stack)
dropitem:set_count(1) 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 local pos_variation = 100
droppos = vector.offset(droppos, droppos = vector.offset(droppos,
math.random(-pos_variation, pos_variation) / 1000, math.random(-pos_variation, pos_variation) / 1000,
@ -156,6 +176,12 @@ local dropperdef = {
local speed = 3 local speed = 3
item_entity:set_velocity(vector.multiply(drop_vel, speed)) item_entity:set_velocity(vector.multiply(drop_vel, speed))
stack:take_item() 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) inv:set_stack("main", stack_id, stack)
end end
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 S = minetest.get_translator(minetest.get_current_modname())
local F = minetest.formspec_escape
local tonumber = tonumber -- Initialize API
dofile(modpath.."/api.lua")
local color_red = mcl_colors.RED local api = mesecon.commandblock
local command_blocks_activated = minetest.settings:get_bool("mcl_enable_commandblocks", true) 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 msg_not_activated = S("Command blocks are not enabled on this server")
local function construct(pos) local function construct(pos)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
api.initialize(meta)
meta:set_string("commands", "")
meta:set_string("commander", "")
end end
local function after_place(pos, placer) local function after_place(pos, placer)
if placer then local meta = minetest.get_meta(pos)
local meta = minetest.get_meta(pos) api.place(meta, placer)
meta:set_string("commander", placer:get_player_name())
end
end end
local function resolve_commands(commands, pos) local function resolve_commands(commands, pos)
local players = minetest.get_connected_players()
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local commander = meta:get_string("commander") return api.resolve_commands(commands, meta)
-- 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
end end
local function commandblock_action_on(pos, node) local function commandblock_action_on(pos, node)
@ -107,7 +30,6 @@ local function commandblock_action_on(pos, node)
end end
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local commander = meta:get_string("commander")
if not command_blocks_activated then if not command_blocks_activated then
--minetest.chat_send_player(commander, msg_not_activated) --minetest.chat_send_player(commander, msg_not_activated)
@ -115,22 +37,7 @@ local function commandblock_action_on(pos, node)
end end
minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"}) minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"})
local commands = resolve_commands(meta:get_string("commands"), pos) api.action_on(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 end
local function commandblock_action_off(pos, node) 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) minetest.chat_send_player(player:get_player_name(), msg_not_activated)
return return
end 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 meta = minetest.get_meta(pos)
local commands = meta:get_string("commands") api.handle_rightclick(meta, player)
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)
end end
local function on_place(itemstack, placer, pointed_thing) local function on_place(itemstack, placer, pointed_thing)
@ -200,12 +63,10 @@ local function on_place(itemstack, placer, pointed_thing)
end end
-- Use pointed node's on_rightclick function first, if present -- Use pointed node's on_rightclick function first, if present
local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing) local new_stack = mcl_util.call_on_rightclick(itemstack, placer, pointed_thing)
if new_stack then if new_stack then
return new_stack return new_stack
end end
--local node = minetest.get_node(pointed_thing.under)
local privs = minetest.get_player_privs(placer:get_player_name()) local privs = minetest.get_player_privs(placer:get_player_name())
if not privs.maphack then if not privs.maphack then
@ -280,44 +141,6 @@ minetest.register_node("mesecons_commandblock:commandblock_on", {
_mcl_hardness = -1, _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 -- Add entry alias for the Help
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on") doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on")

View File

@ -48,6 +48,11 @@ minetest.register_node("mcl_core:cactus", {
end), end),
_mcl_blast_resistance = 0.4, _mcl_blast_resistance = 0.4,
_mcl_hardness = 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", { minetest.register_node("mcl_core:reeds", {

View File

@ -9,6 +9,8 @@ local function mcl_log(message)
end end
end end
mcl_hoppers = {}
--[[ BEGIN OF NODE DEFINITIONS ]] --[[ BEGIN OF NODE DEFINITIONS ]]
local mcl_hoppers_formspec = table.concat({ 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) mcl_util.hopper_push(pos, dst_pos)
local src_pos = vector.offset(pos, 0, 1, 0) 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 end
local function bent_hopper_act(pos, node, active_object_count, active_object_count_wider) 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 end
local src_pos = vector.offset(pos, 0, 1, 0) 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 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) -- Downwards hopper (base definition)
---@type node_definition ---@type node_definition
@ -199,6 +294,46 @@ local def_hopper = {
minetest.log("action", player:get_player_name() .. minetest.log("action", player:get_player_name() ..
" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos)) " takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
end, 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(), sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 4.8, _mcl_blast_resistance = 4.8,
@ -404,6 +539,66 @@ local def_hopper_side = {
on_rotate = on_rotate, on_rotate = on_rotate,
sounds = mcl_sounds.node_sound_metal_defaults(), 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_blast_resistance = 4.8,
_mcl_hardness = 3, _mcl_hardness = 3,
} }
@ -435,87 +630,6 @@ minetest.register_node("mcl_hoppers:hopper_side_disabled", def_hopper_side_disab
--[[ END OF NODE DEFINITIONS ]] --[[ 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 ]] --[[ 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 and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
mcl_log("Minecart close enough") mcl_log("Minecart close enough")
if entity.name == "mcl_minecarts:hopper_minecart" then 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 elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
hopper_pull_from_mc(entity, pos, 27) hopper_pull_from_mc(entity, pos, 27)
end 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 and (hm_pos.z >= pos.z - DIST_FROM_MC and hm_pos.z <= pos.z + DIST_FROM_MC) then
mcl_log("Minecart close enough") mcl_log("Minecart close enough")
if entity.name == "mcl_minecarts:hopper_minecart" then 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 elseif entity.name == "mcl_minecarts:chest_minecart" or entity.name == "mcl_boats:chest_boat" then
hopper_push_to_mc(entity, pos, 27) hopper_push_to_mc(entity, pos, 27)
end end

View File

@ -3,17 +3,38 @@
-- Adapted for MineClone 2! -- 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!) -- Node names (Don't use aliases!)
tsm_railcorridors.nodes = { tsm_railcorridors.nodes = {
dirt = "mcl_core:dirt", dirt = "mcl_core:dirt",
chest = "mcl_chests:chest", chest = "mcl_chests:chest",
rail = "mcl_minecarts:rail", rail = "mcl_minecarts:rail_v2",
torch_floor = "mcl_torches:torch", torch_floor = "mcl_torches:torch",
torch_wall = "mcl_torches:torch_wall", torch_wall = "mcl_torches:torch_wall",
cobweb = "mcl_core:cobweb", cobweb = "mcl_core:cobweb",
spawner = "mcl_mobspawners:spawner", 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") local mg_name = minetest.get_mapgen_setting("mg_name")
if mg_name == "v6" then if mg_name == "v6" then
@ -36,7 +57,11 @@ else
end end
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. -- This is called after a spawner has been placed by the game.
-- Use this to properly set up the metadata and stuff. -- 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) mcl_mobspawners.setup_spawner(pos, "mobs_mc:cave_spider", 0, 7)
end end
-- This is called after a cart has been placed by the game. -- This is called after a cart has been placed by the game.
-- Use this to properly set up entity metadata and stuff. -- Use this to properly set up entity metadata and stuff.
-- * entity_id - type of cart to create
-- * pos: Position of cart -- * pos: Position of cart
-- * cart: Cart entity -- * pr: pseudorandom
function tsm_railcorridors.on_construct_cart(_, cart, pr_carts) function tsm_railcorridors.create_cart_staticdata(entity_id, pos, pr)
local l = cart:get_luaentity() local uuid = create_minecart(entity_id, pos, vector.new(1,0,0))
local inv = mcl_entity_invs.load_inv(l,27)
local items = tsm_railcorridors.get_treasures(pr_carts) -- Fill the cart with loot
mcl_loot.fill_inventory(inv, "main", items, pr_carts) local cartdata = get_cart_data(uuid)
mcl_entity_invs.save_inv(l) 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 end
-- Fallback function. Returns a random treasure. This function is called for chests -- 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_min = 3,
stacks_max = 3, stacks_max = 3,
items = { 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_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:activator_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:detector_rail", 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", 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. -- 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 pairs = pairs
local tonumber = tonumber local tonumber = tonumber
tsm_railcorridors = {} tsm_railcorridors = {
after = {},
}
-- Load node names -- Load node names
dofile(minetest.get_modpath(minetest.get_current_modname()).."/gameconfig.lua") 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) (can_replace_rail and name == tsm_railcorridors.nodes.rail)
) then ) then
minetest.set_node(pos, node) minetest.set_node(pos, node)
local after = tsm_railcorridors.on_place_node[node.name]
if after then
after(pos, node)
end
return true return true
else else
return false return false
@ -392,27 +398,6 @@ local function PlaceChest(pos, param2)
end end
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. -- Try to place a cobweb.
-- pos: Position of cobweb -- pos: Position of cobweb
-- needs_check: If true, checks if any of the nodes above, below or to the side of the 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> -- See <https://github.com/minetest/minetest/issues/4759>
local cart_id = tsm_railcorridors.carts[cart_type] local cart_id = tsm_railcorridors.carts[cart_type]
minetest.log("info", "[tsm_railcorridors] Cart spawn attempt: "..minetest.pos_to_string(cpos)) 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! -- Try to create cart staticdata
-- Note that the callback function is also called there. local hook = tsm_railcorridors.create_cart_staticdata
-- TODO: Move callback function to this position when the if hook then cart_staticdata = hook(cart_id, cpos, pr) end
-- minetest.add_entity bug has been fixed (supposedly in 5.9.0?)
minetest.after(3, RecheckCartHack, {cpos, cart_id}) minetest.add_entity(cpos, cart_id, cart_staticdata)
end end
end end
carts_table = {} carts_table = {}
@ -950,7 +935,7 @@ end
-- Start generation of a rail corridor system -- Start generation of a rail corridor system
-- main_cave_coords is the center of the floor of the dirt room, from which -- main_cave_coords is the center of the floor of the dirt room, from which
-- all corridors expand. -- all corridors expand.
local function create_corridor_system(main_cave_coords) local function create_corridor_system(main_cave_coords, pr)
-- Dirt room size -- Dirt room size
local maxsize = 6 local maxsize = 6
@ -1111,7 +1096,15 @@ mcl_structures.register_structure("mineshaft",{
end end
if p.y > -10 then return true end if p.y > -10 then return true end
InitRandomizer(blockseed) 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 return true
end, end,

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B