Compare commits

...

112 Commits

Author SHA1 Message Date
teknomunk 89f4209565 Make LBM always run 2024-05-04 04:12:40 +00:00
teknomunk 2a7ffd1528 Make punch move minecarts a little, comment out more debug prints 2024-04-28 11:13:27 +00:00
teknomunk 5feb7706a3 Fix visual artifacts on the sides of rails 2024-04-28 11:13:27 +00:00
teknomunk 5e8d8892f4 Stop carts from reversing when they stop, make stopped carts try to start moving in the direction the player is facing 2024-04-28 11:13:27 +00:00
teknomunk 521d60192f Fix crash after entering a minecart not on rails 2024-04-28 11:13:27 +00:00
teknomunk c57e9402fb Fix placed rail conversion, start automatic inventory rail conversion 2024-04-28 11:13:27 +00:00
teknomunk 2d23d7967f Fix players repelling carts with new player metadata system 2024-04-28 11:13:27 +00:00
teknomunk 4b8a32ac45 Cleanup debug prints 2024-04-28 11:13:27 +00:00
teknomunk 2fd1c001f5 Add documentation for newly exposed attach_driver 2024-04-28 11:13:27 +00:00
teknomunk 4d0c767e66 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-04-28 11:13:27 +00:00
teknomunk cef458a959 More fixes for minecart-hopper movement 2024-04-28 11:13:25 +00:00
teknomunk 2343b2d3df Get rail placement creating corners that lead into a downward sloped rail 2024-04-28 11:12:45 +00:00
teknomunk 137c0d0c49 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-04-28 11:12:45 +00:00
teknomunk d571f6d8f2 Fix hopper-minecart interaction, convert ipairs(table) to use for i=1,#table instead 2024-04-28 11:12:45 +00:00
teknomunk ea2d4ac889 Update mineshafts for new rail and minecarts, add loot to generated chest and hopper minecarts (and remove notes about a hack) 2024-04-28 11:12:45 +00:00
teknomunk c127eb449e Give carts a small vertical lift when pushed to allow them to get back on rails 2024-04-28 11:12:45 +00:00
teknomunk 51a29ed423 Stop rail from being placed directly above rail (floating in air) 2024-04-28 11:12:45 +00:00
teknomunk c905b7c9b5 Fix sloped power,activator and detector rails, remove debug print 2024-04-28 11:12:45 +00:00
teknomunk 51ec5990c3 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-04-28 11:12:45 +00:00
teknomunk e67aeaf800 Add documentation on the rail 2024-04-28 11:12:45 +00:00
teknomunk 15616cb103 Add documentation on file structure and overviewes of each file 2024-04-28 11:12:45 +00:00
teknomunk 1db7d4bc0e Fix crashes, fix link in documentation 2024-04-28 11:12:45 +00:00
teknomunk f18e086da1 More documentation, add myself to copyright list in README.txt 2024-04-28 11:12:45 +00:00
teknomunk fd81d19a94 More minor changes to API.md, start overall implementation documentation 2024-04-28 11:12:45 +00:00
teknomunk 0fedfd2e40 Fix table of contents 2024-04-28 11:12:45 +00:00
teknomunk c54dff1f27 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-04-28 11:12:45 +00:00
teknomunk d3bb27c053 Nearly finish API documentation, create mcl_minecarts.add_blocks_to_map() 2024-04-28 11:12:45 +00:00
teknomunk 077834951f Continue writing API documentation, update call signatures for a couple of API functions 2024-04-28 11:12:45 +00:00
teknomunk fc3de80bf7 Change document formatting, finally move cactus cart dropping to node definition for mcl_core:cactus 2024-04-28 11:12:45 +00:00
teknomunk 5145deb273 Correct crashes/item duplication with dropping carts, start API documentation 2024-04-28 11:12:45 +00:00
teknomunk 62964b4dab Fix cart controls, cart pushing 2024-04-28 11:12:45 +00:00
teknomunk 83b794060a Fix typo in rail replacement mapping, fix several crashes 2024-04-28 11:12:45 +00:00
teknomunk 9bb814620d Implement movement thru tee rails 2024-04-28 11:12:45 +00:00
teknomunk 8eee894429 Tune respawn distance limit 2024-04-28 11:12:45 +00:00
teknomunk cdc2310e23 Fix crashes 2024-04-28 11:12:45 +00:00
teknomunk 19da277b3b Remove memory leak for cart data, check distance to players before respawning distant carts to prevent adding entities that are immediately inactivated 2024-04-28 11:12:45 +00:00
teknomunk 4965d6a24d Implement offline/out of range minecart movement and fix minecart respawning, remove railtype tracking 2024-04-28 11:12:45 +00:00
teknomunk 8fb5412bb0 Remove do_movement dependency on the existence of a cart luaentity 2024-04-28 11:12:45 +00:00
teknomunk 8e71c40394 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-04-28 11:12:45 +00:00
teknomunk dd9ede59dc Make trains containing a player in a minecart function, minor cleanup in mcl_playerinfo 2024-04-28 11:12:45 +00:00
teknomunk f4e74a11e8 Fix crashes in train logic, allow breaking apart trains 2024-04-28 11:12:45 +00:00
teknomunk 95867b5da8 Implement train reversing 2024-04-28 11:12:45 +00:00
teknomunk 7bdceb1c21 Repair vectors in cart data, mostly fix train movement bugs (still possible to have a furnace minecart flip, without the train also flipping) 2024-04-28 11:12:45 +00:00
teknomunk 0e899d1bd0 Add cart entity respawn/destroy to match cart data (partially working) 2024-04-28 11:12:45 +00:00
teknomunk 3252ac7919 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-04-28 11:12:45 +00:00
teknomunk 33866ad21f Fix rail detach crash, make tnt minecarts explode if they hit something hard (off rails) 2024-04-28 11:12:45 +00:00
teknomunk d8bdf7b9d2 Make sure carts get detatch if the rail under them is removed 2024-04-28 11:12:45 +00:00
teknomunk 74f0542ba7 Fixish reorganizing, initial train implementation 2024-04-28 11:12:45 +00:00
teknomunk 9ddfd0c4e1 Major reorganization, start setup for trains 2024-04-28 11:12:45 +00:00
teknomunk 020737266b Make sure carts that collide move in the same direction the colliding cart was 2024-04-28 11:12:45 +00:00
teknomunk 6203d20e04 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-04-28 11:12:45 +00:00
teknomunk 74f9d13361 Harden against unknown nodes 2024-04-28 11:12:44 +00:00
teknomunk 2f15de0150 Allow players to push minecarts that are not on track 2024-04-28 11:12:44 +00:00
teknomunk 8a69a80707 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-04-28 11:12:44 +00:00
teknomunk 7120365652 Fix rail movement regressions 2024-04-28 11:12:44 +00:00
teknomunk 1515a9ecbd Move cart code to its own file, more code cleanup, add aliases for old track items 2024-04-28 11:12:44 +00:00
teknomunk 233495d6f8 Cleanup code, restore uphill/downhill cart movement, completely remove old rail 2024-04-28 11:12:44 +00:00
teknomunk 47ed64f45b Get rail reattachment (especially after jumps) working correctly 2024-04-28 11:12:44 +00:00
teknomunk 1315f7ea95 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-04-28 11:12:44 +00:00
teknomunk f13f52ac3b Silence unmaskable print statements 2024-04-28 11:12:44 +00:00
teknomunk 320e36b455 Add immortal item entity support, add legacy rail conversion that uses immortal item drops for corners/tees/crosses that are no longer possible 2024-04-28 11:12:44 +00:00
teknomunk f322dc9e26 Fix more rail connection bugs 2024-04-28 11:12:44 +00:00
teknomunk e5f194cdd5 Get sloped connections working correctly 2024-04-28 11:12:44 +00:00
teknomunk bd4d09b6e2 Re-enable rule for powering rail from underneath, have stairs block minecart movement, fix crash when lightning strikes a minecart 2024-04-28 11:12:44 +00:00
teknomunk 178719b247 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-04-28 11:12:44 +00:00
teknomunk 256018ca1e Update all rail types to new version 2024-04-28 11:12:44 +00:00
teknomunk 019943346d Reorganize 2024-04-28 11:12:44 +00:00
teknomunk 98f58cd78f Finish reverting 08b41a3b39 2024-04-28 11:12:44 +00:00
teknomunk 6b650511b1 Enable new track with get_next_dir handlers 2024-04-28 11:12:44 +00:00
teknomunk 65cd8dabd8 Change connection rules again to allow building parallel track, tees and crosses), start implementing rail rules callbacks 2024-04-28 11:12:44 +00:00
teknomunk 3ed88a8fba Add sloped rail 2024-04-28 11:12:44 +00:00
teknomunk e27d5a9ae0 Fix rail visuals, add switch operation 2024-04-28 11:12:44 +00:00
teknomunk 59e2ab01a4 Implement initial rail connection logic (no vertical track yet), experiment with texture modifiers and gravel underlay for display (not working) 2024-04-28 11:12:44 +00:00
teknomunk 32e626fa6f Start implementing new rail nodes 2024-04-28 11:12:44 +00:00
teknomunk bdab5b1853 Implement minecart with command block 2024-04-28 11:12:44 +00:00
teknomunk 253c82eb31 Create mesecons command API and modify commandblock to use it 2024-04-28 11:12:44 +00:00
teknomunk 62de57b13c Disable punch to move minecarts, implement punch to drop minecart, enable basic cart keyboard controls (accelerate and brake) 2024-04-28 11:12:44 +00:00
teknomunk 59b32a89e0 Remove cart oscillation when pushed 2024-04-28 11:12:44 +00:00
teknomunk d07a7d4ae6 Limit top speed of furnace minecarts to 4 blocks/second, limit total fuel time to 27 minutes 2024-04-28 11:12:44 +00:00
teknomunk 430842f052 Fix bug with furnace minecart at max velocity (stopped until fuel ran out), move _fueltime into staticdata 2024-04-28 11:12:44 +00:00
teknomunk 52846d67a0 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-04-28 11:12:44 +00:00
teknomunk dbd4675856 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-04-28 11:12:42 +00:00
teknomunk 8bd5559d13 Add API function to remove node watch 2024-04-28 11:11:38 +00:00
teknomunk 2265ac1dce 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-04-28 11:11:38 +00:00
teknomunk 3efd9d123a Start adding hooks for implpementing minecart with command block 2024-04-28 11:11:38 +00:00
teknomunk 91964536c7 Make minecarts solid and add players pushing 2024-04-28 11:11:38 +00:00
teknomunk 9332d828a8 Fix forwards/backwars tilt in all directions 2024-04-28 11:11:38 +00:00
teknomunk df29329d74 Prevent players from entering minecarts when sneaking, prevents players from causing MineClone2/MineClone2#3188 2024-04-28 11:11:38 +00:00
teknomunk 23c2fa8649 Increase default track friction, disable right-click to exit minecarts 2024-04-28 11:11:38 +00:00
teknomunk 7a22c1de23 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-04-28 11:11:38 +00:00
teknomunk 44142b65dd 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-04-28 11:11:38 +00:00
teknomunk 4d807e2716 Mostly fix carts stopping between powered rails (there is still some strangeness with acceleration physics) 2024-04-28 11:11:38 +00:00
teknomunk b179bff3b9 Fix diagonal movement 2024-04-28 11:11:38 +00:00
teknomunk b1cd177bb6 Add diagonal track movement on zig-zag track, rewrite mcl_minecarts:get_rail_direction 2024-04-28 11:11:38 +00:00
teknomunk ada5fe43ee Make TNT minecarts available in creative menu 2024-04-28 11:11:38 +00:00
teknomunk 7736bfc0dd Implement custom item dropper handlers, implement droppers placing minecarts 2024-04-28 11:11:38 +00:00
teknomunk 29fa07f785 Hopper minecarts pull from containers above rail 2024-04-28 11:11:38 +00:00
teknomunk 972b104b68 Rework in preparation to add code to pull from containers into the hopper minecart 2024-04-28 11:11:38 +00:00
teknomunk 72a2dfebc2 Move fiction constant to top of file, suppress cart flips when direction reverses due to gravity or end of track 2024-04-28 11:11:38 +00:00
teknomunk 56bf3257de 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-04-28 11:11:38 +00:00
teknomunk 63ed9bc048 Remove dip into the ground that occured when gravity caused the cart to reverse directions 2024-04-28 11:11:38 +00:00
teknomunk a667721c3d Implement gravity, move orientation update to own function, fix cart stopping in process_acceleration 2024-04-28 11:11:38 +00:00
teknomunk 30ccead2b4 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-04-28 11:11:38 +00:00
teknomunk 8447b99e78 Make minecart always stop at correct location at end of track, fix crash when placing chest minecart after changing how staticdata is handled 2024-04-28 11:11:38 +00:00
teknomunk 382c5ee7d9 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-04-28 11:11:38 +00:00
teknomunk 153917fc35 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-04-28 11:11:38 +00:00
teknomunk 8c13ef1784 Fix initial_properties for minecarts 2024-04-28 11:11:38 +00:00
teknomunk 5a54050197 Change left,right and back vectors to matrix math results with no branching 2024-04-28 11:11:38 +00:00
teknomunk 2b51f34e7c Remove now unused properties from minecart definition, convert more vectors to use vector.new syntax 2024-04-28 11:11:38 +00:00
teknomunk 6ee2a0cf84 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-04-28 11:11:38 +00:00
teknomunk 5cb23790a9 Change staticdata serialization (with migration from old data), disable debugging code used to investigate MineClone2/MineClone2#2446 2024-04-28 11:11:38 +00:00
teknomunk 8ab04f0305 Change to vector.new from {x=...}, relocate movement code to own function for future changes 2024-04-28 11:11:38 +00:00
33 changed files with 4250 additions and 1710 deletions

View File

@ -57,6 +57,15 @@ function mcl_util.mcl_log(message, module, bypass_default_logger)
minetest.log(selected_module .. " " .. message)
end
end
function mcl_util.make_mcl_logger(label, option)
-- Return dummy function if debug option isn't set
if not minetest.settings:get_bool(option,false) then return function() end, false end
local label_text = "["..tostring(label).."]"
return function(message)
mcl_util.mcl_log(message, label_text, true)
end, true
end
local player_timers = {}
@ -343,6 +352,33 @@ function mcl_util.hopper_push(pos, dst_pos)
return ok
end
function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
-- TODO: merge together with hopper_pull after https://git.minetest.land/MineClone2/MineClone2/pulls/4190 is merged
-- Get node pos' for item transfer
local src = minetest.get_node(src_pos)
if not minetest.registered_nodes[src.name] then return end
local src_type = minetest.get_item_group(src.name, "container")
if src_type ~= 2 then return end
local src_def = minetest.registered_nodes[src.name]
local src_list = 'main'
local src_inv, stack_id
if src_def._mcl_hoppers_on_try_pull then
src_inv, src_list, stack_id = src_def._mcl_hoppers_on_try_pull(src_pos, pos, hop_inv, hop_list)
else
local src_meta = minetest.get_meta(src_pos)
src_inv = src_meta:get_inventory()
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list)
end
if stack_id ~= nil then
local ok = mcl_util.move_item(src_inv, src_list, stack_id, hop_inv, hop_list)
if src_def._mcl_hoppers_on_after_pull then
src_def._mcl_hoppers_on_after_pull(src_pos)
end
end
end
-- Try pulling from source inventory to hopper inventory
---@param pos Vector
---@param src_pos Vector
@ -1099,3 +1135,89 @@ function mcl_util.is_it_christmas()
return false
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
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

