--[[ Minecart ======== Copyright (C) 2019-2020 Joachim Stolberg MIT See license.txt for more information ]]-- -- for lazy programmers local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos local S = minecart.S local RegisteredInventories = {} local param2_to_dir = {[0]= {x=0, y=0, z=1}, {x=1, y=0, z=0}, {x=0, y=0, z=-1}, {x=-1, y=0, z=0}, {x=0, y=-1, z=0}, {x=0, y=1, z=0} } -- Registered carts local tValidCarts = {} -- [] = local lValidCartNodes = {} local tValidCartEntities = {} minetest.tValidCarts = tValidCarts function minecart.register_cart_names(cart_name_stopped, cart_name_running) tValidCarts[cart_name_stopped] = cart_name_running if minetest.registered_nodes[cart_name_stopped] then lValidCartNodes[#lValidCartNodes+1] = cart_name_stopped end if minetest.registered_nodes[cart_name_running] then lValidCartNodes[#lValidCartNodes+1] = cart_name_running end if minetest.registered_entities[cart_name_stopped] then tValidCartEntities[cart_name_stopped] = true end if minetest.registered_entities[cart_name_running] then tValidCartEntities[cart_name_running] = true end end function minecart.get_node_lvm(pos) local node = minetest.get_node_or_nil(pos) if node then return node end local vm = minetest.get_voxel_manip() local MinEdge, MaxEdge = vm:read_from_map(pos, pos) local data = vm:get_data() local param2_data = vm:get_param2_data() local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) local idx = area:indexp(pos) node = { name = minetest.get_name_from_content_id(data[idx]), param2 = param2_data[idx] } return node end function minecart.stopped(vel, tolerance) tolerance = tolerance or 0.05 return math.abs(vel.x) < tolerance and math.abs(vel.z) < tolerance end local function is_air_like(name) local ndef = minetest.registered_nodes[name] if ndef and ndef.buildable_to then return true end return false end function minecart.range(val, min, max) val = tonumber(val) if val < min then return min end if val > max then return max end return val end function minecart.get_next_node(pos, param2) local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos local node = minetest.get_node(pos2) return pos2, node end local function get_cart_object(pos, radius) for _, object in pairs(minetest.get_objects_inside_radius(pos, radius or 0.5)) do if tValidCartEntities[object:get_entity_name()] then local vel = object:get_velocity() if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing? return object end end end end -- check if cart can be pushed function minecart.check_cart_for_pushing(pos, param2, radius) local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then return true end return get_cart_object(pos2, radius) ~= nil end -- check if cargo can be loaded function minecart.check_cart_for_loading(pos, param2, radius) local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then return true end for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do if object:get_entity_name() == "minecart:cart" then local vel = object:get_velocity() if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing? return true end end end return false end local get_next_node = minecart.get_next_node local check_cart_for_loading = minecart.check_cart_for_loading local check_cart_for_pushing = minecart.check_cart_for_pushing -- Take the given number of items from the inv. -- Returns nil if ItemList is empty. function minecart.inv_take_items(inv, listname, num) if inv:is_empty(listname) then return nil end local size = inv:get_size(listname) for idx = 1, size do local items = inv:get_stack(listname, idx) if items:get_count() > 0 then local taken = items:take_item(num) inv:set_stack(listname, idx, items) return taken end end return nil end function minecart.take_items(pos, param2, num) local npos, node if param2 then npos, node = get_next_node(pos, (param2 + 2) % 4) else npos, node = pos, minetest.get_node(pos) end local def = RegisteredInventories[node.name] local owner = M(pos):get_string("owner") local inv = minetest.get_inventory({type="node", pos=npos}) if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then return minecart.inv_take_items(inv, def.take_listname, num) elseif def and def.take_item then return def.take_item(npos, num, owner) else local ndef = minetest.registered_nodes[node.name] if ndef and ndef.minecart_hopper_takeitem then return ndef.minecart_hopper_takeitem(npos, num) end end end function minecart.put_items(pos, param2, stack) local npos, node = get_next_node(pos, param2) local def = RegisteredInventories[node.name] local owner = M(pos):get_string("owner") local inv = minetest.get_inventory({type="node", pos=npos}) if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then local leftover = inv:add_item(def.put_listname, stack) if leftover:get_count() > 0 then return leftover end elseif def and def.put_item then return def.put_item(npos, stack, owner) elseif is_air_like(node.name) or check_cart_for_loading(npos) then minetest.add_item(npos, stack) else local ndef = minetest.registered_nodes[node.name] if ndef and ndef.minecart_hopper_additem then local leftover = ndef.minecart_hopper_additem(npos, stack) if leftover:get_count() > 0 then return leftover end else return stack end end end function minecart.untake_items(pos, param2, stack) local npos, node if param2 then npos, node = get_next_node(pos, (param2 + 2) % 4) else npos, node = pos, minetest.get_node(pos) end local def = RegisteredInventories[node.name] local inv = minetest.get_inventory({type="node", pos=npos}) if def and inv and def.put_listname then return inv:add_item(def.put_listname, stack) elseif def and def.untake_item then return def.untake_item(npos, stack) else local ndef = minetest.registered_nodes[node.name] if ndef and ndef.minecart_hopper_untakeitem then return ndef.minecart_hopper_untakeitem(npos, stack) end end end function minecart.punch_cart(pos, param2, radius, dir) local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos local pos3 = minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) if pos3 then local node = minetest.get_node(pos3) --print(node.name) minecart.node_on_punch(pos3, node, nil, nil, tValidCarts[node.name], dir) return true end local obj = get_cart_object(pos2, radius) if obj then obj:punch(obj, 1.0, { full_punch_interval = 1.0, damage_groups = {fleshy = 1}, }, dir) end end -- Register inventory node for hopper access -- (for examples, see below) function minecart.register_inventory(node_names, def) for _, name in ipairs(node_names) do RegisteredInventories[name] = { allow_put = def.put and def.put.allow_inventory_put, put_listname = def.put and def.put.listname, allow_take = def.take and def.take.allow_inventory_take, take_listname = def.take and def.take.listname, put_item = def.put and def.put.put_item, take_item = def.take and def.take.take_item, untake_item = def.take and def.take.untake_item, } end end function minecart.register_cart_entity(entity_name, node_name, entity_def) entity_def.velocity = {x=0, y=0, z=0} -- only used on punch entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch entity_def.old_pos = nil entity_def.old_switch = 0 entity_def.node_name = node_name minetest.register_entity(entity_name, entity_def) -- register node for punching minecart.register_cart_names(node_name, entity_name) end minetest.register_on_player_receive_fields(function(player, formname, fields) if formname == "minecart:userID_node" then if fields.exit == "Save" or fields.key_enter == "true" then local cart_pos = S2P(player:get_meta():get_string("cart_pos")) local userID = tonumber(fields.userID) or 0 M(cart_pos):set_int("userID", userID) M(cart_pos):set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..player:get_player_name()..": "..userID) minecart.node_at_station(player:get_player_name(), userID, cart_pos) end return true end if formname == "minecart:userID_entity" then if fields.exit == "Save" or fields.key_enter == "true" then local cart_pos = S2P(player:get_meta():get_string("cart_pos")) local obj = get_cart_object(cart_pos) if obj then local entity = obj:get_luaentity() entity.userID = tonumber(fields.userID) or 0 obj:set_nametag_attributes({color = "#ffff00", text = entity.owner..": "..entity.userID}) minecart.update_userID(entity.myID, entity.userID) end end return true end return false end) minecart.register_inventory({"minecart:hopper"}, { put = { allow_inventory_put = function(pos, stack, player_name) local owner = M(pos):get_string("owner") return owner == player_name end, listname = "main", }, take = { allow_inventory_take = function(pos, stack, player_name) local owner = M(pos):get_string("owner") return owner == player_name end, listname = "main", }, })