require "defines" require "util" -- Energy used per distance per item teleported per tick. energy_per_item = 10 -- The length of time that the energy is used, this smooths out the usage a bit. energy_usage_time = 100 remote.add_interface("TeleBelts", { print_telepairs = function() for name, pair in pairs(global.teles.telepairs) do game.player.print(name) end end, clean = function() global.teles = {gui_timer = 0, gui_open = false, gui_pair_name = nil, gui_entity = nil, telepairs = {}, version = util.current_version } end }) script.on_init(function() on_init() end) script.on_load(function() on_load() end) script.on_event(defines.events.on_tick, function(event) on_tick(event) end) script.on_event(defines.events.on_built_entity, function(event) on_built_entity(event) end) script.on_event(defines.events.on_entity_died, function(event) on_entity_died(event) end) script.on_event(defines.events.on_preplayer_mined_item, function(event) on_preplayer_mined_item(event) end) script.on_event(defines.events.on_gui_click, function(event) on_gui_click(event) end) -- Lookup table for the position offsets of item drops on transport belts. belt_offsets_by_direction = {[0] = {left = {x = -0.24, y = 0 }, right = {x = 0.24, y = 0 }}, [2] = {left = {x = 0 , y = -0.24}, right = {x = 0 , y = 0.24}}, [4] = {left = {x = 0.24, y = 0 }, right = {x = -0.24, y = 0 }}, [6] = {left = {x = 0 , y = 0.24}, right = {x = 0 , y = -0.24}} } function on_init() --game.player.print("TeleBelts: on_init") if global.teles == nil then global.teles = {gui_timer = 0, gui_open = false, gui_pair_name = nil, gui_entity = nil, telepairs = {}, version = util.current_version } end end function on_load() --game.player.print("TeleBelts: on_load") -- Call on_init to ensure global.teles exists. on_init() end function on_tick(event) --game.player.print("TeleBelts: on_tick") -- Selection has to be done though a hacky hover, since custom entity on_click is not possible yet. for index, player in ipairs(game.players) do if game.player.selected ~= nil then if is_tele(game.player.selected) then global.teles.gui_timer = global.teles.gui_timer + 1 end if global.teles.gui_timer >= 100 then open_gui(player.selected, index) end else global.teles.gui_timer = 0 end end for name, telepair in pairs(global.teles.telepairs) do if telepair.receiver and telepair.receiver.valid and telepair.sender and telepair.sender.valid then -- Use the energy to be used from previous ticks. telepair.energy_usage[game.tick % energy_usage_time + 1] = 0 for i, energy in pairs(telepair.energy_usage) do telepair.sender.energy = telepair.sender.energy - energy end -- If there's enough energy left. if telepair.sender.energy > energy_per_item * telepair.dist_sq then --game.player.print("Handling pair " .. name) local valid = false -- Check if there's still items in the stored stacks and if they're still valid. for i, stack in pairs(telepair.stacks) do if stack.valid then valid = true break end -- If they're not, search for new stacks inside this tile. if not stack.valid then telepair.stacks = game.find_entities_filtered{name = "item-on-ground", area = {{telepair.sender.position.x - 0.5, telepair.sender.position.y - 0.5}, {telepair.sender.position.x + 0.5, telepair.sender.position.y + 0.5}} } --game.player.print("Found " .. #telepair.stacks .. " entities") --game.player.print("Looping over " .. #telepair.stacks .. " entities") local teleported_any = false local max_I = 0 for i, stack in pairs(telepair.stacks) do max_I = i if stack.valid then -- Relative position. local r = {x = (stack.position.x - telepair.sender.position.x), y = (stack.position.y - telepair.sender.position.y)} -- Determine whether the item that entered is on the left or right side of the belt. local was_on_left = math.abs(r.x) > math.abs(r.y) if r.x * r.y < 0 then was_on_left = not was_on_left end -- Get the required offset at the target location. local off = belt_offsets_by_direction[telepair.receiver.direction].right if was_on_left then off = belt_offsets_by_direction[telepair.receiver.direction].left end -- Calculate position to drop the item. local new_pos = {telepair.receiver.position.x + off.x, telepair.receiver.position.y + off.y} -- Check if the target location is empty. local possible_pos = game.find_non_colliding_position("item-on-ground", new_pos, 0.10, 0.01) if possible_pos then telepair.energy_usage[game.tick % energy_usage_time + 1] = telepair.energy_usage[game.tick % energy_usage_time + 1] + energy_per_item * telepair.dist_sq --telepair.sender.energy = telepair.sender.energy - energy_per_item * telepair.dist_sq game.create_entity{position=possible_pos, name="item-on-ground", stack={name=stack.stack.name, count=stack.stack.count}} stack.destroy() --stack.teleport(possible_pos) teleported_any = true end -- If none were teleported, it's possible one lane is full, so force rescan. if not teleported_any then telepair.stacks = {} end end end end end end end end end function on_built_entity(event) --game.player.print("TeleBelts: on_built_entity") if is_tele(event.created_entity) then if global.teles.gui_open then close_gui(game.players[event.player_index]) end open_gui(event.created_entity, event.player_index) end end function on_entity_died(event) --game.player.print("TeleBelts: on_entity_died") if not is_tele(event.entity) then return end remove_receiver_or_sender(event.entity,1)--1 is dirty hack end function on_preplayer_mined_item(event) --game.player.print("TeleBelts: on_preplayer_mined_item") if not is_tele(event.entity) then return end remove_receiver_or_sender(event.entity, event.player_index) end function on_gui_click(event) --game.player.print("TeleBelts: on_gui_click") if event.element.name == "tb_okbutton" then local old_name = global.teles.gui_pair_name local new_name = game.players[event.player_index].gui.center.tb_frame.tb_textfield.text if string.len(new_name) > 0 then if not global.teles.telepairs[old_name] then game.players[event.player_index].print("TeleBelts: Clicked OK in gui with invalid global.teles.gui_pair_name (" .. old_name .. ").") end change_receiver_or_sender_telepair(global.teles.gui_entity, new_name, old_name, event.player_index) else game.players[event.player_index].print("TeleBelts: Cannot reroute, target name must be at least 1 character long.") end close_gui(game.players[event.player_index]) end end function open_gui(entity, player_index) --game.player.print("TeleBelts: open_gui") if global.teles.gui_open then close_gui(game.players[player_index]) end if not is_tele(entity) then return nil end local is_sender = entity.name == "telesender" pair_name = find_telepair_name(entity) if pair_name == nil then pair_name = "def_" .. game.tick --game.player.print("Created new pair: " .. pair_name) change_receiver_or_sender_telepair(entity, pair_name, nil, player_index) end global.teles.gui_pair_name = pair_name global.teles.gui_entity = entity local frame = game.players[player_index].gui.center.add({type="frame", name="tb_frame", caption="Rename this one", direction="vertical"}) local textfield = frame.add({type="textfield", name="tb_textfield", caption="", style="textfield_style"}) textfield.style.maximal_width=300 textfield.style.minimal_width=300 textfield.caption = pair_name textfield.text = pair_name frame.add{type = "button", name = "tb_okbutton", caption = "Confirm", style = "button_style"} global.teles.gui_open = true end function close_gui(player) --game.player.print("TeleBelts: close_gui") if not global.teles.gui_open then player.print("TeleBelts: Tried to close gui while it's not open.") return end if player.gui.center.tb_frame ~= nil then player.gui.center.tb_frame.destroy() end global.teles.gui_pair_name = nil global.teles.gui_entity = nil end function remove_receiver_or_sender(entity, player_index) if not is_tele(entity) then game.players[player_index].print("TeleBelts: remove_receiver_or_sender received entity " .. entity.name .. ".") return end local pair_name = find_telepair_name(entity) tp, nottype = get_tele_type(entity) if not global.teles.telepairs[pair_name] then game.players[player_index].print("TeleBelts: remove_receiver_or_sender received invalid telepair name " .. pair_name .. ".") return end global.teles.telepairs[pair_name][tp] = nil clean_pair(pair_name) end function change_receiver_or_sender_telepair(entity, new_pair_name, old_pair_name, player_index) if not is_tele(entity) then game.players[player_index].print("TeleBelts: change_telepair_receiver_or_sender received invalid entity " .. entity.name .. ".") return end if old_pair_name == new_pair_name then return end if not global.teles.telepairs[new_pair_name] then global.teles.telepairs[new_pair_name] = {sender = nil, receiver = nil, dist_sq = 0, stacks = {}, energy_usage = {} } end local new_pair = global.teles.telepairs[new_pair_name] tp, nottype = get_tele_type(entity) if new_pair[tp] then game.players[player_index].print("TeleBelts: Cannot reroute " .. tp .. ", target name (" .. new_pair_name .. ") already taken.") return end -- If the old pair exists, remove it from that pair and clean the pair. if old_pair_name and global.teles.telepairs[old_pair_name] then global.teles.telepairs[old_pair_name][tp] = nil clean_pair(old_pair_name) end new_pair[tp] = entity -- Calculate squared distance between sender and receiver. if new_pair[nottype] then --game.player.print("type = " .. tp .. ", nottype = " .. nottype) local diff = {x = new_pair[tp].position.x - new_pair[nottype].position.x, y = new_pair[tp].position.y - new_pair[nottype].position.y} new_pair.dist_sq = diff.x * diff.x + diff.y * diff.y end end function find_telepair_name(entity) --game.player.print("TeleBelts: find_telepair_name") if not entity.valid then return nil end if entity.name == "telesender" then for name, telepair in pairs(global.teles.telepairs) do if telepair.sender and telepair.sender.valid then if telepair.sender.position.x == entity.position.x and telepair.sender.position.y == entity.position.y then return name end end end elseif entity.name == "telereceiver" then for name, telepair in pairs(global.teles.telepairs) do if telepair.receiver and telepair.receiver.valid then if telepair.receiver.position.x == entity.position.x and telepair.receiver.position.y == entity.position.y then return name end end end end --game.player.print("TeleBelts: find_telepair_name: Not found.") return nil end function clean_pair(pair_name, player_index) if not global.teles.telepairs[pair_name] then game.players[player_index].print("TeleBelts: clean_pair received nonexistent pair (" .. pair_name .. ")") return end if not global.teles.telepairs[pair_name].sender or not global.teles.telepairs[pair_name].sender.valid then if not global.teles.telepairs[pair_name].receiver or not global.teles.telepairs[pair_name].receiver.valid then -- No valid sender and no valid receiver. Delete the pair. global.teles.telepairs[pair_name] = nil end end end function get_tele_type(entity) local tp = "sender" local nottype = "receiver" if entity.name == "telereceiver" then tp = "receiver" nottype = "sender" end return tp, nottype end function is_tele(entity) if not entity or not entity.valid then return false end if entity.name == "telesender" or entity.name == "telereceiver" then return true end return false end