@ -35,7 +35,9 @@ function mcl_entity_invs.load_inv(ent,size)
mcl_log("load_inv 3")
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
inv:set_size("main", size)
if ent._items then
if ent._mcl_entity_invs_load_items then
inv:set_list("main",ent:_mcl_entity_invs_load_items())
elseif ent._items then
inv:set_list("main",ent._items)
end
else
@ -46,9 +48,14 @@ end
function mcl_entity_invs.save_inv(ent)
if ent._inv then
ent._items = {}
local items = {}
for i,it in ipairs(ent._inv:get_list("main")) do
ent._items[i] = it:to_string()
items[i] = it:to_string()
end
if ent._mcl_entity_invs_save_items then
ent:_mcl_entity_invs_save_items(items)
else
ent._items = items
end
minetest.remove_detached_inventory(ent._inv_id)
ent._inv = nil

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,666 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
local mcl_log,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
-- Imports
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
local table_merge = mcl_util.table_merge
local get_cart_data = mod.get_cart_data
local save_cart_data = mod.save_cart_data
local update_cart_data = mod.update_cart_data
local destroy_cart_data = mod.destroy_cart_data
local find_carts_by_block_map = mod.find_carts_by_block_map
local 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.new(0,0,0),vector.new(0,0,0))
mcl_player.player_set_animation(player, "stand" , 30)
--else
--print("No player object found for "..driver_name)
end
end
-- 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
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 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 mod.kill_cart(staticdata, killer)
local pos
minetest.log("action", "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
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()
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)
cart:set_yaw(minetest.dir_to_yaw(cart_dir))
-- Update static data
local le = cart:get_luaentity()
if le then
le._staticdata = data
end
-- Call placer
if le._mcl_minecarts_on_place then
le._mcl_minecarts_on_place(le, placer)
end
if railpos then
handle_cart_enter(data, railpos)
end
local pname = ""
if placer then
pname = placer:get_player_name()
end
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 = pos + vector.new(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,133 @@
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",
}})
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
tnt.smoke_step(pos)
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,33 @@
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 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)
return {
name = minetest.get_name_from_content_id(data[vi]),
param = param_data[vi],
param2 = param2_data[vi]
}
end
function mcl_minecarts:get_sign(z)
if z == 0 then
@ -10,158 +39,356 @@ end
function mcl_minecarts:velocity_to_dir(v)
if math.abs(v.x) > math.abs(v.z) then
return {x=mcl_minecarts:get_sign(v.x), y=mcl_minecarts:get_sign(v.y), z=0}
return vector.new(
mcl_minecarts:get_sign(v.x),
mcl_minecarts:get_sign(v.y),
0
)
else
return {x=0, y=mcl_minecarts:get_sign(v.y), z=mcl_minecarts:get_sign(v.z)}
return vector.new(
0,
mcl_minecarts:get_sign(v.y),
mcl_minecarts:get_sign(v.z)
)
end
end
function mcl_minecarts:is_rail(pos, railtype)
local node = minetest.get_node(pos).name
if node == "ignore" then
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos, pos)
local area = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax,
}
local data = vm:get_data()
local vi = area:indexp(pos)
node = minetest.get_name_from_content_id(data[vi])
end
if minetest.get_item_group(node, "rail") == 0 then
local node_name = force_get_node(pos).name
if minetest.get_item_group(node_name, "rail") == 0 then
return false
end
if not railtype then
return true
end
return minetest.get_item_group(node, "connect_to_raillike") == railtype
return minetest.get_item_group(node_name, "connect_to_raillike") == railtype
end
function mcl_minecarts:check_front_up_down(pos, dir_, check_down, railtype)
local dir = vector.new(dir_)
-- Front
dir.y = 0
local cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
end
-- Up
if check_down then
dir.y = 1
cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
-- Directional constants
local north = vector.new( 0, 0, 1); local N = 1 -- 4dir = 0
local east = vector.new( 1, 0, 0); local E = 4 -- 4dir = 1
local south = vector.new( 0, 0,-1); local S = 2 -- 4dir = 2 Note: S is overwritten below with the translator
local west = vector.new(-1, 0, 0); local W = 8 -- 4dir = 3
-- Share. Consider moving this to some shared location
mod.north = north
mod.south = south
mod.east = east
mod.west = west
--[[
mcl_minecarts.snap_direction(dir)
returns a valid cart direction that has the smallest angle difference to `dir'
]]
local VALID_DIRECTIONS = {
north, vector.offset(north, 0, 1, 0), vector.offset(north, 0, -1, 0),
south, vector.offset(south, 0, 1, 0), vector.offset(south, 0, -1, 0),
east, vector.offset(east, 0, 1, 0), vector.offset(east, 0, -1, 0),
west, vector.offset(west, 0, 1, 0), vector.offset(west, 0, -1, 0),
}
function mod.snap_direction(dir)
dir = vector.normalize(dir)
local best = nil
local diff = -1
for _,d in pairs(VALID_DIRECTIONS) do
local dot = vector.dot(dir,d)
if dot > diff then
best = d
diff = dot
end
end
-- Down
dir.y = -1
cur = vector.add(pos, dir)
if mcl_minecarts:is_rail(cur, railtype) then
return dir
end
return nil
return best
end
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
local pos = vector.round(pos_)
local cur
local left_check, right_check = true, true
local CONNECTIONS = { north, south, east, west }
local HORIZONTAL_STANDARD_RULES = {
[N] = { "", 0, mask = N, score = 1, can_slope = true },
[S] = { "", 0, mask = S, score = 1, can_slope = true },
[N+S] = { "", 0, mask = N+S, score = 2, can_slope = true },
-- Check left and right
local left = {x=0, y=0, z=0}
local right = {x=0, y=0, z=0}
if dir.z ~= 0 and dir.x == 0 then
left.x = -dir.z
right.x = dir.z
elseif dir.x ~= 0 and dir.z == 0 then
left.z = dir.x
right.z = -dir.x
end
[E] = { "", 1, mask = E, score = 1, can_slope = true },
[W] = { "", 1, mask = W, score = 1, can_slope = true },
[E+W] = { "", 1, mask = E+W, score = 2, can_slope = true },
}
mod.HORIZONTAL_STANDARD_RULES = HORIZONTAL_STANDARD_RULES
if ctrl then
if old_switch == 1 then
left_check = false
elseif old_switch == 2 then
right_check = false
end
if ctrl.left and left_check then
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
if cur then
return cur, 1
end
left_check = false
end
if ctrl.right and right_check then
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
if cur then
return cur, 2
end
right_check = true
end
end
local HORIZONTAL_CURVES_RULES = {
[N+E] = { "_corner", 3, name = "ne corner", mask = N+E, score = 3 },
[N+W] = { "_corner", 2, name = "nw corner", mask = N+W, score = 3 },
[S+E] = { "_corner", 0, name = "se corner", mask = S+E, score = 3 },
[S+W] = { "_corner", 1, name = "sw corner", mask = S+W, score = 3 },
-- Normal
cur = mcl_minecarts:check_front_up_down(pos, dir, true, railtype)
if cur then
return cur
end
[N+E+W] = { "_tee_off", 3, mask = N+E+W, score = 4 },
[S+E+W] = { "_tee_off", 1, mask = S+E+W, score = 4 },
[N+S+E] = { "_tee_off", 0, mask = N+S+E, score = 4 },
[N+S+W] = { "_tee_off", 2, mask = N+S+W, score = 4 },
-- Left, if not already checked
if left_check then
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
if cur then
return cur
end
end
[N+S+E+W] = { "_cross", 0, mask = N+S+E+W, score = 5 },
}
table_merge(HORIZONTAL_CURVES_RULES, HORIZONTAL_STANDARD_RULES)
mod.HORIZONTAL_CURVES_RULES = HORIZONTAL_CURVES_RULES
-- Right, if not already checked
if right_check then
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
if cur then
return cur
end
end
-- Backwards
if not old_switch then
cur = mcl_minecarts:check_front_up_down(pos, {
x = -dir.x,
y = dir.y,
z = -dir.z
}, true, railtype)
if cur then
return cur
end
end
return {x=0, y=0, z=0}
end
local plane_adjacents = {
vector.new(-1,0,0),
vector.new(1,0,0),
vector.new(0,0,-1),
vector.new(0,0,1),
local HORIZONTAL_RULES_BY_RAIL_GROUP = {
[1] = HORIZONTAL_STANDARD_RULES,
[2] = HORIZONTAL_CURVES_RULES,
}
function mcl_minecarts:get_start_direction(pos)
local dir
local i = 0
while (not dir and i < #plane_adjacents) do
i = i+1
local node = minetest.get_node_or_nil(vector.add(pos, plane_adjacents[i]))
if node ~= nil
and minetest.get_item_group(node.name, "rail") == 0
and minetest.get_item_group(node.name, "solid") == 1
and minetest.get_item_group(node.name, "opaque") == 1
then
dir = mcl_minecarts:check_front_up_down(pos, vector.multiply(plane_adjacents[i], -1), true)
end
local function check_connection_rule(pos, connections, rule)
-- All bits in the mask must be set for the connection to be possible
if bit.band(rule.mask,connections) ~= rule.mask then
--print("Mask mismatch ("..tostring(rule.mask)..","..tostring(connections)..")")
return false
end
return dir
-- If there is an allow filter, that mush also return true
if rule.allow and rule.allow(rule, connections, pos) then
return false
end
return true
end
function mcl_minecarts:set_velocity(obj, dir, factor)
obj._velocity = vector.multiply(dir, factor or 3)
obj._old_pos = nil
obj._punched = true
local function make_sloped_if_straight(pos, dir)
local node = minetest.get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
local param2 = 0
if dir == east then
param2 = 3
elseif dir == west then
param2 = 1
elseif dir == north then
param2 = 2
elseif dir == south then
param2 = 0
end
if get_path( nodedef, "_mcl_minecarts", "railtype" ) == "straight" then
minetest.swap_node(pos, {name = nodedef._mcl_minecarts.base_name .. "_sloped", param2 = param2})
end
end
local function is_connection(pos, dir)
local node = force_get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
local get_next_dir = get_path(nodedef, "_mcl_minecarts", "get_next_dir")
if not get_next_dir then return end
local next_dir = get_next_dir(pos, dir, node)
next_dir.y = 0
return vector.equals(next_dir, dir)
end
local function get_rail_connections(pos, opt)
local legacy = opt and opt.legacy
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
local connections = 0
for i = 1,#CONNECTIONS do
dir = CONNECTIONS[i]
local neighbor = vector.add(pos, dir)
local node = force_get_node(neighbor)
local nodedef = minetest.registered_nodes[node.name]
-- Only allow connections to the open ends of rails, as decribed by get_next_dir
if mcl_minecarts:is_rail(neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
local rev_dir = vector.direction(dir,vector.zero())
if ignore_neighbor_connections or is_connection(neighbor, rev_dir) then
connections = bit.bor(connections, bit.lshift(1,i - 1))
end
end
-- Check for sloped rail one block down
local below_neighbor = vector.offset(neighbor, 0, -1, 0)
local node = force_get_node(below_neighbor)
local nodedef = minetest.registered_nodes[node.name]
if mcl_minecarts:is_rail(below_neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
local rev_dir = vector.direction(dir, vector.zero())
if ignore_neighbor_connections or is_connection(below_neighbor, rev_dir) then
connections = bit.bor(connections, bit.lshift(1,i - 1))
end
end
end
return connections
end
mod.get_rail_connections = get_rail_connections
local function update_rail_connections(pos, opt)
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
local node = minetest.get_node(pos)
local nodedef = minetest.registered_nodes[node.name]
if not nodedef or not nodedef._mcl_minecarts then
minetest.log("warning", "attemting to rail connect to "..node.name)
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.new(0,0,0))
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
function mcl_minecarts:get_rail_direction(pos_, dir)
local pos = vector.round(pos_)
-- 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 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()
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
staticdata.connected_at = staticdata.connected_at + staticdata.dir
staticdata.distance = 1 - (staticdata.distance or 0)
-- recalculate direction
local next_dir,_ = mod:get_rail_direction(staticdata.connected_at, next_dir)
staticdata.dir = next_dir
end

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
name = mcl_minecarts
author = Krock
description = Minecarts are vehicles to move players quickly on rails.
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
optional_depends = doc_identifier, mcl_wip
depends = mcl_title, mcl_explosions, mcl_core, mcl_util, mcl_sounds, mcl_player, mcl_playerinfo, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
optional_depends = doc_identifier, mcl_wip, mcl_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,543 @@
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 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 detach_minecart(staticdata)
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 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 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.new(0,0,0),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
local a = calculate_acceleration(staticdata)
local v_0 = staticdata.velocity
-- 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)..
",timestep="..tostring(timestep)..
",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
-- 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 mcl_physics then
mcl_physics.apply_entity_environmental_physics(self)
else
-- Simple physics
local friction = self.object:get_velocity() or vector.new(0,0,0)
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_r = vector.round(self.object:get_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)
-- Clear freebody movement
self.object:set_velocity(vector.new(0,0,0))
self.object:set_acceleration(vector.new(0,0,0))
end
end
--return do_movement, do_detatched_movement
return do_movement,do_detached_movement,handle_cart_enter

View File

@ -1,48 +1,399 @@
local S = minetest.get_translator(minetest.get_current_modname())
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mod = mcl_minecarts
local S = minetest.get_translator(modname)
mod.RAIL_GROUPS = {
STANDARD = 1,
CURVES = 2,
}
-- Template rail function
local function register_rail(itemstring, tiles, def_extras, creative)
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=0,destroy_by_lava_flow=0, transport=1}
if creative == false then
groups.not_in_creative_inventory = 1
end
local ndef = {
drawtype = "raillike",
tiles = tiles,
is_ground_content = false,
inventory_image = tiles[1],
wield_image = tiles[1],
paramtype = "light",
walkable = false,
selection_box = {
type = "fixed",
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
},
stack_max = 64,
groups = groups,
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 0.7,
_mcl_hardness = 0.7,
after_destruct = function(pos)
-- Scan for minecarts in this pos and force them to execute their "floating" check.
-- Normally, this will make them drop.
local objs = minetest.get_objects_inside_radius(pos, 1)
for o=1, #objs do
local le = objs[o]:get_luaentity()
if le then
-- All entities in this mod are minecarts, so this works
if string.sub(le.name, 1, 14) == "mcl_minecarts:" then
le._last_float_check = mcl_minecarts.check_float_time
end
end
end
end,
}
if def_extras then
for k,v in pairs(def_extras) do
ndef[k] = v
-- Inport functions and constants from elsewhere
local table_merge = mcl_util.table_merge
local check_connection_rules = mod.check_connection_rules
local update_rail_connections = mod.update_rail_connections
local north = mod.north
local south = mod.south
local east = mod.east
local west = mod.west
--- Rail direction Handleres
local function rail_dir_straight(pos, dir, node)
dir = vector.new(dir.x, 0, dir.z)
if node.param2 == 0 or node.param2 == 2 then
if vector.equals(dir, north) then
return north
else
return south
end
else
if vector.equals(dir,east) then
return east
else
return west
end
end
end
local function rail_dir_sloped(pos, dir, node)
local uphill = minetest.fourdir_to_dir(node.param2)
local downhill = minetest.fourdir_to_dir((node.param2+2)%4)
local up_uphill = vector.offset(uphill,0,1,0)
if vector.equals(dir, uphill) or vector.equals(dir, up_uphill) then
return up_uphill
else
return downhill
end
end
local function rail_dir_curve(pos, dir, node)
dir = vector.new(dir.x, 0, dir.z)
if node.param2 == 0 then -- north
-- South and East
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return east end
if vector.equals(dir, west) then return south end
if vector.equals(dir, east) then return east end
elseif node.param2 == 1 then -- east
-- South and West
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return west end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return south end
elseif node.param2 == 2 then
-- North and West
if vector.equals(dir, south) then return west end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return north end
elseif node.param2 == 3 then
-- North and East
if vector.equals(dir, south) then return east end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return north end
if vector.equals(dir, east) then return east end
end
end
local function rail_dir_tee_off(pos, dir, node)
dir = vector.new(dir.x, 0, dir.z)
if node.param2 == 0 then -- north
-- South and East
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return east end
if vector.equals(dir, west) then return south end
if vector.equals(dir, east) then return east end
elseif node.param2 == 1 then -- east
-- South and West
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return west end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return south end
elseif node.param2 == 2 then
-- North and West
if vector.equals(dir, south) then return west end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return north end
elseif node.param2 == 3 then
-- North and East
if vector.equals(dir, south) then return east end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return north end
if vector.equals(dir, east) then return east end
end
end
local function rail_dir_tee_on(pos, dir, node)
dir = vector.new(dir.x, 0, dir.z)
if node.param2 == 0 then -- north
-- South and East
if vector.equals(dir, south) then return east end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return north end
if vector.equals(dir, east) then return east end
elseif node.param2 == 1 then -- east
-- South and West
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return east end
if vector.equals(dir, west) then return south end
if vector.equals(dir, east) then return east end
elseif node.param2 == 2 then
-- North and West
if vector.equals(dir, south) then return south end
if vector.equals(dir, north) then return west end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return south end
elseif node.param2 == 3 then
-- North and East
if vector.equals(dir, south) then return west end
if vector.equals(dir, north) then return north end
if vector.equals(dir, west) then return west end
if vector.equals(dir, east) then return north end
end
end
local function rail_dir_cross(pos, dir, node)
dir = vector.new(dir.x, 0, dir.z)
-- 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",
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),{
description = S("Sloped Rail"), -- Temporary name to make debugging easier
_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] },
groups = {
not_in_creative_inventory = 1,
},
mesecons = {
effector = {
action_on = function(pos, node)
local new_node = {name = base_name.."_tee_on", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
},
_mcl_minecarts = {
get_next_dir = rail_dir_tee_off,
railtype = "tee",
},
}))
mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
tiles = { tiles[4] },
_mcl_minecarts = {
get_next_dir = rail_dir_tee_on,
railtype = "tee",
},
mesecons = {
effector = {
action_off = function(pos, node)
local new_node = {name = base_name.."_tee_off", param2 = node.param2}
minetest.swap_node(pos, new_node)
end,
rules = mesecon.rules.alldirs,
}
}
}))
-- Sloped variant
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
description = S("Sloped Rail"), -- Temporary name to make debugging easier
_mcl_minecarts = {
get_next_dir = rail_dir_sloped,
railtype = "tee",
},
tiles = { tiles[1] },
}))
-- Cross variant
mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
tiles = { tiles[5] },
_mcl_minecarts = {
get_next_dir = rail_dir_cross,
railtype = "cross",
},
}))
end
function mod.register_rail_sloped(itemstring, def)
assert(def.tiles)
-- Build the node definition
local ndef = table.copy(SLOPED_RAIL_DEF)
table_merge(ndef, def)
-- Add sensible defaults
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))
-- Make registrations
minetest.register_node(itemstring, ndef)
end
@ -66,200 +417,309 @@ local rail_rules_long =
local rail_rules_short = mesecon.rules.pplate
local railuse = S("Place them on the ground to build your railway, the rails will automatically connect to each other and will turn into curves, T-junctions, crossings and slopes as needed.")
-- Normal rail
register_rail("mcl_minecarts:rail",
{"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
{
description = S("Rail"),
_tt_help = S("Track for minecarts"),
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
_doc_items_usagehelp = railuse,
}
)
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 = 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"},
}
},
})
-- 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,
},
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 = 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 = 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 = 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)
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",
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,
},
false
)
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:golden_rail_v2",
onstate = "mcl_minecarts:golden_rail_v2_on",
rules = 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 = rail_rules_long,
},
},
drop = "mcl_minecarts:golden_rail_v2",
})
-- 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,
},
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 = 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 = 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 = 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)
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",
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,
},
false
)
mesecons = {
conductor = {
state = mesecon.state.on,
offstate = "mcl_minecarts:activator_rail_v2",
onstate = "mcl_minecarts:activator_rail_v2_on",
rules = 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 = 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",
})
-- 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,
},
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 = 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)
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",
mod.register_straight_rail("mcl_minecarts:detector_rail_v2_on",{"mcl_minecarts_rail_detector_powered.png"},{
groups = {
not_in_creative_inventory = 1,
},
false
)
_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)
-- 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"},
}
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",
})
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
if minetest.get_modpath("doc") then
doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
end
local CURVY_RAILS_MAP = {
["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
}
for old,new in pairs(CURVY_RAILS_MAP) do
minetest.register_node(old, {
inventory_image = minetest.registered_nodes[new].inventory_image,
groups = { rail = 1 }
})
end
minetest.register_lbm({
name = "mcl_minecarts:update_legacy_curvy_rails",
nodenames = mcl_util.table_keys(CURVY_RAILS_MAP),
action = function(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
})
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",
}
for old,new in pairs(STRAIGHT_RAILS_MAP) do
minetest.register_node(old, {
inventory_image = minetest.registered_nodes[new].inventory_image,
groups = { rail = 1 }
})
end
local TRANSLATE_RAILS_MAP = table.copy(STRAIGHT_RAILS_MAP)
table_merge(TRANSLATE_RAILS_MAP, CURVY_RAILS_MAP)
minetest.register_lbm({
name = "mcl_minecarts:update_legacy_straight_rails",
nodenames = mcl_util.table_keys(STRAIGHT_RAILS_MAP),
run_at_every_load = true,
action = function(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
})
-- Convert old rail in the player's inventory to new rail
minetest.register_on_joinplayer(function(player)
local inv = player:get_inventory()
local size = inv:get_size("main")
for i=1,size do
local stack = inv:get_stack("main", i)
local new_name = TRANSLATE_RAILS_MAP[stack:get_name()]
if new_name then
stack:set_name(new_name)
inv:set_stack("main", i, stack)
end
end
end)

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,155 @@
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
local function 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
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)
--local staticdata = cart._staticdata
-- Only update from the back
if staticdata.behind or not staticdata.ahead then return end
--print("\nUpdating train")
-- 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 = 0
local count = 0
for cart in train_cars(staticdata) do
velocity = velocity + (cart.velocity or 0)
count = count + 1
end
velocity = velocity / count
--print("Using velocity "..tostring(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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,17 +3,38 @@
-- Adapted for MineClone 2!
-- Imports
local create_minecart = mcl_minecarts.create_minecart
local get_cart_data = mcl_minecarts.get_cart_data
local save_cart_data = mcl_minecarts.save_cart_data
-- Node names (Don't use aliases!)
tsm_railcorridors.nodes = {
dirt = "mcl_core:dirt",
chest = "mcl_chests:chest",
rail = "mcl_minecarts:rail",
rail = "mcl_minecarts:rail_v2",
torch_floor = "mcl_torches:torch",
torch_wall = "mcl_torches:torch_wall",
cobweb = "mcl_core:cobweb",
spawner = "mcl_mobspawners:spawner",
}
local update_rail_connections = mcl_minecarts.update_rail_connections
local rails_to_update = {}
tsm_railcorridors.on_place_node = {
[tsm_railcorridors.nodes.rail] = function(pos, node)
rails_to_update[#rails_to_update + 1] = pos
end,
}
tsm_railcorridors.on_start = function()
rails_to_update = {}
end
tsm_railcorridors.on_finish = function()
for _,pos in pairs(rails_to_update) do
update_rail_connections(pos, {legacy = true, ignore_neighbor_connections = true})
end
end
local mg_name = minetest.get_mapgen_setting("mg_name")
if mg_name == "v6" then
@ -37,19 +58,31 @@ else
end
-- TODO: Use minecart with chest instead of normal minecart
tsm_railcorridors.carts = { "mcl_minecarts: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,
}
function tsm_railcorridors.on_construct_cart(pos, cart)
-- TODO: Fill cart with treasures
function tsm_railcorridors.create_cart_staticdata(entity_id,pos, pr)
local uuid = create_minecart(entity_id, pos, vector.new(1,0,0))
-- This is it? There's this giant hack announced in
-- the other file and I grep for the function and it's
-- a stub? :)
-- Fill the cart with loot
local cartdata = get_cart_data(uuid)
if cartdata and has_loot[entity_id] then
local items = tsm_railcorridors.get_treasures(pr)
-- The path here using some minetest.after hackery was
-- deactivated in init.lua - reactivate when this does
-- something the function is called RecheckCartHack.
-- Convert from ItemStack to itemstrings
for k,item in pairs(items) do
items[k] = item:to_string()
end
cartdata.inventory = items
print("cartdata = "..dump(cartdata))
save_cart_data(uuid)
end
return minetest.serialize({ uuid=uuid, seq=1 })
end
-- Fallback function. Returns a random treasure. This function is called for chests
@ -102,11 +135,11 @@ function tsm_railcorridors.get_treasures(pr)
stacks_min = 3,
stacks_max = 3,
items = {
{ itemstring = "mcl_minecarts:rail", weight = 20, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_minecarts:rail_v2", weight = 20, amount_min = 4, amount_max = 8 },
{ itemstring = "mcl_torches:torch", weight = 15, amount_min = 1, amount_max = 16 },
{ itemstring = "mcl_minecarts:activator_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:detector_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:golden_rail", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:activator_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:detector_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
{ itemstring = "mcl_minecarts:golden_rail_v2", weight = 5, amount_min = 1, amount_max = 4 },
}
},
-- non-MC loot: 50% chance to add a minecart, offered as alternative to spawning minecarts on rails.

View File

@ -1,7 +1,9 @@
local pairs = pairs
local tonumber = tonumber
tsm_railcorridors = {}
tsm_railcorridors = {
after = {},
}
-- Load node names
dofile(minetest.get_modpath(minetest.get_current_modname()).."/gameconfig.lua")
@ -170,6 +172,10 @@ local function SetNodeIfCanBuild(pos, node, check_above, can_replace_rail)
(can_replace_rail and name == tsm_railcorridors.nodes.rail)
) then
minetest.set_node(pos, node)
local after = tsm_railcorridors.on_place_node[node.name]
if after then
after(pos, node)
end
return true
else
return false
@ -394,34 +400,6 @@ local function PlaceChest(pos, param2)
end
end
-- This function checks if a cart has ACTUALLY been spawned.
-- To be calld by minetest.after.
-- This is a workaround thanks to the fact that minetest.add_entity is unreliable as fuck
-- See: https://github.com/minetest/minetest/issues/4759
-- FIXME: Kill this horrible hack with fire as soon you can.
-- Why did anyone activate it in the first place? It doesn't
-- have a function seeing as there are no chest minecarts yet.
--[[
local function RecheckCartHack(params)
local pos = params[1]
local cart_id = params[2]
-- Find cart
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
if obj 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)
return
end
end
minetest.log("info", "[tsm_railcorridors] Cart spawn FAILED: "..minetest.pos_to_string(pos))
end
--]]
-- Try to place a cobweb.
-- pos: Position of cobweb
-- needs_check: If true, checks if any of the nodes above, below or to the side of the cobweb.
@ -944,16 +922,13 @@ local function spawn_carts()
-- See <https://github.com/minetest/minetest/issues/4759>
local cart_id = tsm_railcorridors.carts[cart_type]
minetest.log("info", "[tsm_railcorridors] Cart spawn attempt: "..minetest.pos_to_string(cpos))
minetest.add_entity(cpos, cart_id)
local cart_staticdata = nil
-- This checks if the cart is actually spawned, it's a giant hack!
-- Note that the callback function is also called there.
-- TODO: Move callback function to this position when the
-- minetest.add_entity bug has been fixed.
-- Try to create cart staticdata
local hook = tsm_railcorridors.create_cart_staticdata
if hook then cart_staticdata = hook(cart_id, cpos, pr) end
-- minetest.after(3, RecheckCartHack, {cpos, cart_id})
-- This whole recheck logic leads to a stub right now
-- it can be reenabled when chest carts are a thing.
minetest.add_entity(cpos, cart_id, cart_staticdata)
end
end
carts_table = {}
@ -1117,13 +1092,22 @@ mcl_structures.register_structure("mineshaft",{
y_min = mcl_vars.mg_overworld_min,
place_func = function(pos,def,pr,blockseed)
local r = pr:next(-50,-10)
r = -10
local p = vector.offset(pos,0,r,0)
if p.y < mcl_vars.mg_overworld_min + 5 then
p.y = mcl_vars.mg_overworld_min + 5
end
if p.y > -10 then return true end
--if p.y > -10 then return true end
InitRandomizer(blockseed)
local hook = tsm_railcorridors.on_start
if hook then hook() end
create_corridor_system(p, pr)
local hook = tsm_railcorridors.on_finish
if hook then hook() end
return true
end,

View File

@ -25,7 +25,7 @@ tsm_railcorridors_probability_chest (Chest probability) float 0.05 0.0 1.0
#of finding a cart in rail corridors with high rail damage will be lower.
#NOTE: Due to a bug in Minetest <https://github.com/minetest/minetest/issues/4759>
#carts often fail to spawn even if they should.
tsm_railcorridors_probability_cart (Cart probability) float 0.0 0.0 1.0
tsm_railcorridors_probability_cart (Cart probability) float 0.05 0.0 1.0
#If enabled, cobwebs may be placed in some corridors.
#Currently, cobwebs are only supported with the Mobs Redo mod.

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB