-- Soft mod version of Blueprint Flipper and Turner https://mods.factorio.com/mods/Marthen/Blueprint_Flip_Turn local Event = require 'utils.event' local Token = require 'utils.global_token' local Gui = require 'utils.gui' local function getBlueprintCursorStack(player) local cursor = player.cursor_stack if cursor.valid_for_read and (cursor.name == 'blueprint' or cursor.name == 'blueprint-book') and cursor.is_blueprint_setup() then --check if is a blueprint, work in book as well return cursor end return nil end local function flip_v(cursor) local ents = cursor.get_blueprint_entities() if ents then for i = 1, #ents do local dir = ents[i].direction or 0 if ents[i].name == 'curved-rail' then ents[i].direction = (13 - dir) % 8 elseif ents[i].name == 'storage-tank' then if ents[i].direction == 2 or ents[i].direction == 6 then ents[i].direction = 4 else ents[i].direction = 2 end elseif ents[i].name == 'rail-signal' or ents[i].name == 'rail-chain-signal' then if dir == 1 then ents[i].direction = 7 elseif dir == 2 then ents[i].direction = 6 elseif dir == 3 then ents[i].direction = 5 elseif dir == 5 then ents[i].direction = 3 elseif dir == 6 then ents[i].direction = 2 elseif dir == 7 then ents[i].direction = 1 end elseif ents[i].name == 'train-stop' then if dir == 2 then ents[i].direction = 6 elseif dir == 6 then ents[i].direction = 2 end else ents[i].direction = (12 - dir) % 8 end ents[i].position.y = -ents[i].position.y if ents[i].drop_position then ents[i].drop_position.y = -ents[i].drop_position.y end if ents[i].pickup_position then ents[i].pickup_position.y = -ents[i].pickup_position.y end end cursor.set_blueprint_entities(ents) end if cursor.get_blueprint_tiles() ~= nil then local ents = cursor.get_blueprint_tiles() for i = 1, #ents do local dir = ents[i].direction or 0 ents[i].direction = (12 - dir) % 8 ents[i].position.y = -ents[i].position.y end cursor.set_blueprint_tiles(ents) end end local function flip_h(cursor) local ents = cursor.get_blueprint_entities() if ents then for i = 1, #ents do local dir = ents[i].direction or 0 if ents[i].name == 'curved-rail' then ents[i].direction = (9 - dir) % 8 elseif ents[i].name == 'storage-tank' then if ents[i].direction == 2 or ents[i].direction == 6 then ents[i].direction = 4 else ents[i].direction = 2 end elseif ents[i].name == 'rail-signal' or ents[i].name == 'rail-chain-signal' then if dir == 0 then ents[i].direction = 4 elseif dir == 1 then ents[i].direction = 3 elseif dir == 3 then ents[i].direction = 1 elseif dir == 4 then ents[i].direction = 0 elseif dir == 5 then ents[i].direction = 7 elseif dir == 7 then ents[i].direction = 5 end elseif ents[i].name == 'train-stop' then if dir == 0 then ents[i].direction = 4 elseif dir == 4 then ents[i].direction = 0 end else ents[i].direction = (16 - dir) % 8 end ents[i].position.x = -ents[i].position.x if ents[i].drop_position then ents[i].drop_position.x = -ents[i].drop_position.x end if ents[i].pickup_position then ents[i].pickup_position.x = -ents[i].pickup_position.x end end cursor.set_blueprint_entities(ents) end if cursor.get_blueprint_tiles() ~= nil then local ents = cursor.get_blueprint_tiles() for i = 1, #ents do local dir = ents[i].direction or 0 ents[i].direction = (16 - dir) % 8 ents[i].position.x = -ents[i].position.x end cursor.set_blueprint_tiles(ents) end end local function build_filters(data) local filters = {} for _, filter in pairs(data) do local from = filter.from.tooltip local to = filter.to.tooltip if from ~= '' and to ~= '' then filters[from] = to end end return filters end local function convert(cursor, filters) local entities = cursor.get_blueprint_entities() if not entities then return end for _, e in ipairs(entities) do local to_name = filters[e.name] if to_name then e.name = to_name end end cursor.set_blueprint_entities(entities) end local valid_filters = { 'wooden-chest', 'iron-chest', 'steel-chest', 'storage-tank', 'transport-belt', 'fast-transport-belt', 'express-transport-belt', 'underground-belt', 'fast-underground-belt', 'express-underground-belt', 'splitter', 'fast-splitter', 'express-splitter', 'loader', 'fast-loader', 'express-loader', 'burner-inserter', 'inserter', 'long-handed-inserter', 'fast-inserter', 'filter-inserter', 'stack-inserter', 'stack-filter-inserter', 'small-electric-pole', 'medium-electric-pole', 'big-electric-pole', 'substation', 'pipe', 'pipe-to-ground', 'pump', 'curved-rail', 'straight-rail', 'train-stop', 'rail-signal', 'rail-chain-signal', 'logistic-chest-active-provider', 'logistic-chest-passive-provider', 'logistic-chest-storage', 'logistic-chest-buffer', 'logistic-chest-requester', 'roboport', 'small-lamp', 'arithmetic-combinator', 'decider-combinator', 'constant-combinator', 'power-switch', 'programmable-speaker', 'boiler', 'steam-engine', 'steam-turbine', 'solar-panel', 'accumulator', 'nuclear-reactor', 'heat-exchanger', 'heat-pipe', 'burner-mining-drill', 'electric-mining-drill', 'offshore-pump', 'pumpjack', 'stone-furnace', 'steel-furnace', 'electric-furnace', 'assembling-machine-1', 'assembling-machine-2', 'assembling-machine-3', 'oil-refinery', 'chemical-plant', 'centrifuge', 'lab', 'beacon', 'stone-wall', 'gate', 'gun-turret', 'laser-turret', 'flamethrower-turret', 'artillery-turret', 'radar', 'rocket-silo' } -- Gui implementation. local player_filters = {} local player_filters_token = Token.register_global(player_filters) Event.on_load( function() player_filters = Token.get_global(player_filters_token) end ) local main_button_name = Gui.uid_name() local main_frame_name = Gui.uid_name() local flip_h_button_name = Gui.uid_name() local flip_v_button_name = Gui.uid_name() local convert_button_name = Gui.uid_name() local filter_button_name = Gui.uid_name() local filter_element_name = Gui.uid_name() local filters_table_name = Gui.uid_name() local filter_table_close_button_name = Gui.uid_name() local filter_table_clear_name = Gui.uid_name() local clear_all_filters_name = Gui.uid_name() local function player_joined(event) local player = game.players[event.player_index] if not player or not player.valid then return end if player.gui.top[main_button_name] ~= nil then return end player.gui.top.add {name = main_button_name, type = 'sprite-button', sprite = 'item/blueprint'} end local function draw_filters_table(event) local center = event.player.gui.center if center[filters_table_name] then return end local frame = center.add {type = 'frame', name = filters_table_name, direction = 'vertical', caption = 'Set Filter'} local t = frame.add {type = 'table', column_count = 10} t.style.horizontal_spacing = 0 t.style.vertical_spacing = 0 for _, v in ipairs(valid_filters) do local flow = t.add {type = 'flow'} local b = flow.add {type = 'sprite-button', name = filter_element_name, sprite = 'entity/' .. v, tooltip = v} Gui.set_data(b, frame) b.style = 'slot_button' end local flow = frame.add {type = 'flow'} local close = flow.add {type = 'button', name = filter_table_close_button_name, caption = 'Close'} Gui.set_data(close, frame) local clear = flow.add {type = 'button', name = filter_table_clear_name, caption = 'Clear Filter'} Gui.set_data(clear, frame) event.player.opened = frame Gui.set_data(frame, event.element) end local function toggle(event) local p_filters = player_filters[event.player_index] if not p_filters then p_filters = {} for i = 1, 9 do p_filters[i] = {from = '', to = ''} end player_filters[event.player_index] = p_filters end local left = event.player.gui.left local main_frame = left[main_frame_name] if main_frame and main_frame.valid then local filters = Gui.get_data(main_frame) for i, f in pairs(filters) do p_filters[i].from = f.from.tooltip p_filters[i].to = f.to.tooltip end Gui.remove_data_recursivly(main_frame) main_frame.destroy() else main_frame = left.add { type = 'frame', name = main_frame_name, direction = 'vertical', caption = 'Blueprint Helper' } local scroll_pane = main_frame.add {type = 'scroll-pane', direction = 'vertical', vertical_scroll_policy = 'auto'} scroll_pane.style.maximal_height = 500 -- Flipper. local flipper_frame = scroll_pane.add {type = 'frame', caption = 'Flipper', direction = 'vertical'} local label = flipper_frame.add { type = 'label', caption = 'Place blueprint on buttons below to flip blueprint.\n' .. 'Obviously this wont work correctly with refineries or chemical plants.' } label.style.single_line = false local flow = flipper_frame.add {type = 'flow'} flow.add { type = 'button', name = flip_h_button_name, caption = 'Flip Horizontal ⇄' } flow.add { type = 'button', name = flip_v_button_name, caption = 'Flip Vertical ⇵' } -- Converter. local filter_frame = scroll_pane.add {type = 'frame', caption = 'Entity Converter', direction = 'vertical'} filter_frame.add { type = 'label', -- The empty space is a hacky way to line this frame up with the above frame. caption = 'Set filters then place blueprint on convert button to apply filters. ' } local filter_table = filter_frame.add {type = 'table', column_count = 12} local filters = {} for i = 1, 9 do local filler = filter_table.add {type = 'label'} filler.style.minimal_width = 16 local from_tooltip = p_filters[i].from local to_tooltip = p_filters[i].to local from_filter = filter_table.add({type = 'flow'}).add { type = 'sprite-button', name = filter_button_name, tooltip = from_tooltip, sprite = from_tooltip ~= '' and 'entity/' .. from_tooltip or nil } from_filter.style = 'slot_button' filter_table.add {type = 'label', caption = '→'} local to_filter = filter_table.add({type = 'flow'}).add { type = 'sprite-button', name = filter_button_name, tooltip = to_tooltip, sprite = to_tooltip ~= '' and 'entity/' .. to_tooltip or nil } to_filter.style = 'slot_button' table.insert(filters, {from = from_filter, to = to_filter}) end local converter_buttons_flow = filter_frame.add {type = 'flow'} local clear_button = converter_buttons_flow.add {type = 'button', name = clear_all_filters_name, caption = 'Clear Filters'} Gui.set_data(clear_button, filters) local filter_button = converter_buttons_flow.add {type = 'button', name = convert_button_name, caption = 'Convert'} Gui.set_data(filter_button, filters) main_frame.add {type = 'button', name = main_button_name, caption = 'Close'} Gui.set_data(main_frame, filters) end end Gui.on_click(main_button_name, toggle) Gui.on_click( flip_h_button_name, function(event) local player = event.player local cursor = getBlueprintCursorStack(player) if cursor then flip_h(cursor) else player.print('Click the button with a blueprint or blueprint book.') end end ) Gui.on_click( flip_v_button_name, function(event) local player = event.player local cursor = getBlueprintCursorStack(player) if cursor then flip_v(cursor) else player.print('Click the button with a blueprint or blueprint book.') end end ) Gui.on_click( filter_button_name, function(event) if event.button == defines.mouse_button_type.right then local element = event.element element.sprite = 'utility/pump_cannot_connect_icon' element.tooltip = '' else draw_filters_table(event) end end ) Gui.on_click( filter_element_name, function(event) local element = event.element local frame = Gui.get_data(element) local filter_button = Gui.get_data(frame) filter_button.sprite = element.sprite filter_button.tooltip = element.tooltip Gui.remove_data_recursivly(frame) frame.destroy() end ) Gui.on_click( filter_table_close_button_name, function(event) local frame = Gui.get_data(event.element) Gui.remove_data_recursivly(frame) frame.destroy() end ) Gui.on_click( filter_table_clear_name, function(event) local frame = Gui.get_data(event.element) local filter_button = Gui.get_data(frame) filter_button.sprite = 'utility/pump_cannot_connect_icon' filter_button.tooltip = '' Gui.remove_data_recursivly(frame) frame.destroy() end ) Gui.on_click( clear_all_filters_name, function(event) local filters = Gui.get_data(event.element) for _, filter in ipairs(filters) do local from = filter.from local to = filter.to from.sprite = 'utility/pump_cannot_connect_icon' from.tooltip = '' to.sprite = 'utility/pump_cannot_connect_icon' to.tooltip = '' end end ) Gui.on_click( convert_button_name, function(event) local player = event.player local cursor = getBlueprintCursorStack(player) if not cursor then player.print('Click the button with a blueprint or blueprint book.') end local data = Gui.get_data(event.element) local filters = build_filters(data) if next(filters) == nil then player.print('No filters have been set') end convert(cursor, filters) end ) Gui.on_custom_close( filters_table_name, function(event) local element = event.element Gui.remove_data_recursivly(element) element.destroy() end ) Event.add(defines.events.on_player_joined_game, player_joined)