------------------------------------------------------------------------------------------------------------------------------------------------------ -- DEADLOCK'S INDUSTRIAL DISPLAYS -- Forked from Industrial Revolution, for your signage pleasure ------------------------------------------------------------------------------------------------------------------------------------------------------ -- constants local DID = require("globals") ------------------------------------------------------------------------------------------------------------------------------------------------------ -- functions local function get_global_player_info(player_index,info) if global[info] == nil then global[info] = {} end return global[info][player_index] end local function set_global_player_info(player_index,info,value) if global[info] == nil then global[info] = {} end global[info][player_index] = value end local function splitstring(s, d) result = {}; for m in (s..d):gmatch("(.-)"..d) do table.insert(result, m); end return result; end local function get_map_markers(entity) return entity.force.find_chart_tags(entity.surface, entity.bounding_box) end local function add_map_marker(entity, icon_type, icon_name) if icon_type and icon_name then entity.force.add_chart_tag(entity.surface, { icon = { type = icon_type, name = icon_name}, position = entity.position }) entity.surface.play_sound{path = "map-marker-ping", position = entity.position, volume_modifier = 1} end end local function change_map_markers(entity, icon_type, icon_name) local markers = get_map_markers(entity) if markers then for _,marker in pairs(markers) do marker.icon = { type = icon_type, name = icon_name} end end end local function get_has_map_marker(entity) return next(get_map_markers(entity)) ~= nil end local function remove_markers(entity) if entity and entity.valid then for _,marker in pairs(get_map_markers(entity)) do marker.destroy() end end end local function find_entity_render(entity) for _,id in pairs(rendering.get_all_ids(DID.mod_name)) do if rendering.get_target(id).entity == entity then return id end end return nil end local function get_render_sprite_info(entity) local id = find_entity_render(entity) if id then local strings = splitstring(rendering.get_sprite(id), "/") if #strings == 2 then return strings[1], strings[2] end end return nil, nil end local function gui_close(event) local player = game.players[event.player_index] local frame = player.gui.screen[DID.custom_gui] if frame then set_global_player_info(event.player_index,"display_gui_location",player.gui.screen[DID.custom_gui].location) return frame.destroy() end return false end local function render_overlay_sprite(entity,sprite) if game.is_valid_sprite_path(sprite) then local size = (string.find(entity.name,"small") and 0.65) or (string.find(entity.name,"medium") and 1.5) or 2.5 rendering.draw_sprite{ sprite = sprite, x_scale = size, y_scale = size, render_layer = "lower-object", target = entity, surface = entity.surface, } end end local function render_overlay(entity,spritetype,spritename) render_overlay_sprite(entity, spritetype.."/"..spritename) end local function destroy_render(entity) local last_id = find_entity_render(entity) if last_id then rendering.destroy(last_id) end end local function get_all_children_with_style(root,style,children) if not root then return nil end if children == nil then children = {} end for _,child in pairs(root.children) do if child.style.name == style then table.insert(children,child) end children = get_all_children_with_style(child,style,children) end return children end local function get_all_children_with_name(root,name,children) if children == nil then children = {} end if not root or not root.children then return children end for _,child in pairs(root.children) do if child.name and string.find(child.name, name, 1, true) then table.insert(children,child) end children = get_all_children_with_name(child,name,children) end return children end local function display_filter_tabs(player,filter) local textfield = player.gui.screen[DID.custom_gui]["display-header"]["display-search-textfield"] local tabs = player.gui.screen[DID.custom_gui]["inner-frame"]["display-tabs"] local selected = tabs.selected_tab_index for index,tab in pairs(tabs.tabs) do tabs.selected_tab_index = index -- shenanigans local count = 0 for _,child in pairs(get_all_children_with_name(tab.content,"display-symbol")) do if child.sprite and string.find(string.lower(child.sprite), string.lower(filter), 1, true) then count = count + 1 child.visible = true else child.visible = false end end tab.tab.enabled = (count > 0) local name = splitstring(tab.tab.name,":") end tabs.selected_tab_index = selected or 1 -- end of shenanigans if textfield.visible then textfield.focus() end -- fix textfield focus end local function toggle_search(player,element,override) local textfield = player.gui.screen[DID.custom_gui]["display-header"]["display-search-textfield"] if textfield then textfield.visible = override or not textfield.visible element.style = (override or textfield.visible) and "display_small_button_active" or "display_small_button" if not textfield.visible and not override then textfield.text = "" display_filter_tabs(player,"") else textfield.focus() end end end local display_gui_click = { ["display-symbol"] = function (event,sprite) local player = game.players[event.player_index] local last_display = get_global_player_info(player.index,"last_display") if last_display then destroy_render(last_display) render_overlay_sprite(last_display, event.element.sprite) for _,child in pairs(get_all_children_with_style(player.gui.screen[DID.custom_gui], "display_button_selected")) do child.style = "quick_bar_slot_button" child.ignored_by_interaction = false end event.element.style = "display_button_selected" event.element.ignored_by_interaction = true local map_button = player.gui.screen[DID.custom_gui]["display-header"]["display-map-marker"] if map_button then if not map_button.enabled then map_button.enabled = true elseif get_has_map_marker(last_display) then local spritetype, spritename = get_render_sprite_info(last_display) change_map_markers(last_display, spritetype, spritename) end end end end, ["display-search-button"] = function (event) toggle_search(game.players[event.player_index], event.element) end, ["display-map-marker"] = function (event) local last_display = get_global_player_info(event.player_index,"last_display") if last_display then if get_has_map_marker(last_display) then event.element.style = "display_small_button" remove_markers(last_display) local player = game.players[event.player_index] player.play_sound{path = "map-marker-pong"} else local spritetype, spritename = get_render_sprite_info(last_display) add_map_marker(last_display, spritetype, spritename) event.element.style = "display_small_button_active" end end end, ["display-header-close"] = function (event) gui_close(event) end, } local function is_a_display(entity) return DID.displays[entity.name] ~= nil end local function get_display_event_filter() local filters = {} for display,_ in pairs(DID.displays) do table.insert(filters, { filter = "name", name = display }) end return filters end local function event_raised_destroy(event) if event.entity and event.entity.valid and is_a_display(event.entity) then -- remove any map markers remove_markers(event.entity) -- close any/all open guis for _,player in pairs(game.players) do local last_display = get_global_player_info(player.index, "last_display") local frame = player.gui.screen[DID.custom_gui] if frame and event.entity == last_display then frame.destroy() end end end end local function gui_click(event) -- check the entity this gui refers to - in multiplayer it could have been removed while player wasn't logged in if event.player_index then local player = game.players[event.player_index] local frame = player.gui.screen[DID.custom_gui] local last_display = get_global_player_info(player.index, "last_display") if frame and (not last_display or not last_display.valid) then frame.destroy() return end end -- is there a method for this element? local clicked = splitstring(event.element.name,":") if display_gui_click[clicked[1]] then display_gui_click[clicked[1]](event,clicked[2]) return end end local function create_display_gui(player, selected) if not player or not selected then return end -- cache which entity this gui belongs to set_global_player_info(player.index,"last_display",selected) -- close any existing gui local frame = player.gui.screen[DID.custom_gui] if frame then frame.destroy() end player.opened = player.gui.screen -- get markers and currently rendered sprite local markers = next(get_map_markers(selected)) ~= nil local sname, stype = get_render_sprite_info(selected) local render_sprite = (sname and stype) and sname.."/"..stype or nil -- create frame frame = player.gui.screen.add { type = "frame", name = DID.custom_gui, direction = "vertical", style = "display_frame", } -- update frame location if cached if get_global_player_info(player.index,"display_gui_location") then frame.location = get_global_player_info(player.index,"display_gui_location") else frame.force_auto_center() end -- header local header = frame.add { type = "flow", direction = "horizontal", name = "display-header", } header.style.bottom_padding = -4 header.style.horizontally_stretchable = true -- title header.add { type = "label", caption = {"controls.display-plate"}, style = "frame_title", } -- "drag filler" local filler = header.add { type = "empty-widget", style = "draggable_space_header", } filler.style.natural_height = 24 filler.style.horizontally_stretchable = true filler.drag_target = frame -- search textfield local search_textfield = header.add { name = "display-search-textfield", type = "textfield", style = "search_popup_textfield", } search_textfield.style.height = 24 search_textfield.style.width = 100 search_textfield.visible = false -- search button local search_button = header.add { name = "display-search-button", type = "sprite-button", sprite = "utility/search_white", style = "display_small_button", tooltip = {"gui.search-with-focus","__CONTROL__focus-search__"}, } -- map marker button local map_button = header.add { name = "display-map-marker", type = "sprite-button", sprite = "display-map-marker", style = markers and "display_small_button_active" or "display_small_button", tooltip = {"controls.display-map-marker"}, } map_button.enabled = (find_entity_render(selected) ~= nil) -- close button local close_button = header.add { name = "display-header-close", type = "sprite-button", style = "display_small_button", sprite = "utility/close_white", tooltip = {"controls.close-gui"}, } -- body frame local content_frame = frame.add { type = "frame", name = "inner-frame", style = "display_inside_frame", direction = "vertical", } content_frame.style.top_margin = 8 -- tabbed pane local display_tabs = content_frame.add { name = "display-tabs", type = "tabbed-pane", style = "display_tabbed_pane", } -- build a table of info about existing items/fluids -- groups of subgroups of sprites -> localised_string local button_table = {} for prototype_type,prototypes in pairs(DID.elem_prototypes) do for _,prototype in pairs(game[prototypes]) do if not DID.displays[prototype.name] and not ((prototype_type == "item" and prototype.has_flag("hidden")) or (prototype_type == "fluid" and prototype.hidden)) then local group = (prototype.group.name == "fluids") and "intermediate-products" or prototype.group.name if not DID.group_blacklist[group] then if button_table[group] == nil then button_table[group] = {} end if button_table[group][prototype.subgroup.name] == nil then button_table[group][prototype.subgroup.name] = {} end button_table[group][prototype.subgroup.name][prototype_type.."/"..prototype.name] = prototype.localised_name end end end end -- determine the biggest tab size local max_rows = 0 for group,subgroups in pairs(button_table) do local rows = 0 for subgroup,entries in pairs(subgroups) do rows = rows + math.ceil(table_size(entries)/DID.grid_columns) end max_rows = math.max(rows,max_rows) end -- set up tabs local tab_index = 1 for group,subgroups in pairs(button_table) do -- this tab local this_tab = false local tab = display_tabs.add{ type = "tab", name = "display-tab:"..group, caption = "[img=item-group/"..group.."]", tooltip = game.item_group_prototypes[group].localised_name, style = "display_tab", } tab.style.width = (420/table_size(button_table)) local tab_content = display_tabs.add { type = "frame", direction = "vertical", name = "display-group:"..group, style = "display_tab_deep_frame", } tab_content.style.width = 400 -- add table of buttons for subgroup,entries in pairs(subgroups) do local subgroup_table = tab_content.add{ type = "table", column_count = DID.grid_columns, style = "display_buttons", } local count = 0 for sprite,localised_name in pairs(entries) do -- add the button local button = subgroup_table.add{ type = "sprite-button", name = "display-symbol:"..sprite, sprite = sprite, style = (render_sprite and render_sprite == sprite) and "display_button_selected" or "quick_bar_slot_button", tooltip = localised_name, } button.ignored_by_interaction = (render_sprite and render_sprite == sprite) if not this_tab and (render_sprite and render_sprite == sprite) then this_tab = true end count = count + 1 end end display_tabs.add_tab(tab,tab_content) -- switch selection to this tab if rendered sprite exists if this_tab then display_tabs.selected_tab_index = tab_index end tab_index = tab_index + 1 end -- make all tabs as big as biggest for _,tab in pairs(display_tabs.tabs) do tab.content.style.height = math.min(640, max_rows * 40) end end local function player_cannot_reach(player,entity) player.play_sound{path = "utility/cannot_build"} player.create_local_flying_text{text={"cant-reach"}, position=entity.position} end local function set_up_display_from_ghost(entity,tags) if tags["display-plate-sprite-type"] and tags["display-plate-sprite-name"] then render_overlay(entity, tags["display-plate-sprite-type"], tags["display-plate-sprite-name"]) if tags["display-plate-sprite-map-marker"] then add_map_marker(entity, tags["display-plate-sprite-type"], tags["display-plate-sprite-name"]) end end end -- local function reset_globals() -- global.translations = nil -- end ------------------------------------------------------------------------------------------------------------------------------------------------------ -- event handlers -- script.on_configuration_changed(reset_globals) script.on_event(defines.events.on_gui_closed, gui_close) script.on_event(defines.events.on_gui_click, gui_click) script.on_event(defines.events.on_player_mined_entity, event_raised_destroy, get_display_event_filter()) script.on_event(defines.events.on_robot_mined_entity, event_raised_destroy, get_display_event_filter()) script.on_event(defines.events.on_entity_died, event_raised_destroy, get_display_event_filter()) script.on_event(defines.events.on_built_entity, function (event) if event.tags and event.created_entity and event.created_entity.valid then set_up_display_from_ghost(event.created_entity, event.tags) end end, get_display_event_filter()) script.on_event(defines.events.on_robot_built_entity, function (event) if event.tags and event.created_entity and event.created_entity.valid then set_up_display_from_ghost(event.created_entity, event.tags) end end, get_display_event_filter()) script.on_event(defines.events.script_raised_revive, function (event) if event.tags and event.entity and event.entity.valid and is_a_display(event.entity) then set_up_display_from_ghost(event.entity, event.tags) end end) script.on_event("deadlock-open-gui", function(event) local player = game.players[event.player_index] if player.cursor_stack and player.cursor_stack.valid_for_read then return end local selected = player and player.selected if selected and selected.valid and is_a_display(selected) then if player.can_reach_entity(selected) then create_display_gui(player, selected) else player_cannot_reach(player, selected) end end end) script.on_event("deadlock-focus-search", function(event) local player = game.players[event.player_index] local frame = player.gui.screen[DID.custom_gui] if frame then local search = player.gui.screen[DID.custom_gui]["display-header"]["display-search-button"] toggle_search(player, search, true) end end) script.on_event(defines.events.on_entity_settings_pasted, function (event) if event.destination and event.destination.valid and event.source and event.source.valid and is_a_display(event.destination) and is_a_display(event.source) then local spritetype, spritename = get_render_sprite_info(event.source) if spritetype and spritename then destroy_render(event.destination) render_overlay(event.destination, spritetype, spritename) remove_markers(event.destination) if get_has_map_marker(event.source) then add_map_marker(event.destination, spritetype, spritename) end end end end) script.on_event(defines.events.on_player_setup_blueprint, function (event) local player = game.players[event.player_index] local blueprint = nil if player and player.blueprint_to_setup and player.blueprint_to_setup.valid_for_read then blueprint = player.blueprint_to_setup elseif player and player.cursor_stack.valid_for_read and player.cursor_stack.name == "blueprint" then blueprint = player.cursor_stack end if blueprint then for index,entity in pairs(event.mapping.get()) do local stype,sname = get_render_sprite_info(entity) if stype and sname then blueprint.set_blueprint_entity_tag(index, "display-plate-sprite-type", stype) blueprint.set_blueprint_entity_tag(index, "display-plate-sprite-name", sname) blueprint.set_blueprint_entity_tag(index, "display-plate-sprite-map-marker", get_has_map_marker(entity)) end end end end) script.on_event(defines.events.on_gui_location_changed, function (event) if event.element.name == DID.custom_gui then set_global_player_info(event.player_index, "display_gui_location", event.element.location) end end) script.on_event(defines.events.on_player_changed_position, function (event) local player = game.players[event.player_index] if player.gui.screen[DID.custom_gui] then local last_display = get_global_player_info(event.player_index, "last_display") if last_display and last_display.valid and not player.can_reach_entity(last_display) then gui_close(event) end end end) script.on_event(defines.events.on_gui_text_changed, function(event) local player = game.players[event.player_index] if player and event.element.name == "display-search-textfield" then display_filter_tabs(player, event.element.text) end end) ------------------------------------------------------------------------------------------------------------------------------------------------------