Advertisement
Guest User

Factorissimo-BioIndustries-Fix

a guest
Apr 27th, 2022
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 50.39 KB | None | 0 0
  1. require("layout")
  2. local HasLayout = HasLayout
  3.  
  4. require("connections")
  5. local Connections = Connections
  6.  
  7. require("updates")
  8. local Updates = Updates
  9.  
  10. require("compat.factoriomaps")
  11.  
  12. local mod_gui = require("mod-gui")
  13. -- DATA STRUCTURE --
  14.  
  15. -- Factory buildings are entities of type "storage-tank" internally, because reasons
  16. local BUILDING_TYPE = "storage-tank"
  17.  
  18. --[[
  19. factory = {
  20.     +outside_surface = *,
  21.     +outside_x = *,
  22.     +outside_y = *,
  23.     +outside_door_x = *,
  24.     +outside_door_y = *,
  25.  
  26.     +inside_surface = *,
  27.     +inside_x = *,
  28.     +inside_y = *,
  29.     +inside_door_x = *,
  30.     +inside_door_y = *,
  31.  
  32.     +force = *,
  33.     +layout = *,
  34.     +building = *,
  35.     +outside_energy_receiver = *,
  36.     +outside_overlay_displays = {*},
  37.     +outside_fluid_dummy_connectors = {*},
  38.     +outside_port_markers = {*},
  39.     (+)outside_other_entities = {*},
  40.  
  41.     +inside_overlay_controller = *,
  42.     +inside_fluid_dummy_connectors = {*},
  43.     +inside_power_poles = {*},
  44.     (+)outside_power_pole = *,
  45.  
  46.     (+)middleman_id = *,
  47.     (+)direct_connection = *,
  48.  
  49.     +stored_pollution = *,
  50.  
  51.     +connections = {*},
  52.     +connection_settings = {{*}*},
  53.     +connection_indicators = {*},
  54.  
  55.     +upgrades = {},
  56. }
  57. ]]--
  58.  
  59. -- INITIALIZATION --
  60.  
  61. local function init_globals()
  62.     -- List of all factories
  63.     global.factories = global.factories or {}
  64.     -- Map: Save name -> Factory it is currently saving
  65.     global.saved_factories = global.saved_factories or {}
  66.     -- Map: Player or robot -> Save name to give him on the next relevant event
  67.     global.pending_saves = global.pending_saves or {}
  68.     -- Map: Entity unit number -> Factory it is a part of
  69.     global.factories_by_entity = global.factories_by_entity or {}
  70.     -- Map: Surface name -> list of factories on it
  71.     global.surface_factories = global.surface_factories or {}
  72.     -- Map: Surface name -> number of used factory spots on it
  73.     global.surface_factory_counters = global.surface_factory_counters or {}
  74.     -- Scalar
  75.     global.next_factory_surface = global.next_factory_surface or 0
  76.     -- Map: Player index -> Last teleport time
  77.     global.last_player_teleport = global.last_player_teleport or {}
  78.     -- Map: Player index -> Whether preview is activated
  79.     global.player_preview_active = global.player_preview_active or {}
  80.     -- List of all factory power pole middlemen
  81.     global.middleman_power_poles = global.middleman_power_poles or {}
  82. end
  83.  
  84. local prepare_gui = 0  -- Function stub
  85. local update_hidden_techs = 0 -- Function stub
  86. local power_middleman_surface = 0 -- Function stub
  87. local cancel_creation = 0 -- Function stub
  88.  
  89. local function init_gui()
  90.     for _, player in pairs(game.players) do
  91.         prepare_gui(player)
  92.     end
  93. end
  94.  
  95. script.on_init(function()
  96.     init_globals()
  97.     Connections.init_data_structure()
  98.     Updates.init()
  99.     init_gui()
  100.     power_middleman_surface()
  101.     for _, force in pairs(game.forces) do
  102.         update_hidden_techs(force)
  103.     end
  104.     Compat.handle_factoriomaps()
  105. end)
  106.  
  107. script.on_load(function()
  108.     Compat.handle_factoriomaps()
  109. end)
  110.  
  111. script.on_configuration_changed(function(config_changed_data)
  112.     init_globals()
  113.     Updates.run()
  114.     init_gui()
  115.     power_middleman_surface()
  116.     for surface_name, _ in pairs(global.surface_factories or {}) do
  117.         if remote.interfaces["RSO"] then -- RSO compatibility
  118.             pcall(remote.call, "RSO", "ignoreSurface", surface_name)
  119.         end
  120.     end
  121. end)
  122.  
  123. -- DATA MANAGEMENT --
  124.  
  125. local function set_entity_to_factory(entity, factory)
  126.     global.factories_by_entity[entity.unit_number] = factory
  127. end
  128.  
  129. local function get_factory_by_entity(entity)
  130.     if entity == nil then return nil end
  131.     return global.factories_by_entity[entity.unit_number]
  132. end
  133.  
  134. local function get_factory_by_building(entity)
  135.     local factory = global.factories_by_entity[entity.unit_number]
  136.     if factory == nil then
  137.         game.print("ERROR: Unbound factory building: " .. entity.name .. "@" .. entity.surface.name .. "(" .. entity.position.x .. ", " .. entity.position.y .. ")")
  138.     end
  139.     return factory
  140. end
  141.  
  142. local function find_factory_by_building(surface, area)
  143.     local candidates = surface.find_entities_filtered{area=area, type=BUILDING_TYPE}
  144.     for _,entity in pairs(candidates) do
  145.         if HasLayout(entity.name) then return get_factory_by_building(entity) end
  146.     end
  147.     return nil
  148. end
  149.  
  150. local function find_surrounding_factory(surface, position)
  151.     local factories = global.surface_factories[surface.name]
  152.     if factories == nil then return nil end
  153.     local x = math.floor(0.5+position.x/(16*32))
  154.     local y = math.floor(0.5+position.y/(16*32))
  155.     if (x > 7 or x < 0) then return nil end
  156.     return factories[8*y+x+1]
  157. end
  158.  
  159. -- POWER MANAGEMENT --
  160.  
  161. function power_middleman_surface()
  162.     if game.surfaces["factory-power-connection"] then
  163.         return game.surfaces["factory-power-connection"]
  164.     end
  165.    
  166.     if #game.surfaces == 256 then
  167.         error("Unfortunately you have no available surfaces left for Factorissimo2. You cannot use Factorissimo2 on this map.")
  168.     end
  169.    
  170.     local map_gen_settings = {height=1, width=1, property_expression_names={}}
  171.     map_gen_settings.autoplace_settings = {
  172.         ["decorative"] = {treat_missing_as_default=false, settings={}},
  173.         ["entity"] = {treat_missing_as_default=false, settings={}},
  174.         ["tile"] = {treat_missing_as_default=false, settings={["out-of-map"]={}}},
  175.     }
  176.    
  177.     local surface = game.create_surface("factory-power-connection", map_gen_settings)
  178.     surface.set_chunk_generated_status({0, 0}, defines.chunk_generated_status.entities)
  179.     surface.set_chunk_generated_status({-1, 0}, defines.chunk_generated_status.entities)
  180.     surface.set_chunk_generated_status({0, -1}, defines.chunk_generated_status.entities)
  181.     surface.set_chunk_generated_status({-1, -1}, defines.chunk_generated_status.entities)
  182.    
  183.     return surface
  184. end
  185.  
  186. local function remove_direct_connection(factory)
  187.     local dc = factory.direct_connection
  188.     if not dc or not dc.valid then return end
  189.    
  190.     for _, pole in pairs(factory.inside_power_poles) do
  191.         for _, neighbour in pairs(pole.neighbours.copper) do
  192.             if neighbour == dc then
  193.                 local old = {}
  194.                 for _, neighbour in ipairs(dc.neighbours.copper) do
  195.                     if neighbour ~= pole then old[#old+1] = neighbour end
  196.                 end
  197.                 dc.disconnect_neighbour()
  198.                 for _, neighbour in ipairs(old) do
  199.                     dc.connect_neighbour(neighbour)
  200.                 end
  201.                 factory.direct_connection = nil
  202.                 return
  203.             end
  204.         end
  205.     end
  206. end
  207.  
  208. local function delete_middleman(i)
  209.     local pole = global.middleman_power_poles[i]
  210.     if pole == 0 then return end
  211.     global.middleman_power_poles[i] = i < #global.middleman_power_poles and 0 or nil
  212.     pole.destroy()
  213.     for _, factory in pairs(global.factories) do
  214.         if factory.middleman_id == i then
  215.             factory.middleman_id = nil
  216.         end
  217.     end
  218. end
  219.  
  220. local function cleanup_middlemen()
  221.     for i, pole in ipairs(global.middleman_power_poles) do
  222.         if pole ~= 0 and #pole.neighbours.copper<2 then delete_middleman(i) end
  223.     end
  224. end
  225.  
  226. -- power poles can only connect to 5 other power poles. give priority to factory poles
  227. local our_poles = {["factory-power-connection"] = true, ["factory-power-pole"] = true, ["factory-overflow-pole"] = true}
  228. local function reduce_neighbours(pole)
  229.     local neighbours = pole.neighbours.copper
  230.     if #neighbours < 5 or #neighbours == 0 then return true end
  231.    
  232.     local n
  233.     for i, neighbour in ipairs(neighbours) do
  234.         if not our_poles[neighbour.name] and neighbour.surface == pole.surface then n = i break end
  235.     end
  236.    
  237.     if n == nil then
  238.         pole.surface.create_entity{name="flying-text", position=pole.position, text={"electric-pole-wire-limit-reached"}}
  239.         return false
  240.     end
  241.    
  242.     pole.disconnect_neighbour(neighbours[n])
  243.     return true
  244. end
  245.  
  246. local function available_pole(factory)
  247.     local poles = factory.inside_power_poles
  248.     for i, pole in ipairs(poles) do
  249.         local next = poles[i+1]
  250.         if next then
  251.             next.connect_neighbour(pole)
  252.         end
  253.     end
  254.    
  255.     for i, pole in ipairs(poles) do
  256.         if #pole.neighbours.copper < (i == #poles and 4 or 5) then return pole end
  257.     end
  258.    
  259.     local layout = factory.layout
  260.     local pole = factory.inside_surface.create_entity{name="factory-overflow-pole", position=poles[1].position, force=poles[1].force}
  261.     pole.destructible = false
  262.     pole.disconnect_neighbour()
  263.     pole.connect_neighbour(poles[#poles])
  264.     table.insert(poles, pole)
  265.     return pole
  266. end
  267.  
  268. local function connect_power(factory, pole)
  269.     if not reduce_neighbours(pole) then return end
  270.     factory.outside_power_pole = pole
  271.    
  272.     if factory.inside_surface.name ~= pole.surface.name then
  273.         available_pole(factory).connect_neighbour(pole)
  274.         factory.direct_connection = pole
  275.         return
  276.     end
  277.    
  278.     local n
  279.     for i, pole in ipairs(global.middleman_power_poles) do
  280.         if pole == 0 then n = i break end
  281.     end
  282.     n = n or #global.middleman_power_poles + 1
  283.    
  284.     local surface = power_middleman_surface()
  285.     local middleman = surface.create_entity{name = "factory-power-connection", position = {2*(n%32), 2*math.floor(n/32)}, force = "neutral"}
  286.     middleman.destructible = false
  287.     global.middleman_power_poles[n] = middleman
  288.    
  289.     middleman.connect_neighbour(available_pole(factory))
  290.     middleman.connect_neighbour(pole)
  291.    
  292.     factory.middleman_id = n
  293. end
  294.  
  295. function update_power_connection(factory, pole) -- pole parameter is optional
  296.     local electric_network = factory.outside_energy_receiver.electric_network_id
  297.     if electric_network == nil then return end
  298.     local surface = factory.outside_surface
  299.     local x = factory.outside_x
  300.     local y = factory.outside_y
  301.    
  302.     if not script.active_mods['factorissimo-power-pole-addon'] and global.surface_factory_counters[surface.name] then
  303.         local surrounding = find_surrounding_factory(surface, {x=x, y=y})
  304.         if surrounding then
  305.             connect_power(factory, available_pole(surrounding))
  306.             return
  307.         end
  308.     end
  309.    
  310.     -- find the nearest connected power pole
  311.     local D = game.max_electric_pole_supply_area_distance + factory.layout.outside_size / 2
  312.     local canidates = {}
  313.     for _, entity in ipairs(surface.find_entities_filtered{type="electric-pole", area={{x-D, y-D}, {x+D,y+D}}}) do
  314.         if entity.electric_network_id == electric_network and entity ~= pole then
  315.             canidates[#canidates+1] = entity
  316.         end
  317.     end
  318.    
  319.     if #canidates == 0 then return end
  320.     connect_power(factory, surface.get_closest({x, y}, canidates))
  321. end
  322.  
  323. local function power_pole_placed(pole)
  324.     local D = pole.prototype.supply_area_distance + 10
  325.     local position = pole.position
  326.     local x = position.x
  327.     local y = position.y
  328.    
  329.     for _, entity in ipairs(pole.surface.find_entities_filtered{type=BUILDING_TYPE, area={{x-D, y-D}, {x+D,y+D}}}) do
  330.         if not HasLayout(entity.name) then goto continue end
  331.         factory = get_factory_by_building(entity)
  332.         local electric_network = factory.outside_energy_receiver.electric_network_id
  333.         if electric_network == nil or electric_network ~= pole.electric_network_id then goto continue end
  334.         if electric_network == factory.inside_power_poles[1].electric_network_id then goto continue end
  335.        
  336.         connect_power(factory, pole)
  337.        
  338.         ::continue::
  339.     end
  340. end
  341.  
  342. local function power_pole_destroyed(pole)
  343.     pole.disconnect_neighbour()
  344.    
  345.     for _, factory in pairs(global.factories) do
  346.         if factory.built and factory.outside_power_pole and factory.outside_power_pole.valid and factory.outside_power_pole == pole then
  347.             update_power_connection(factory, pole)
  348.         end
  349.     end
  350.    
  351.     cleanup_middlemen()
  352. end
  353.  
  354. -- FACTORY UPGRADES --
  355.  
  356. local function build_lights_upgrade(factory)
  357.     if factory.upgrades.lights then return end
  358.     factory.upgrades.lights = true
  359.     factory.inside_surface.daytime = 1
  360. end
  361.  
  362. function build_display_upgrade(factory)
  363.     if not factory.force.technologies["factory-interior-upgrade-display"].researched then return end
  364.     if factory.inside_overlay_controller and factory.inside_overlay_controller.valid then return end
  365.  
  366.     pos = factory.layout.overlays
  367.     local controller = factory.inside_surface.create_entity{
  368.         name = "factory-overlay-controller",
  369.         position = {
  370.             factory.inside_x + pos.inside_x,
  371.             factory.inside_y + pos.inside_y
  372.         },
  373.         force = factory.force
  374.     }
  375.     controller.minable = false
  376.     controller.destructible = false
  377.     controller.rotatable = false
  378.     factory.inside_overlay_controller = controller
  379. end
  380.  
  381. -- OVERLAY MANAGEMENT --
  382.  
  383. local sprite_path_translation = {
  384.     item = "item",
  385.     fluid = "fluid",
  386.     virtual = "virtual-signal",
  387. }
  388. local function draw_overlay_sprite(signal, target_entity, offset, scale, id_table)
  389.  
  390.     local sprite_name = sprite_path_translation[signal.type] .. "/" .. signal.name
  391.     if target_entity.valid then
  392.         local sprite_data = {
  393.             sprite = sprite_name,
  394.             x_scale = scale,
  395.             y_scale = scale,
  396.             target = target_entity,
  397.             surface = target_entity.surface,
  398.             only_in_alt_mode = true,
  399.             render_layer = "entity-info-icon",
  400.         }
  401.         -- Fake shadows
  402.         local shadow_radius = 0.07 * scale
  403.         for _, shadow_offset in pairs({{0,shadow_radius}, {0, -shadow_radius}, {shadow_radius, 0}, {-shadow_radius, 0}}) do
  404.             sprite_data.tint = {0, 0, 0, 0.5} -- Transparent black
  405.             sprite_data.target_offset = {offset[1] + shadow_offset[1], offset[2] + shadow_offset[2]}
  406.             table.insert(id_table, rendering.draw_sprite(sprite_data))
  407.         end
  408.         -- Proper sprite
  409.         sprite_data.tint = nil
  410.         sprite_data.target_offset = offset
  411.         table.insert(id_table, rendering.draw_sprite(sprite_data))
  412.     end
  413. end
  414.  
  415. local function get_nice_overlay_arrangement(width, height, amount)
  416.     -- Computes a nice arrangement of square sprites within a rectangle of given size
  417.     -- Returned coordinates are relative to the center of the rectangle
  418.     if amount <= 0 then return {} end
  419.     local opt_rows = 1
  420.     local opt_cols = 1
  421.     local opt_scale = 0
  422.     -- Determine the optimal number of rows to use
  423.     -- This assumes width >= height
  424.     for rows = 1, math.ceil(math.sqrt(amount)) do
  425.         local cols = math.ceil(amount/rows)
  426.         local scale = math.min(width/cols, height/rows)
  427.         if scale > opt_scale then
  428.             opt_rows = rows
  429.             opt_cols = cols
  430.             opt_scale = scale
  431.         end
  432.     end
  433.     -- Adjust scale to ensure that sprites do not become too big
  434.     opt_scale = math.pow(opt_scale, 0.8) * math.pow(1.5, 0.8 - 1)
  435.     -- Create evenly spaced coordinates
  436.     local result = {}
  437.     for i = 0, amount-1 do
  438.         local col = i % opt_cols
  439.         local row = math.floor(i / opt_cols)
  440.         local cols_in_row = (row < opt_rows - 1 and opt_cols or (amount - 1) % opt_cols + 1)
  441.         table.insert(result, {
  442.             x = (2 * col + 1 - cols_in_row) * width / (2 * opt_cols),
  443.             y = (2 * row + 1 - opt_rows) * height / (2 * opt_rows),
  444.             scale = opt_scale
  445.         })
  446.     end
  447.     return result
  448. end
  449.  
  450. function update_overlay(factory)
  451.     for _, id in pairs(factory.outside_overlay_displays) do
  452.         rendering.destroy(id)
  453.     end
  454.     factory.outside_overlay_displays = {}
  455.     if factory.built and factory.inside_overlay_controller and factory.inside_overlay_controller.valid then
  456.         local params = factory.inside_overlay_controller.get_or_create_control_behavior().parameters
  457.         local nonempty_params = {}
  458.         for _, param in pairs(params) do
  459.             if param and param.signal and param.signal.name then
  460.                 table.insert(nonempty_params, param)
  461.             end
  462.         end
  463.         local sprite_positions = get_nice_overlay_arrangement(
  464.             factory.layout.overlays.outside_w,
  465.             factory.layout.overlays.outside_h,
  466.             #nonempty_params
  467.         )
  468.         local i = 0
  469.         for _, param in pairs(nonempty_params) do
  470.             i = i + 1
  471.             draw_overlay_sprite(param.signal, factory.building,
  472.                 {
  473.                     sprite_positions[i].x + factory.layout.overlays.outside_x,
  474.                     sprite_positions[i].y + factory.layout.overlays.outside_y,
  475.                 },
  476.                 sprite_positions[i].scale,
  477.             factory.outside_overlay_displays)
  478.         end
  479.     end
  480. end
  481.  
  482. -- FACTORY GENERATION --
  483.  
  484. local function update_destructible(factory)
  485.     if factory.built and factory.building.valid then
  486.         factory.building.destructible = not settings.global["Factorissimo2-indestructible-buildings"].value
  487.     end
  488. end
  489.  
  490. local function create_factory_position()
  491.     global.next_factory_surface = global.next_factory_surface + 1
  492.     local max_surface_id = settings.global["Factorissimo2-max-surfaces"].value
  493.     if (max_surface_id > 0 and global.next_factory_surface > max_surface_id) then
  494.         global.next_factory_surface = 1
  495.     end
  496.     local surface_name = "Factory floor " .. global.next_factory_surface
  497.     local surface = game.surfaces[surface_name]
  498.     if surface == nil then
  499.         if #(game.surfaces) < 256 then
  500.             surface = game.create_surface(surface_name, {width = 2, height = 2})
  501.             surface.daytime = 0.5
  502.             surface.freeze_daytime = true
  503.             if remote.interfaces["RSO"] then -- RSO compatibility
  504.                 pcall(remote.call, "RSO", "ignoreSurface", surface_name)
  505.             end
  506.         else
  507.             global.next_factory_surface = 1
  508.             surface_name = "Factory floor 1"
  509.             surface = game.surfaces[surface_name]
  510.             if surface == nil then
  511.                 error("Unfortunately you have no available surfaces left for Factorissimo2. You cannot use Factorissimo2 on this map.")
  512.             end
  513.         end
  514.     end
  515.     local n = global.surface_factory_counters[surface_name] or 0
  516.     global.surface_factory_counters[surface_name] = n+1
  517.     local cx = 16*(n % 8)
  518.     local cy = 16*math.floor(n / 8)
  519.  
  520.     -- To make void chunks show up on the map, you need to tell them they"ve finished generating.
  521.     surface.set_chunk_generated_status({cx-2, cy-2}, defines.chunk_generated_status.entities)
  522.     surface.set_chunk_generated_status({cx-1, cy-2}, defines.chunk_generated_status.entities)
  523.     surface.set_chunk_generated_status({cx+0, cy-2}, defines.chunk_generated_status.entities)
  524.     surface.set_chunk_generated_status({cx+1, cy-2}, defines.chunk_generated_status.entities)
  525.     surface.set_chunk_generated_status({cx-2, cy-1}, defines.chunk_generated_status.entities)
  526.     surface.set_chunk_generated_status({cx-1, cy-1}, defines.chunk_generated_status.entities)
  527.     surface.set_chunk_generated_status({cx+0, cy-1}, defines.chunk_generated_status.entities)
  528.     surface.set_chunk_generated_status({cx+1, cy-1}, defines.chunk_generated_status.entities)
  529.     surface.set_chunk_generated_status({cx-2, cy+0}, defines.chunk_generated_status.entities)
  530.     surface.set_chunk_generated_status({cx-1, cy+0}, defines.chunk_generated_status.entities)
  531.     surface.set_chunk_generated_status({cx+0, cy+0}, defines.chunk_generated_status.entities)
  532.     surface.set_chunk_generated_status({cx+1, cy+0}, defines.chunk_generated_status.entities)
  533.     surface.set_chunk_generated_status({cx-2, cy+1}, defines.chunk_generated_status.entities)
  534.     surface.set_chunk_generated_status({cx-1, cy+1}, defines.chunk_generated_status.entities)
  535.     surface.set_chunk_generated_status({cx+0, cy+1}, defines.chunk_generated_status.entities)
  536.     surface.set_chunk_generated_status({cx+1, cy+1}, defines.chunk_generated_status.entities)
  537.     surface.destroy_decoratives{area={{32*(cx-2),32*(cy-2)},{32*(cx+2),32*(cy+2)}}}
  538.  
  539.     local factory = {}
  540.     factory.inside_surface = surface
  541.     factory.inside_x = 32*cx
  542.     factory.inside_y = 32*cy
  543.     factory.stored_pollution = 0
  544.     factory.upgrades = {}
  545.  
  546.     global.surface_factories[surface_name] = global.surface_factories[surface_name] or {}
  547.     global.surface_factories[surface_name][n+1] = factory
  548.     local fn = #(global.factories)+1
  549.     global.factories[fn] = factory
  550.     factory.id = fn
  551.  
  552.     return factory
  553. end
  554.  
  555. local function add_tile_rect(tiles, tile_name, xmin, ymin, xmax, ymax) -- tiles is rw
  556.     local i = #tiles
  557.     for x = xmin, xmax-1 do
  558.         for y = ymin, ymax-1 do
  559.             i = i + 1
  560.             tiles[i] = {name = tile_name, position = {x, y}}
  561.         end
  562.     end
  563. end
  564.  
  565. local function add_tile_mosaic(tiles, tile_name, xmin, ymin, xmax, ymax, pattern) -- tiles is rw
  566.     local i = #tiles
  567.     for x = 0, xmax-xmin-1 do
  568.         for y = 0, ymax-ymin-1 do
  569.             if (string.sub(pattern[y+1],x+1, x+1) == "+") then
  570.                 i = i + 1
  571.                 tiles[i] = {name = tile_name, position = {x+xmin, y+ymin}}
  572.             end
  573.         end
  574.     end
  575. end
  576.  
  577. local function create_factory_interior(layout, force)
  578.     local factory = create_factory_position()
  579.     factory.layout = layout
  580.     factory.force = force
  581.     factory.inside_door_x = layout.inside_door_x + factory.inside_x
  582.     factory.inside_door_y = layout.inside_door_y + factory.inside_y
  583.     local tiles = {}
  584.     for _, rect in pairs(layout.rectangles) do
  585.         add_tile_rect(tiles, rect.tile, rect.x1 + factory.inside_x, rect.y1 + factory.inside_y, rect.x2 + factory.inside_x, rect.y2 + factory.inside_y)
  586.     end
  587.     for _, mosaic in pairs(layout.mosaics) do
  588.         add_tile_mosaic(tiles, mosaic.tile, mosaic.x1 + factory.inside_x, mosaic.y1 + factory.inside_y, mosaic.x2 + factory.inside_x, mosaic.y2 + factory.inside_y, mosaic.pattern)
  589.     end
  590.     for _, cpos in pairs(layout.connections) do
  591.         table.insert(tiles, {name = layout.connection_tile, position = {factory.inside_x + cpos.inside_x, factory.inside_y + cpos.inside_y}})
  592.     end
  593.     factory.inside_surface.set_tiles(tiles)
  594.  
  595.     local power_pole = factory.inside_surface.create_entity{name = "factory-power-pole", position = {factory.inside_x + layout.inside_energy_x, factory.inside_y + layout.inside_energy_y}, force = force}
  596.     power_pole.destructible = false
  597.     factory.inside_power_poles = {power_pole}
  598.  
  599.     if force.technologies["factory-interior-upgrade-lights"].researched then
  600.         build_lights_upgrade(factory)
  601.     end
  602.  
  603.     factory.inside_overlay_controllers = {}
  604.  
  605.     if force.technologies["factory-interior-upgrade-display"].researched then
  606.         build_display_upgrade(factory)
  607.     end
  608.  
  609.     factory.inside_fluid_dummy_connectors = {}
  610.  
  611.     for id, cpos in pairs(layout.connections) do
  612.         local name = "factory-fluid-dummy-connector-" .. cpos.direction_in
  613.         local connector = factory.inside_surface.create_entity{name = name, position = {factory.inside_x + cpos.inside_x + cpos.indicator_dx, factory.inside_y + cpos.inside_y + cpos.indicator_dy}, force = force}
  614.         connector.destructible = false
  615.         connector.operable = false
  616.         connector.rotatable = false
  617.         factory.inside_fluid_dummy_connectors[id] = connector
  618.     end
  619.  
  620.     factory.connections = {}
  621.     factory.connection_settings = {}
  622.     factory.connection_indicators = {}
  623.  
  624.     return factory
  625. end
  626.  
  627. local function create_factory_exterior(factory, building)
  628.     local layout = factory.layout
  629.     local force = factory.force
  630.     factory.outside_x = building.position.x
  631.     factory.outside_y = building.position.y
  632.     factory.outside_door_x = factory.outside_x + layout.outside_door_x
  633.     factory.outside_door_y = factory.outside_y + layout.outside_door_y
  634.     factory.outside_surface = building.surface
  635.  
  636.     local oer = factory.outside_surface.create_entity{name = layout.outside_energy_receiver_type, position = {factory.outside_x, factory.outside_y}, force = force}
  637.     oer.destructible = false
  638.     oer.operable = false
  639.     oer.rotatable = false
  640.     factory.outside_energy_receiver = oer
  641.  
  642.     factory.outside_overlay_displays = {}
  643.  
  644.     factory.outside_fluid_dummy_connectors = {}
  645.  
  646.     for id, cpos in pairs(layout.connections) do
  647.         local name = "factory-fluid-dummy-connector-" .. cpos.direction_out
  648.         local connector = factory.outside_surface.create_entity{name = name, position = {factory.outside_x + cpos.outside_x - cpos.indicator_dx, factory.outside_y + cpos.outside_y - cpos.indicator_dy}, force = force}
  649.         connector.destructible = false
  650.         connector.operable = false
  651.         connector.rotatable = false
  652.         factory.outside_fluid_dummy_connectors[id] = connector
  653.     end
  654.  
  655.     local overlay = factory.outside_surface.create_entity{name = factory.layout.overlay_name, position = {factory.outside_x + factory.layout.overlay_x, factory.outside_y + factory.layout.overlay_y}, force = force}
  656.     overlay.destructible = false
  657.     overlay.operable = false
  658.     overlay.rotatable = false
  659.  
  660.     factory.outside_other_entities = {overlay}
  661.  
  662.     factory.outside_port_markers = {}
  663.  
  664.     set_entity_to_factory(building, factory)
  665.     factory.building = building
  666.     factory.built = true
  667.  
  668.     Connections.recheck_factory(factory, nil, nil)
  669.     update_power_connection(factory)
  670.     update_overlay(factory)
  671.     update_destructible(factory)
  672.     return factory
  673. end
  674.  
  675. local function toggle_port_markers(factory)
  676.     if not factory.built then return end
  677.     if #(factory.outside_port_markers) == 0 then
  678.         for id, cpos in pairs(factory.layout.connections) do
  679.             local sprite_data = {
  680.                 sprite = "utility/indication_arrow",
  681.                 orientation = cpos.direction_out/8,
  682.                 target = factory.building,
  683.                 surface = factory.building.surface,
  684.                 target_offset = {cpos.outside_x - 0.5 * cpos.indicator_dx, cpos.outside_y - 0.5 * cpos.indicator_dy},
  685.                 only_in_alt_mode = true,
  686.                 render_layer = "entity-info-icon",
  687.             }
  688.             table.insert(factory.outside_port_markers, rendering.draw_sprite(sprite_data))
  689.         end
  690.     else
  691.         for _, sprite in pairs(factory.outside_port_markers) do rendering.destroy(sprite) end
  692.         factory.outside_port_markers = {}
  693.     end
  694. end
  695.  
  696. local function cleanup_factory_exterior(factory, building)
  697.     factory.outside_energy_receiver.destroy()
  698.     if factory.middleman_id then delete_middleman(factory.middleman_id) end
  699.     remove_direct_connection(factory)
  700.    
  701.     Connections.disconnect_factory(factory)
  702.     for _, render_id in pairs(factory.outside_overlay_displays) do rendering.destroy(render_id) end
  703.     factory.outside_overlay_displays = {}
  704.     for _, entity in pairs(factory.outside_fluid_dummy_connectors) do entity.destroy() end
  705.     factory.outside_fluid_dummy_connectors = {}
  706.     for _, render_id in pairs(factory.outside_port_markers) do rendering.destroy(render_id) end
  707.     factory.outside_port_markers = {}
  708.     for _, entity in pairs(factory.outside_other_entities) do entity.destroy() end
  709.     factory.outside_other_entities = {}
  710.     factory.building = nil
  711.     factory.built = false
  712. end
  713.  
  714. -- FACTORY SAVING AND LOADING --
  715.  
  716. local SAVE_NAMES = {} -- Set of all valid factory save names
  717. local SAVE_ITEMS = {}
  718. for _,f in ipairs({"factory-1", "factory-2", "factory-3"}) do
  719.     SAVE_ITEMS[f] = {}
  720.     for n = 10,99 do
  721.         SAVE_NAMES[f .. "-s" .. n] = true
  722.         SAVE_ITEMS[f][n] = f .. "-s" .. n
  723.     end
  724. end
  725.  
  726. local function save_factory(factory)
  727.     for _,sf in pairs(SAVE_ITEMS[factory.layout.name] or {}) do
  728.         if global.saved_factories[sf] then
  729.         else
  730.             global.saved_factories[sf] = factory
  731.             return sf
  732.         end
  733.     end
  734.     --game.print("Could not save factory!")
  735.     return nil
  736. end
  737.  
  738. local function is_invalid_save_slot(name)
  739.     return SAVE_NAMES[name] and not global.saved_factories[name]
  740. end
  741.  
  742. local function init_factory_requester_chest(entity)
  743.     local saved_factories = global.saved_factories
  744.     local i = 0
  745.     for sf,_ in pairs(saved_factories) do
  746.         i = i+1
  747.         entity.set_request_slot({name=sf,count=1},i)
  748.     end
  749.     for j=i+1,entity.request_slot_count do
  750.         entity.clear_request_slot(j)
  751.     end
  752. end
  753.  
  754. commands.add_command("give-lost-factory-buildings", {"command-help-message.give-lost-factory-buildings"}, function(event)
  755.     --game.print(serpent.line(event))
  756.     local player = game.players[event.player_index]
  757.     if not (player and player.connected and player.admin) then return end
  758.     if event.parameter == "destroyed" then
  759.         for _,factory in pairs(global.factories) do
  760.             local saved_or_built = factory.built
  761.             for _,saved_factory in pairs(global.saved_factories) do
  762.                 if saved_factory.id == factory.id then
  763.                     saved_or_built = true
  764.                     break
  765.                 end
  766.             end
  767.             if not saved_or_built then
  768.                 save_factory(factory)
  769.             end
  770.         end
  771.     end
  772.     local main_inventory =
  773.         player.get_inventory(defines.inventory.player_main or defines.inventory.character_main)
  774.         or player.get_inventory(defines.inventory.god_main)
  775.     for save_name,_ in pairs(global.saved_factories) do
  776.         if main_inventory.get_item_count(save_name) == 0 and not (player.cursor_stack and player.cursor_stack.valid_for_read and player.cursor_stack.name == save_name) then
  777.             player.insert{name = save_name, count = 1}
  778.         end
  779.     end
  780. end)
  781. -- FACTORY PLACEMENT AND DESTRUCTION --
  782.  
  783. local function can_place_factory_here(tier, surface, position)
  784.     local factory = find_surrounding_factory(surface, position)
  785.     if not factory then return true end
  786.     local outer_tier = factory.layout.tier
  787.     if outer_tier > tier and (factory.force.technologies["factory-recursion-t1"].researched or settings.global["Factorissimo2-free-recursion"].value) then return true end
  788.     if (outer_tier >= tier or settings.global["Factorissimo2-better-recursion-2"].value)
  789.         and (factory.force.technologies["factory-recursion-t2"].researched or settings.global["Factorissimo2-free-recursion"].value) then return true end
  790.     if outer_tier > tier then
  791.         surface.create_entity{name="flying-text", position=position, text={"factory-connection-text.invalid-placement-recursion-1"}, force = factory.force}
  792.     elseif (outer_tier >= tier or settings.global["Factorissimo2-better-recursion-2"].value) then
  793.         surface.create_entity{name="flying-text", position=position, text={"factory-connection-text.invalid-placement-recursion-2"}, force = factory.force}
  794.     else
  795.         surface.create_entity{name="flying-text", position=position, text={"factory-connection-text.invalid-placement"}, force = factory.force}
  796.     end
  797.     return false
  798. end
  799.  
  800. local function recheck_nearby_connections(entity, delayed)
  801.     local surface = entity.surface
  802.     -- Find nearby factory buildings
  803.     local bbox = entity.bounding_box
  804.     -- Expand box by one tile to catch factories and also avoid illegal zero-area finds
  805.     local bbox2 = {
  806.         left_top = {x = bbox.left_top.x - 1.5, y = bbox.left_top.y - 1.5},
  807.         right_bottom = {x = bbox.right_bottom.x + 1.5, y = bbox.right_bottom.y + 1.5}
  808.     }
  809.     local building_candidates = surface.find_entities_filtered{area = bbox2, type = BUILDING_TYPE}
  810.     for _,candidate in pairs(building_candidates) do
  811.         if candidate ~= entity and HasLayout(candidate.name) then
  812.             local factory = get_factory_by_building(candidate)
  813.             if factory then
  814.                 if delayed then
  815.                     Connections.recheck_factory_delayed(factory, bbox2, nil)
  816.                 else
  817.                     Connections.recheck_factory(factory, bbox2, nil)
  818.                 end
  819.             end
  820.         end
  821.     end
  822.     local surrounding_factory = find_surrounding_factory(surface, entity.position)
  823.     if surrounding_factory then
  824.         if delayed then
  825.             Connections.recheck_factory_delayed(surrounding_factory, nil, bbox2)
  826.         else
  827.             Connections.recheck_factory(surrounding_factory, nil, bbox2)
  828.         end
  829.     end
  830. end
  831.  
  832. script.on_event({defines.events.on_built_entity, defines.events.on_robot_built_entity, defines.events.script_raised_built, defines.events.script_raised_revive}, function(event)
  833.     local entity = event.created_entity or event.entity
  834.     --if BUILDING_TYPE ~= entity.type then return nil end
  835.     if HasLayout(entity.name) then
  836.         -- This is a fresh factory, we need to create it
  837.         local layout = CreateLayout(entity.name)
  838.         if can_place_factory_here(layout.tier, entity.surface, entity.position) then
  839.             local factory = create_factory_interior(layout, entity.force)
  840.             create_factory_exterior(factory, entity)
  841.         else
  842.             entity.surface.create_entity{name=entity.name .. "-i", position=entity.position, force=entity.force}
  843.             entity.destroy()
  844.         end
  845.     elseif global.saved_factories[entity.name] then
  846.         -- This is a saved factory, we need to unpack it
  847.         local factory = global.saved_factories[entity.name]
  848.         if can_place_factory_here(factory.layout.tier, entity.surface, entity.position) then
  849.             global.saved_factories[entity.name] = nil
  850.             local newbuilding = entity.surface.create_entity{name=factory.layout.name, position=entity.position, force=factory.force}
  851.             newbuilding.last_user = entity.last_user
  852.             create_factory_exterior(factory, newbuilding)
  853.             entity.destroy()
  854.         end
  855.     elseif is_invalid_save_slot(entity.name) then
  856.         entity.surface.create_entity{name="flying-text", position=entity.position, text={"factory-connection-text.invalid-factory-data"}}
  857.         entity.destroy()
  858.     elseif Connections.is_connectable(entity) then
  859.         recheck_nearby_connections(entity)
  860.     elseif entity.type == "electric-pole" then
  861.         power_pole_placed(entity)
  862.     elseif entity.type == "solar-panel" then
  863.         if global.surface_factory_counters[entity.surface.name] then
  864.             -- FIX FOR Bio Industries // ADD check if it bio farm
  865.             if ( entity.name ~= "bi-bio-farm-hidden-panel" ) then -- ADDED
  866.                 cancel_creation(entity, event.player_index, {"factory-connection-text.invalid-placement"})
  867.             end -- ADDED
  868.             -- FIX FOR Bio Industries
  869.         else
  870.             entity.force.technologies["factory-interior-upgrade-lights"].researched = true
  871.         end
  872.     elseif entity.name == "factory-requester-chest" then
  873.         init_factory_requester_chest(entity)
  874.     end
  875. end)
  876.  
  877.  
  878. -- How players pick up factories
  879. -- Working factory buildings don"t return items, so we have to manually give the player an item
  880. script.on_event(defines.events.on_pre_player_mined_item, function(event)
  881.     local entity = event.entity
  882.     if HasLayout(entity.name) then
  883.         local factory = get_factory_by_building(entity)
  884.         if factory then
  885.             local save = save_factory(factory)
  886.             if save then
  887.                 cleanup_factory_exterior(factory, entity)
  888.                 local player = game.players[event.player_index]
  889.                 if player.insert{name = save, count = 1} < 1 then
  890.                     player.print{"inventory-restriction.player-inventory-full", {"entity-name."..save}}
  891.                     player.surface.spill_item_stack(player.position, {name = save, count = 1})
  892.                 end
  893.             else
  894.                 local newbuilding = entity.surface.create_entity{name=entity.name, position=entity.position, force=factory.force}
  895.                 newbuilding.last_user = entity.last_user
  896.                 entity.destroy()
  897.                 set_entity_to_factory(newbuilding, factory)
  898.                 factory.building = newbuilding
  899.                 game.players[event.player_index].print("Could not pick up factory, too many factories picked up at once")
  900.             end
  901.         end
  902.     elseif Connections.is_connectable(entity) then
  903.         recheck_nearby_connections(entity, true) -- Delay
  904.     elseif entity.type == 'electric-pole' then
  905.         power_pole_destroyed(entity)
  906.     end
  907. end)
  908.  
  909. -- How robots pick up factories
  910. -- Since you can"t insert items into construction robots, we"ll have to swap out factories for fake placeholder factories
  911. -- as soon as they are marked for deconstruction, and swap them back should they be unmarked.
  912. script.on_event(defines.events.on_marked_for_deconstruction, function(event)
  913.     local entity = event.entity
  914.     if HasLayout(entity.name) then
  915.         local factory = get_factory_by_building(entity)
  916.         if factory then
  917.             local save = save_factory(factory)
  918.             if save then
  919.                 -- Replace by placeholder
  920.                 cleanup_factory_exterior(factory, entity)
  921.                 local placeholder = entity.surface.create_entity{name=save, position=entity.position, force=factory.force}
  922.                 placeholder.order_deconstruction(factory.force)
  923.                 entity.destroy()
  924.             else
  925.                 -- Not saved, so put it back
  926.                 -- Don"t cancel deconstruction (it"d cause another event), instead simply replace with new building
  927.                 local newbuilding = entity.surface.create_entity{name=entity.name, position=entity.position, force=factory.force}
  928.                 entity.destroy()
  929.                 set_entity_to_factory(newbuilding, factory)
  930.                 factory.building = newbuilding
  931.                 newbuilding.surface.print("Could not pick up factory, too many factories picked up at once. Place some down before you pick up more.")
  932.             end
  933.         end
  934.     end
  935. end)
  936.  
  937. -- Factories also need to start working again once they are unmarked
  938. script.on_event(defines.events.on_cancelled_deconstruction, function(event)
  939.     local entity = event.entity
  940.     if global.saved_factories[entity.name] then
  941.         -- Rebuild factory from save
  942.         local factory = global.saved_factories[entity.name]
  943.         if can_place_factory_here(factory.layout.tier, entity.surface, entity.position) then
  944.             global.saved_factories[entity.name] = nil
  945.             local newbuilding = entity.surface.create_entity{name=factory.layout.name, position=entity.position, force=factory.force}
  946.             create_factory_exterior(factory, newbuilding)
  947.             entity.destroy()
  948.         end
  949.     end
  950. end)
  951.  
  952. -- We need to check when a robot mines a piece of a connection
  953. script.on_event(defines.events.on_robot_pre_mined, function(event)
  954.     local entity = event.entity
  955.     if Connections.is_connectable(entity) then
  956.         recheck_nearby_connections(entity, true) -- Delay
  957.     elseif entity.type == 'electric-pole' then
  958.         power_pole_destroyed(entity)
  959.     end
  960. end)
  961.  
  962. -- How biters pick up factories
  963. -- Too bad they don"t have hands
  964. script.on_event({defines.events.on_entity_died, defines.events.script_raised_destroy}, function(event)
  965.     local entity = event.entity
  966.     if HasLayout(entity.name) then
  967.         local factory = get_factory_by_building(entity)
  968.         if factory then
  969.             cleanup_factory_exterior(factory, entity)
  970.             -- Don"t save it. It will be inaccessible from now on.
  971.             --save_factory(factory)
  972.         end
  973.     elseif Connections.is_connectable(entity) then
  974.         recheck_nearby_connections(entity, true) -- Delay
  975.     elseif entity.type == 'electric-pole' then
  976.         power_pole_destroyed(entity)
  977.     end
  978. end)
  979.  
  980. -- How to clone your factory
  981. -- This implementation will not actually clone factory buildings, but move them to where they were cloned.
  982. local clone_forbidden_prefixes = {
  983.     "factory-1-",
  984.     "factory-2-",
  985.     "factory-3-",
  986.     "factory-power-input-",
  987.     "factory-connection-indicator-",
  988.     "factory-power-pole",
  989.     "factory-ceiling-light",
  990.     "factory-overlay-controller",
  991.     "factory-overlay-display",
  992.     "factory-port-marker",
  993.     "factory-fluid-dummy-connector"
  994. }
  995.  
  996. local function is_entity_clone_forbidden(name)
  997.     for _, prefix in pairs(clone_forbidden_prefixes) do
  998.         if name:sub(1, #prefix) == prefix then
  999.             return true
  1000.         end
  1001.     end
  1002.     return false
  1003. end
  1004.  
  1005. script.on_event(defines.events.on_entity_cloned, function(event)
  1006.     local src_entity = event.source
  1007.     local dst_entity = event.destination
  1008.     if is_entity_clone_forbidden(dst_entity.name) then
  1009.         dst_entity.destroy()
  1010.     elseif HasLayout(src_entity.name) then
  1011.         local factory = get_factory_by_building(src_entity)
  1012.         cleanup_factory_exterior(factory, src_entity)
  1013.         if src_entity.valid then src_entity.destroy() end
  1014.         create_factory_exterior(factory, dst_entity)
  1015.     end
  1016. end)
  1017.  
  1018. -- GUI --
  1019.  
  1020. local function get_camera_toggle_button(player)
  1021.     local buttonflow = mod_gui.get_button_flow(player)
  1022.     local button = buttonflow.factory_camera_toggle_button or buttonflow.add{type="sprite-button", name="factory_camera_toggle_button", sprite="technology/factory-architecture-t1"}
  1023.     button.visible = player.force.technologies["factory-preview"].researched
  1024.     return button
  1025. end
  1026.  
  1027. local function get_camera_frame(player)
  1028.     local frameflow = mod_gui.get_frame_flow(player)
  1029.     local camera_frame = frameflow.factory_camera_frame
  1030.     if not camera_frame then
  1031.         camera_frame = frameflow.add{type = "frame", name = "factory_camera_frame", style = "captionless_frame"}
  1032.         camera_frame.visible = false
  1033.     end
  1034.     return camera_frame
  1035. end
  1036.  
  1037. -- prepare_gui was declared waaay above
  1038. prepare_gui = function(player)
  1039.     get_camera_toggle_button(player)
  1040.     get_camera_frame(player)
  1041. end
  1042.  
  1043. local function set_camera(player, factory, inside)
  1044.     if not player.force.technologies["factory-preview"].researched then return end
  1045.  
  1046.     local ps = settings.get_player_settings(player)
  1047.     local ps_preview_size = ps["Factorissimo2-preview-size"]
  1048.     local preview_size = ps_preview_size and ps_preview_size.value or 300
  1049.     local ps_preview_zoom = ps["Factorissimo2-preview-zoom"]
  1050.     local preview_zoom = ps_preview_zoom and ps_preview_zoom.value or 1
  1051.     local position, surface_index, zoom
  1052.     if not inside then
  1053.         position = {x = factory.outside_x, y = factory.outside_y}
  1054.         surface_index = factory.outside_surface.index
  1055.         zoom = (preview_size/(32/preview_zoom))/(8+factory.layout.outside_size)
  1056.     else
  1057.         position = {x = factory.inside_x, y = factory.inside_y}
  1058.         surface_index = factory.inside_surface.index
  1059.         zoom = (preview_size/(32/preview_zoom))/(5+factory.layout.inside_size)
  1060.     end
  1061.     local camera_frame = get_camera_frame(player)
  1062.     local camera = camera_frame.factory_camera
  1063.     if camera then
  1064.         camera.position = position
  1065.         camera.surface_index = surface_index
  1066.         camera.zoom = zoom
  1067.         camera.style.minimal_width = preview_size
  1068.         camera.style.minimal_height = preview_size
  1069.     else
  1070.         local camera = camera_frame.add{type = "camera", name = "factory_camera", position = position, surface_index = surface_index, zoom = zoom}
  1071.         camera.style.minimal_width = preview_size
  1072.         camera.style.minimal_height = preview_size
  1073.     end
  1074.     camera_frame.visible = true
  1075. end
  1076.  
  1077. local function unset_camera(player)
  1078.     get_camera_frame(player).visible = false
  1079. end
  1080.  
  1081. local function update_camera(player)
  1082.     if not global.player_preview_active[player.index] then return end
  1083.     if not player.force.technologies["factory-preview"].researched then return end
  1084.     local cursor_stack = player.cursor_stack
  1085.     if cursor_stack and cursor_stack.valid_for_read and global.saved_factories[cursor_stack.name] then
  1086.         set_camera(player, global.saved_factories[cursor_stack.name], true)
  1087.         return
  1088.     end
  1089.     local selected = player.selected
  1090.     if selected then
  1091.         local factory = get_factory_by_entity(player.selected)
  1092.         if factory then
  1093.             set_camera(player, factory, true)
  1094.             return
  1095.         elseif selected.name == "factory-power-pole" then
  1096.             local factory = find_surrounding_factory(player.surface, player.position)
  1097.             if factory then
  1098.                 set_camera(player, factory, false)
  1099.                 return
  1100.             end
  1101.         end
  1102.     end
  1103.     unset_camera(player)
  1104. end
  1105.  
  1106. script.on_event(defines.events.on_selected_entity_changed, function(event)
  1107.     update_camera(game.players[event.player_index])
  1108. end)
  1109.  
  1110. script.on_event(defines.events.on_player_cursor_stack_changed, function(event)
  1111.     update_camera(game.players[event.player_index])
  1112. end)
  1113.  
  1114. script.on_event(defines.events.on_player_created, function(event)
  1115.     prepare_gui(game.players[event.player_index])
  1116. end)
  1117.  
  1118. script.on_event(defines.events.on_gui_click, function(event)
  1119.     local player = game.players[event.player_index]
  1120.     if event.element.valid and event.element.name == "factory_camera_toggle_button" then
  1121.         if global.player_preview_active[player.index] then
  1122.             get_camera_toggle_button(player).sprite = "technology/factory-architecture-t1"
  1123.             global.player_preview_active[player.index] = false
  1124.         else
  1125.             get_camera_toggle_button(player).sprite = "technology/factory-preview"
  1126.             global.player_preview_active[player.index] = true
  1127.         end
  1128.     end
  1129. end)
  1130.  
  1131. -- TRAVEL --
  1132.  
  1133. local function teleport_player_safely(player, surface, position)
  1134.     if player and player.character then
  1135.         position = surface.find_non_colliding_position(
  1136.             player.character.name, position, 5, 0.5, false
  1137.         ) or position
  1138.     end
  1139.     player.teleport(position, surface)
  1140.     global.last_player_teleport[player.index] = game.tick
  1141.     update_camera(player)
  1142. end
  1143.  
  1144. local function enter_factory(player, factory)
  1145.     teleport_player_safely(
  1146.         player, factory.inside_surface,
  1147.         {factory.inside_door_x, factory.inside_door_y}
  1148.     )
  1149. end
  1150.  
  1151. local function leave_factory(player, factory)
  1152.     teleport_player_safely(
  1153.         player, factory.outside_surface,
  1154.         {factory.outside_door_x, factory.outside_door_y}
  1155.     )
  1156.     update_camera(player)
  1157.     update_overlay(factory)
  1158. end
  1159.  
  1160. local function player_may_enter_factory(player, factory)
  1161.     return player.force.name == factory.force.name
  1162.             or (player.force.get_friend(factory.force) and settings.global["Factorissimo2-allied-players-may-enter"].value)
  1163.             or settings.global["Factorissimo2-enemy-players-may-enter"].value
  1164. end
  1165.  
  1166. local function teleport_players()
  1167.     local tick = game.tick
  1168.     for player_index, player in pairs(game.players) do
  1169.         if player.connected and not player.driving and tick - (global.last_player_teleport[player_index] or 0) >= 45 then
  1170.             local walking_state = player.walking_state
  1171.             if walking_state.walking then
  1172.                 if walking_state.direction == defines.direction.north
  1173.                 or walking_state.direction == defines.direction.northeast
  1174.                 or walking_state.direction == defines.direction.northwest then
  1175.                     -- Enter factory
  1176.                     local factory = find_factory_by_building(player.surface, {{player.position.x-0.2, player.position.y-0.3},{player.position.x+0.2, player.position.y}})
  1177.                     if factory ~= nil then
  1178.                         if math.abs(player.position.x-factory.outside_x)<0.6 then
  1179.                             if player_may_enter_factory(player, factory) then
  1180.                                 enter_factory(player, factory)
  1181.                             end
  1182.                         end
  1183.                     end
  1184.                 elseif walking_state.direction == defines.direction.south
  1185.                 or walking_state.direction == defines.direction.southeast
  1186.                 or walking_state.direction == defines.direction.southwest then
  1187.                     local factory = find_surrounding_factory(player.surface, player.position)
  1188.                     if factory ~= nil then
  1189.                         if player.position.y > factory.inside_door_y+1 then
  1190.                             leave_factory(player, factory)
  1191.                         end
  1192.                     end
  1193.                 end
  1194.             end
  1195.         end
  1196.     end
  1197. end
  1198.  
  1199. -- POLLUTION MANAGEMENT --
  1200.  
  1201. local function update_pollution(factory)
  1202.     local inside_surface = factory.inside_surface
  1203.     local pollution, cp = 0, 0
  1204.     local inside_x, inside_y = factory.inside_x, factory.inside_y
  1205.  
  1206.     cp = inside_surface.get_pollution({inside_x-16,inside_y-16})
  1207.     inside_surface.pollute({inside_x-16,inside_y-16},-cp)
  1208.     pollution = pollution + cp
  1209.     cp = inside_surface.get_pollution({inside_x+16,inside_y-16})
  1210.     inside_surface.pollute({inside_x+16,inside_y-16},-cp)
  1211.     pollution = pollution + cp
  1212.     cp = inside_surface.get_pollution({inside_x-16,inside_y+16})
  1213.     inside_surface.pollute({inside_x-16,inside_y+16},-cp)
  1214.     pollution = pollution + cp
  1215.     cp = inside_surface.get_pollution({inside_x+16,inside_y+16})
  1216.     inside_surface.pollute({inside_x+16,inside_y+16},-cp)
  1217.     pollution = pollution + cp
  1218.     if factory.built then
  1219.         factory.outside_surface.pollute({factory.outside_x, factory.outside_y}, pollution + factory.stored_pollution)
  1220.         factory.stored_pollution = 0
  1221.     else
  1222.         factory.stored_pollution = factory.stored_pollution + pollution
  1223.     end
  1224. end
  1225.  
  1226. -- ON TICK --
  1227.  
  1228. script.on_event(defines.events.on_tick, function(event)
  1229.     local factories = global.factories
  1230.  
  1231.     -- Transfer pollution
  1232.     local fn = #factories
  1233.     local offset = (23*event.tick)%60+1
  1234.     while offset <= fn do
  1235.         local factory = factories[offset]
  1236.         if factory ~= nil then update_pollution(factory) end
  1237.         offset = offset + 60
  1238.     end
  1239.  
  1240.     -- Update connections
  1241.     Connections.update() -- Duh
  1242.  
  1243.     -- Teleport players
  1244.     teleport_players() -- What did you expect
  1245. end)
  1246.  
  1247. -- CONNECTION SETTINGS --
  1248.  
  1249. local CONNECTION_INDICATOR_NAMES = {}
  1250. for _,name in pairs(Connections.indicator_names) do
  1251.     CONNECTION_INDICATOR_NAMES["factory-connection-indicator-" .. name] = true
  1252. end
  1253.  
  1254. script.on_event(defines.events.on_player_rotated_entity, function(event)
  1255.     local entity = event.entity
  1256.     if CONNECTION_INDICATOR_NAMES[entity.name] then
  1257.         -- Skip
  1258.     elseif Connections.is_connectable(entity) then
  1259.         recheck_nearby_connections(entity)
  1260.         if entity.type == "underground-belt" then
  1261.             local neighbour = entity.neighbours
  1262.             if neighbour then
  1263.                 recheck_nearby_connections(neighbour)
  1264.             end
  1265.         end
  1266.     end
  1267. end)
  1268.  
  1269. script.on_event("factory-rotate", function(event)
  1270.     local entity = game.players[event.player_index].selected
  1271.     if not entity then return end
  1272.     if HasLayout(entity.name) then
  1273.         local factory = get_factory_by_building(entity)
  1274.         if factory then
  1275.             toggle_port_markers(factory)
  1276.         end
  1277.     elseif CONNECTION_INDICATOR_NAMES[entity.name] then
  1278.         local factory = find_surrounding_factory(entity.surface, entity.position)
  1279.         if factory then
  1280.             Connections.rotate(factory, entity)
  1281.         end
  1282.     elseif entity.name == "factory-requester-chest" then
  1283.         init_factory_requester_chest(entity)
  1284.     end
  1285. end)
  1286.  
  1287. script.on_event("factory-increase", function(event)
  1288.     local entity = game.players[event.player_index].selected
  1289.     if not entity then return end
  1290.     if CONNECTION_INDICATOR_NAMES[entity.name] then
  1291.         local factory = find_surrounding_factory(entity.surface, entity.position)
  1292.         if factory then
  1293.             Connections.adjust(factory, entity, true)
  1294.         end
  1295.     end
  1296. end)
  1297.  
  1298. script.on_event("factory-decrease", function(event)
  1299.     local entity = game.players[event.player_index].selected
  1300.     if not entity then return end
  1301.     if CONNECTION_INDICATOR_NAMES[entity.name] then
  1302.         local factory = find_surrounding_factory(entity.surface, entity.position)
  1303.         if factory then
  1304.             Connections.adjust(factory, entity, false)
  1305.         end
  1306.     end
  1307. end)
  1308.  
  1309. -- MISC --
  1310.  
  1311. function cancel_creation(entity, player_index, message)
  1312.     local inserted = 0
  1313.     local item_to_place = entity.prototype.items_to_place_this[1]
  1314.     local surface = entity.surface
  1315.     local position = entity.position
  1316.     local force = entity.force
  1317.    
  1318.     if player_index then
  1319.         local player = game.get_player(player_index)
  1320.         if player.mine_entity(entity, false) then
  1321.             inserted = 1
  1322.         elseif item_to_place then
  1323.             inserted = player.insert(item_to_place)
  1324.         end
  1325.     end
  1326.    
  1327.     entity.destroy{raise_destroy = true}
  1328.    
  1329.     if inserted == 0 and item_to_place then
  1330.         surface.spill_item_stack{
  1331.             enable_looted  = true,
  1332.             force = force,
  1333.             allow_belts = false,
  1334.             position = position,
  1335.             items = item_to_place
  1336.         }
  1337.     end
  1338.    
  1339.     if message then
  1340.         surface.create_entity{
  1341.             name = "flying-text",
  1342.             position = position,
  1343.             text = message,
  1344.             render_player_index = player_index
  1345.         }
  1346.     end
  1347. end
  1348.  
  1349. update_hidden_techs = function(force)
  1350.     if settings.global["Factorissimo2-hide-recursion"] and settings.global["Factorissimo2-hide-recursion"].value then
  1351.         force.technologies["factory-recursion-t1"].enabled = false
  1352.         force.technologies["factory-recursion-t2"].enabled = false
  1353.     elseif settings.global["Factorissimo2-hide-recursion-2"] and settings.global["Factorissimo2-hide-recursion-2"].value then
  1354.         force.technologies["factory-recursion-t1"].enabled = true
  1355.         force.technologies["factory-recursion-t2"].enabled = false
  1356.     else
  1357.         force.technologies["factory-recursion-t1"].enabled = true
  1358.         force.technologies["factory-recursion-t2"].enabled = true
  1359.     end
  1360. end
  1361.  
  1362. script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
  1363.     local setting = event.setting
  1364.     if setting == "Factorissimo2-hide-recursion" or setting == "Factorissimo2-hide-recursion-2" then
  1365.         for _, force in pairs(game.forces) do
  1366.             update_hidden_techs(force)
  1367.         end
  1368.     elseif setting == "Factorissimo2-indestructible-buildings" then
  1369.         for _, factory in pairs(global.factories) do
  1370.             update_destructible(factory)
  1371.         end
  1372.     end
  1373. end)
  1374.  
  1375. script.on_event(defines.events.on_force_created, function(event)
  1376.     local force = event.force
  1377.     update_hidden_techs(force)
  1378. end)
  1379.  
  1380. script.on_event(defines.events.on_forces_merging, function(event)
  1381.     for _, factory in pairs(global.factories) do
  1382.         if not factory.force.valid then
  1383.             factory.force = game.forces["player"]
  1384.         end
  1385.         if factory.force.name == event.source.name then
  1386.             factory.force = event.destination
  1387.         end
  1388.     end
  1389. end)
  1390.  
  1391. script.on_event(defines.events.on_research_finished, function(event)
  1392.     if not global.factories then return end -- In case any mod or scenario script calls LuaForce.research_all_technologies() during its on_init
  1393.     local research = event.research
  1394.     local name = research.name
  1395.     if name == "factory-connection-type-fluid" or name == "factory-connection-type-chest" or name == "factory-connection-type-circuit" then
  1396.         for _, factory in pairs(global.factories) do
  1397.             if factory.built then Connections.recheck_factory(factory, nil, nil) end
  1398.         end
  1399.     --elseif name == "factory-interior-upgrade-power" then
  1400.     --  for _, factory in pairs(global.factories) do build_power_upgrade(factory) end
  1401.     elseif name == "factory-interior-upgrade-lights" then
  1402.         for _, factory in pairs(global.factories) do build_lights_upgrade(factory) end
  1403.     elseif name == "factory-interior-upgrade-display" then
  1404.         for _, factory in pairs(global.factories) do build_display_upgrade(factory) end
  1405.     elseif name == "factory-interior-upgrade-roboport" then
  1406.         for _, factory in pairs(global.factories) do build_roboport_upgrade(factory) end
  1407.     -- elseif name == "factory-recursion-t1" or name == "factory-recursion-t2" then
  1408.         -- Nothing happens, because implementing stuff here would be horrible.
  1409.         -- You just gotta pick up and replace your invalid factories manually for them to work with the newly researched recursion.
  1410.     elseif name == "factory-preview" then
  1411.         for _, player in pairs(game.players) do get_camera_toggle_button(player) end
  1412.     end
  1413. end)
  1414.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement