local EMPTY_BUCKET = logistica.itemstrings.empty_bucket -- sort of weird, but we move liquids around in buckets, so we need some known "empty" bucket local PUMP_MAX_RANGE = logistica.settings.pump_max_range local PUMP_MAX_DEPTH = logistica.settings.pump_max_depth local PUMP_NODES_PER_ROW = 2 * PUMP_MAX_RANGE + 1 local PUMP_NODES_PER_LAYER = PUMP_NODES_PER_ROW * PUMP_NODES_PER_ROW local PUMP_INDEX_MAX = PUMP_NODES_PER_LAYER * PUMP_MAX_DEPTH local META_PUMP_INDEX = "pumpix" local META_OWNER = "pumpowner" local META_LAST_LAYER = "pumplsl" local META_LAST_LAYER_HAD_SUCCESS = "pumplss" local META_MAX_AMOUNT_ON_NETWORK = "pumpmxn" local ON_SUFFIX = "_on" local MAX_CHECKS_PER_TIMER = PUMP_NODES_PER_LAYER -- limits how many nodes the index can advance per timer local TIMER_SHORT = 1.0 local TIMER_LONG = 3.0 local SOURCES_TO_NAMES = nil local PUMP_NEIGHBORS = { vector.new(-1, 0, 0), vector.new( 1, 0, 0), vector.new( 0, 0, 1), vector.new( 0, 0, -1), } local function ends_with(str, ending) return str:sub(-#ending) == ending end local function sources_to_names() if not SOURCES_TO_NAMES then SOURCES_TO_NAMES = logistica.reservoir_get_all_sources_to_names_map() end return SOURCES_TO_NAMES end local function pump_get_index(meta) return meta:get_int(META_PUMP_INDEX) end local function pump_set_index(meta, newIndex) meta:set_int(META_PUMP_INDEX, newIndex) end local function get_owner_name(meta) return meta:get_string(META_OWNER) end local function get_last_layer(meta) return meta:get_int(META_LAST_LAYER) end local function set_last_layer(meta, layerInt) meta:set_int(META_LAST_LAYER, layerInt) end local function get_last_layer_success(meta) return meta:get_int(META_LAST_LAYER_HAD_SUCCESS) == 0 end local function set_last_layer_success(meta, success) meta:set_int(META_LAST_LAYER_HAD_SUCCESS, success and 0 or 1) end local function get_max_level(meta) return meta:get_int(META_MAX_AMOUNT_ON_NETWORK) end local function set_max_level(meta, maxLevel) meta:set_int(META_MAX_AMOUNT_ON_NETWORK, maxLevel) end -- returns nil if target position does not have a valid liquid source -- otherwise returns table {nodeName = "name", isRenewable = true/false, bucketName = "bucket_itemstack_name"} local function get_valid_source(targetPosition, ownerName) logistica.load_position(targetPosition) if minetest.is_protected(targetPosition, ownerName) then return nil end local node = minetest.get_node_or_nil(targetPosition) if not node then return nil end local liquidName = sources_to_names()[node.name] if not liquidName then return nil end -- ensure it's really a source node local nodeDef = minetest.registered_nodes[node.name] if nodeDef.liquidtype ~= "source" then return nil end local bucketsName = logistica.reservoir_get_full_buckets_for_liquid(liquidName) local bucketName = nil for potentialBucket, _ in pairs(bucketsName) do -- basically, try to pick the EMPTY_BUCKET, but if not present for some weird reason, pick any valid filled bucket if bucketName ~= EMPTY_BUCKET then bucketName = potentialBucket end end -- otherwise its valid local isRenewable = nodeDef.liquid_renewable ; if isRenewable == nil then isRenewable = true end -- default value is true, per api docs return { nodeName = node.name, isRenewable = isRenewable, bucketName = bucketName, } end -- returns a vector of the position associated with this index local function pump_index_to_position(pumpPosition, pumpIndex) local x = pumpIndex % PUMP_NODES_PER_LAYER % PUMP_NODES_PER_ROW local y = -math.floor(pumpIndex / PUMP_NODES_PER_LAYER) local z = math.floor((pumpIndex % PUMP_NODES_PER_LAYER) / PUMP_NODES_PER_ROW) return vector.add(pumpPosition, vector.new(x - PUMP_MAX_RANGE, y - 1, z - PUMP_MAX_RANGE)) end -- returns true if succeeded, false if not local function put_liquid_in_neighboring_reservoirs(pumpPosition, bucketItemStack) for _, v in ipairs(PUMP_NEIGHBORS) do local neighborPos = vector.add(pumpPosition, v) logistica.load_position(neighborPos) local neighborNode = minetest.get_node_or_nil(neighborPos) if neighborNode and logistica.GROUPS.reservoirs.is(neighborNode.name) then local resultStack = logistica.reservoir_use_item_on(neighborPos, bucketItemStack, neighborNode) if resultStack ~= nil then return true end end end return false end -- returns true if succeeded, false if not local function put_liquid_in_network_reservoirs(pumpPosition, bucketItemStack, network) if not network then return false end local liquidName = logistica.reservoir_get_liquid_name_for_filled_bucket(bucketItemStack:get_name()) if not liquidName then return false end local amountInNetwork = logistica.get_liquid_info_in_network(pumpPosition, liquidName) local maxAmount = get_max_level(minetest.get_meta(pumpPosition)) if maxAmount > 0 and amountInNetwork.curr >= maxAmount then return false end local resultStack = logistica.use_bucket_for_liquid_in_network(pumpPosition, bucketItemStack) return resultStack ~= nil -- if we got a replacement, it was successfully emptied into network end ---------------------------------------------------------------- -- public functions ---------------------------------------------------------------- function logistica.pump_on_power(pos, power) local node = minetest.get_node_or_nil(pos) if power then logistica.start_node_timer(pos, TIMER_SHORT) if node and not ends_with(node.name, ON_SUFFIX) then logistica.swap_node(pos, node.name..ON_SUFFIX) end else if node and ends_with(node.name, ON_SUFFIX) then logistica.swap_node(pos, node.name:sub(1, #node.name - #ON_SUFFIX)) end end logistica.set_node_tooltip_from_state(pos, nil, power) end function logistica.pump_timer(pos, _) local network = logistica.get_network_or_nil(pos) local meta = minetest.get_meta(pos) local count = 1 local success = false local index = pump_get_index(meta) local owner = get_owner_name(meta) local lastLayer = get_last_layer(meta) local lastLayerSuccess = get_last_layer_success(meta) repeat local targetPosition = pump_index_to_position(pos, index) if targetPosition.y ~= lastLayer then -- new layer reached if lastLayerSuccess then -- let index continue as normal, but reset last layer success set_last_layer_success(meta, false) else -- reset index back to 0, and target position with it index = 0 targetPosition = pump_index_to_position(pos, index) end set_last_layer(meta, targetPosition.y) end local sourceInfo = get_valid_source(targetPosition, owner) if sourceInfo then local bucketItemStack = ItemStack(sourceInfo.bucketName) success = put_liquid_in_neighboring_reservoirs(pos, bucketItemStack) if not success then success = put_liquid_in_network_reservoirs(pos, bucketItemStack, network) end if success then set_last_layer_success(meta, true) if not sourceInfo.isRenewable then -- renewable liquids are not removed to reduce lag minetest.remove_node(targetPosition) end end end index = (index + 1) % PUMP_INDEX_MAX count = count + 1 until (count > MAX_CHECKS_PER_TIMER or success) if success then logistica.start_node_timer(pos, TIMER_SHORT) else logistica.start_node_timer(pos, TIMER_LONG) end pump_set_index(meta, index) -- save index even if no success return false end function logistica.pump_change_max_network_level(pos, change) local meta = minetest.get_meta(pos) local currMax = get_max_level(meta) local newMax = logistica.clamp(currMax + change, 0, 10000) -- 10k arbitrary max set_max_level(meta, newMax) end function logistica.pump_get_max_network_level(pos) local meta = minetest.get_meta(pos) return get_max_level(meta) end