Advertisement
Guest User

control.lua

a guest
Apr 25th, 2017
207
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.82 KB | None | 0 0
  1. --Generate event ID's for custom events
  2. local events ={}
  3. events.bottleneck_toggle = script.generate_event_name()
  4. events.rebuild_overlays = script.generate_event_name()
  5.  
  6. --Message Everyone
  7. local function msg(message)
  8.   game.print(message)
  9. end
  10.  
  11. --[[ Remove the light]]
  12. local function remove_signal(event)
  13.   local entity = event.entity
  14.   local index = entity.unit_number
  15.   local overlays = global.overlays
  16.   local data = overlays[index]
  17.   if not data then return end
  18.   local signal = data.signal
  19.   if signal and signal.valid then
  20.     signal.destroy()
  21.   end
  22.   overlays[index] = nil
  23.   --table.remove(overlays, index)
  24. end
  25.  
  26. --[[ Calculates bottom center of the entity to place bottleneck there ]]
  27. local function get_signal_position_from(entity, shift_x, shift_y)
  28.   local left_top = entity.prototype.selection_box.left_top
  29.   local right_bottom = entity.prototype.selection_box.right_bottom
  30.   --Calculating center of the selection box
  31.   shift_x = shift_x or (right_bottom.x + left_top.x) / 2
  32.   shift_y = shift_y or right_bottom.y
  33.   --Calculating bottom center of the selection box
  34.   return {x = entity.position.x + shift_x, y = entity.position.y + shift_y}
  35. end
  36.  
  37. --[[ code modified from AutoDeconstruct mod by mindmix https://mods.factorio.com/mods/mindmix ]]
  38. local function check_drill_depleted(data)
  39.   local drill = data.entity
  40.   local position = drill.position
  41.   local range = drill.prototype.mining_drill_radius
  42.   local top_left = {x = position.x - range, y = position.y - range}
  43.   local bottom_right = {x = position.x + range, y = position.y + range}
  44.   local resources = drill.surface.find_entities_filtered{area={top_left, bottom_right}, type='resource'}
  45.   for _, resource in pairs(resources) do
  46.     if resource.prototype.resource_category == 'basic-solid' and resource.amount > 0 then
  47.       return false
  48.     end
  49.   end
  50.   data.drill_depleted = true
  51.   return true
  52. end
  53.  
  54. local function has_fluid_output_available(entity)
  55.   local fluidbox = entity.fluidbox
  56.   if (not fluidbox) or (#fluidbox == 0) then return false end
  57.   local recipe = entity.recipe
  58.   if not recipe then return false end
  59.   for _, product in pairs(recipe.products) do
  60.     if product.type == 'fluid' then
  61.       local name = product.name
  62.       for i = 1, #fluidbox do
  63.         local fluid = fluidbox[i]
  64.         if fluid and (fluid.type == name) and (fluid.amount > 0) then
  65.           return true
  66.         end
  67.       end
  68.     end
  69.   end
  70.   return false
  71. end
  72.  
  73. local light = {
  74.   off = defines.direction.north,
  75.   green = defines.direction.east,
  76.   red = defines.direction.south,
  77.   yellow = defines.direction.west,
  78. }
  79.  
  80. --Faster to just change the color than it is to check it first.
  81. local function change_signal(signal, signal_color)
  82.   signal_color = light[signal_color] or "red"
  83.   signal.direction = signal_color
  84. end
  85.  
  86. local update = {}
  87. function update.drill(data)
  88.   if data.drill_depleted then return end
  89.   local entity = data.entity
  90.   local progress = data.progress
  91.   if (entity.energy == 0) or (entity.mining_target == nil and check_drill_depleted(data)) then
  92.     change_signal(data.signal, "red")
  93.   elseif (entity.mining_progress == progress) then
  94.     change_signal(data.signal, "yellow")
  95.   else
  96.     change_signal(data.signal, "green")
  97.     data.progress = entity.mining_progress
  98.   end
  99. end
  100.  
  101. function update.machine(data)
  102.   local entity = data.entity
  103.   if entity.energy == 0 then
  104.     change_signal(data.signal, "red")
  105.   elseif entity.is_crafting() and (entity.crafting_progress < 1) and (entity.bonus_progress < 1) then
  106.     change_signal(data.signal, "green")
  107.   elseif (entity.crafting_progress >= 1) or (entity.bonus_progress >= 1) or (not entity.get_output_inventory().is_empty()) or (has_fluid_output_available(entity)) then
  108.     change_signal(data.signal, "yellow")
  109.   else
  110.     change_signal(data.signal, "red")
  111.   end
  112. end
  113.  
  114. function update.furnace(data)
  115.   local entity = data.entity
  116.   if entity.energy == 0 then
  117.     change_signal(data.signal, "red")
  118.   elseif entity.is_crafting() and (entity.crafting_progress < 1) and (entity.bonus_progress < 1) then
  119.     change_signal(data.signal, "green")
  120.   elseif (entity.crafting_progress >= 1) or (entity.bonus_progress >= 1) or (not entity.get_output_inventory().is_empty()) or (has_fluid_output_available(entity)) then
  121.     change_signal(data.signal, "yellow")
  122.   else
  123.     change_signal(data.signal, "red")
  124.   end
  125. end
  126.  
  127. --[[ A function that is called whenever an entity is built (both by player and by robots) ]]--
  128. local function built(event)
  129.   local entity = event.created_entity
  130.   --local surface = entity.surface
  131.   local data
  132.  
  133.   -- If the entity that's been built is an assembly machine or a furnace...
  134.   if entity.type == "assembling-machine" then
  135.     data = { update = "machine" }
  136.   elseif entity.type == "furnace" then
  137.     data = { update = "furnace" }
  138.   elseif entity.type == "mining-drill" then
  139.     data = { update = "drill" }
  140.   end
  141.   if data then
  142.     data.entity = entity
  143.     data.position = get_signal_position_from(entity)
  144.     local name = (global.high_contrast and "bottleneck-stoplight-high") or "bottleneck-stoplight"
  145.     local signal = entity.surface.create_entity{name=name, position=data.position, direction=light.off, force=entity.force}
  146.     signal.active = false
  147.     signal.operable = false
  148.     signal.destructible = false
  149.     data.signal=signal
  150.  
  151.     if global.show_bottlenecks == 1 then
  152.       update[data.update](data)
  153.     end
  154.     global.overlays[entity.unit_number] = data
  155.     -- if we are in the process of removing lights, we need to restart
  156.     -- that, since inserting into the overlays table may mess up the
  157.     -- iteration order.
  158.     if global.show_bottlenecks == -1 then
  159.       global.update_index = nil
  160.     end
  161.   end
  162. end
  163.  
  164. local function rebuild_overlays()
  165.   --[[Setup the global overlays table This table contains the machine entity, the signal entity and the freeze variable]]--
  166.   global.overlays = {}
  167.   global.update_index = nil
  168.   msg("Bottleneck: Rebuilding data from scratch")
  169.  
  170.   --[[Find all assembling machines on the map. Check each surface]]--
  171.   for _, surface in pairs(game.surfaces) do
  172.     --find-entities-filtered with no area argument scans for all entities in loaded chunks and should
  173.     --be more effiecent then scanning through all chunks like in previous version
  174.  
  175.     --[[destroy any existing bottleneck-signals]]--
  176.     for _, stoplight in pairs(surface.find_entities_filtered{type="storage-tank"}) do
  177.       if stoplight.name == "bottleneck-stoplight" or stoplight.name == "bottleneck-stoplight-high" then
  178.         stoplight.destroy()
  179.       end
  180.     end
  181.  
  182.     --[[Find all assembling machines within the bounds, and pretend that they were just built]]--
  183.     for _, am in pairs(surface.find_entities_filtered{type="assembling-machine"}) do
  184.       built({created_entity = am})
  185.     end
  186.  
  187.     --[[Find all furnaces within the bounds, and pretend that they were just built]]--
  188.     for _, am in pairs(surface.find_entities_filtered{type="furnace"}) do
  189.       built({created_entity = am})
  190.     end
  191.  
  192.     --[[Find all mining-drills within the bounds, and pretend that they were just built]]--
  193.     for _, am in pairs(surface.find_entities_filtered{type="mining-drill"}) do
  194.       built({created_entity = am})
  195.     end
  196.     script.raise_event(events.rebuild_overlays, {})
  197.   end
  198. end
  199.  
  200. local next = next --very slight perfomance improvment
  201. local function on_tick()
  202.   if global.show_bottlenecks == 1 then
  203.     local signals_per_tick = global.signals_per_tick or 40
  204.     local overlays = global.overlays
  205.     local index, data = global.update_index
  206.     --check for existing data at index
  207.     if index and overlays[index] then
  208.       data = overlays[index]
  209.     else
  210.       index, data = next(overlays, index)
  211.     end
  212.     local numiter = 0
  213.     while index and (numiter < signals_per_tick) do
  214.       local entity = data.entity
  215.       local signal = data.signal
  216.  
  217.       -- if entity is valid, update it, otherwise remove the signal and the associated data
  218.       if entity.valid and signal.valid then
  219.         update[data.update](data)
  220.       elseif entity.valid and not signal.valid then
  221.         -- Rebuild the icon something broke it!
  222.         local name = (global.high_contrast and "bottleneck-stoplight-high") or "bottleneck-stoplight"
  223.         signal = entity.surface.create_entity{name=name, position=data.position, direction=light.off, force=entity.force}
  224.         data.signal = signal
  225.       elseif not entity.valid and signal.valid then
  226.         signal.destroy()
  227.         overlays[index] = nil
  228.       end
  229.       numiter = numiter + 1
  230.  
  231.       index, data = next(overlays, index)
  232.     end
  233.     global.update_index = index
  234.   elseif global.show_bottlenecks < 0 then
  235.     local show, signals_per_tick = global.show_bottlenecks, global.signals_per_tick or 40
  236.     local overlays = global.overlays
  237.     local index, data = global.update_index
  238.     --Check for existing index and associated data
  239.     if index and overlays[index] then
  240.       data = overlays[index]
  241.     else
  242.       index, data = next(overlays, index)
  243.     end
  244.     local numiter = 0
  245.     while index and (numiter < signals_per_tick) do
  246.       local signal = data.signal
  247.       if signal and signal.valid then
  248.         if show == -1 then
  249.           change_signal(signal, "off")
  250.         elseif show == -2 then
  251.           local name = (global.high_contrast and "bottleneck-stoplight-high") or "bottleneck-stoplight"
  252.           local signal2 = signal.surface.create_entity{name=name, position=data.position, direction=signal.direction, force=signal.force}
  253.           signal.destroy()
  254.           data.signal = signal2
  255.         end
  256.       else
  257.         overlays[index] = nil
  258.       end
  259.       numiter = numiter + 1
  260.       index, data = next(overlays, index)
  261.     end
  262.     global.update_index = index
  263.     -- if we have reached the end of the list (i.e., have removed all lights),
  264.     -- pause updating until enabled by hotkey next
  265.     if not index then
  266.       global.show_bottlenecks = (show == -2 and 1) or (show == -1 and 0)
  267.     end
  268.   end
  269. end
  270.  
  271. -------------------------------------------------------------------------------
  272. --[[HOTKEYS]]--
  273. local function on_hotkey(event)
  274.   local player = game.players[event.player_index]
  275.   if not player.admin then
  276.     player.print('Bottleneck: You do not have privileges to toggle bottleneck')
  277.     return
  278.   end
  279.   global.update_index = nil
  280.   if global.show_bottlenecks == 1 then
  281.     script.raise_event(events.bottleneck_toggle, {tick=event.tick, player_index=event.player_index, enable=false})
  282.     global.show_bottlenecks = -1
  283.   else
  284.     script.raise_event(events.bottleneck_toggle, {tick=event.tick, player_index=event.player_index, enable=true})
  285.     global.show_bottlenecks = 1
  286.   end
  287. end
  288.  
  289. local function toggle_highcontrast(event) --luacheck: ignore
  290.   local player = game.players[event.player_index]
  291.   if not player.admin then
  292.     player.print('Bottleneck: You do not have privileges to toggle contrast')
  293.     return
  294.   end
  295.   global.high_contrast = not global.high_contrast
  296.   msg('Bottleneck: Using '..(global.high_contrast and 'high' or 'normal')..' contrast mode')
  297.   global.show_bottlenecks = -2
  298.   global.update_index = nil
  299.   --Toggling high contrast will turn bottlenecks back on if they are off
  300.   --This is better then saving and comparing "old_show_bottlenecks"
  301. end
  302.  
  303. -------------------------------------------------------------------------------
  304. --[[Init Events]]
  305. local function init()
  306.   --seperate out init and config changed
  307.   global = {}
  308.   global.show_bottlenecks = 1
  309.   global.signals_per_tick = 40
  310.   global.high_contrast = false
  311.   rebuild_overlays()
  312. end
  313.  
  314. local function on_configuration_changed(event)
  315.   --Any MOD has been changed/added/removed, including base game updates.
  316.   if event.mod_changes ~= nil then --should never be nil
  317.     msg("Bottleneck: Game or mod version changes detected")
  318.  
  319.     --This mod has changed
  320.     local changes = event.mod_changes["Bottleneck"]
  321.     if changes ~= nil then -- THIS Mod has changed
  322.       msg("Bottleneck: Updated from ".. tostring(changes.old_version) .. " to " .. tostring(changes.new_version))
  323.     end
  324.   end
  325.   global.show_bottlenecks = global.showbottlenecks or 1
  326.   global.signals_per_tick = global.lights_per_tick or 40
  327.   global.lights_per_tick = nil
  328.   global.showbottlenecks = nil
  329.   global.output_idle_signal = nil
  330.   global.high_contrast = global.high_contrast or false
  331.   rebuild_overlays()
  332. end
  333.  
  334. --[[ Setup event handlers ]]--
  335. script.on_init(init)
  336. script.on_configuration_changed(on_configuration_changed)
  337. local e=defines.events
  338. local remove_events = {e.on_preplayer_mined_item, e.on_robot_pre_mined, e.on_entity_died}
  339. local add_events = {e.on_built_entity, e.on_robot_built_entity}
  340.  
  341. script.on_event(remove_events, remove_signal)
  342. script.on_event(add_events, built)
  343. script.on_event(defines.events.on_tick, on_tick)
  344. script.on_event("bottleneck-hotkey", on_hotkey)
  345. script.on_event("bottleneck-highcontrast", toggle_highcontrast)
  346.  
  347. --[[ Setup remote interface]]--
  348. local interface = {}
  349. --get_ids, return the table of event ids
  350. interface.get_ids = function() return events end
  351. --is_enabled - return show_bottlenecks
  352. interface.enabled = function() return global.show_bottlenecks == 1 end
  353. --print the global to a file
  354. interface.print_global = function () game.write_file("logs/Bottleneck/global.lua",serpent.block(global, {comment=false}),false) end
  355. --rebuild all icons
  356. interface.rebuild = rebuild_overlays
  357. --change signals per tick calculation
  358. interface.signals_per_tick = function(count) global.signals_per_tick = tonumber(count) or 40 return global.signals_per_tick end
  359. --allow other mods to interact with bottlneck
  360. interface.change_signal = change_signal --function(data, color) change_signal(signal, color) end
  361. --get a place position for a signal
  362. interface.get_position_for_signal = get_signal_position_from
  363. --get the signal data associated with an entity
  364. interface.get_signal_data = function(unit_number) return global.overlays[unit_number] end
  365.  
  366. remote.add_interface("Bottleneck", interface)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement