-- initial settings, before loading them local display_mode = "alerts-list-display-mode-icon-only" local show_percentage = false local columns_for_compact_mode = 4 -- constants local ZOOM_LEVEL = 0.1 function format_guielement_name(prefix, surface, pos) return prefix .. surface.index .. "_" .. pos.x .. "_" .. pos.y end function starts_with(str, start) return str:sub(1, #start) == start end function signalid_to_spritepath(signalid) local type = signalid.type if type == "virtual" then type = "virtual-signal" end return type .. "/" .. signalid.name end function refresh_speakers() if global["speaker_cache"] then for k, _ in pairs(global["speaker_cache"]) do global["speaker_cache"][k] = nil end for k, v in pairs(game.surfaces) do for sid, speaker in pairs(v.find_entities_filtered {type = "programmable-speaker"}) do table.insert(global["speaker_cache"], speaker) end end end end function remove_speaker(speaker) if global["speaker_cache"] then for k, v in pairs(global["speaker_cache"]) do if v == speaker then table.remove(global["speaker_cache"], k) end end end end function compare_signals(s1, s2) return s1 == s2 or (s1 and s2 and s1.type == s2.type and s1.name == s2.name) end function on_built(e) if (e.created_entity.type == "programmable-speaker") then if not global["speaker_cache"] then global["speaker_cache"] = {} end table.insert(global["speaker_cache"], e.created_entity) for k, player in pairs(game.players) do draw_gui(player) end end end function on_destroyed(e) if e.entity.type == "programmable-speaker" then remove_speaker(e.entity) for k, player in pairs(game.players) do draw_gui(player) end end end function calculate_percentage(speaker, circuit) local all_signals = speaker.get_merged_signals() local left = 0 local right = circuit.condition["constant"] if all_signals then for k, v in pairs(all_signals) do if compare_signals(v.signal, circuit.condition.first_signal) then left = v.count end if compare_signals(v.signal, circuit.condition["second_signal"]) then right = v.count end end end if right == 0 then right = 1 end percentage = left / right return percentage end function get_signal_value(signals, signalid) local value = 0 if signals then for k, v in pairs(signals) do if v.signal.type == signalid.type and v.signal.name == signalid.name then value = v.count break end end end return value end function left_column_for(tbl, speaker, circuit) local elem_name = format_guielement_name("alerts_list_icons_", speaker.surface, speaker.position) local element = tbl[elem_name] if display_mode == "alerts-list-display-mode-icon-only" or display_mode == "alerts-list-display-mode-icon-and-text" then if not element then element = tbl.add { type = "sprite-button", name = elem_name, enabled = true, mouse_button_filter = {"left"}, sprite = signalid_to_spritepath(speaker.alert_parameters.icon_signal_id), show_percent_for_small_numbers = show_percentage } end element.sprite = signalid_to_spritepath(speaker.alert_parameters.icon_signal_id) if show_percentage then element.number = calculate_percentage(speaker, circuit) else element.number = get_signal_value(speaker.get_merged_signals(), circuit.condition.first_signal) end element.tooltip = speaker.alert_parameters.alert_message -- TODO: else for complex icon (full condition?) end -- update element data end function right_column_for(tbl, speaker, circuit) if display_mode ~= "alerts-list-display-mode-icon-only" then local elem_name = format_guielement_name("alerts_list_info_", speaker.surface, speaker.position) local element = tbl[elem_name] if not element then element = tbl.add {type = "label", name = elem_name, caption = speaker.alert_parameters.alert_message} end element.caption = speaker.alert_parameters.alert_message end end function on_click(element, player) if element and player then if starts_with(element.name, "alerts_list_icons_") then for k, speaker in pairs(global["speaker_cache"]) do if speaker then if element.name == format_guielement_name("alerts_list_icons_", speaker.surface, speaker.position) then player.open_map(speaker.position, ZOOM_LEVEL) break end end end end end end function fill_table_with_speakers(tbl, speakers, player) if not speakers then speakers = {} end local remove_list = {} for k, v in pairs(tbl.children) do remove_list[v.name] = v end local found_any = false for i, speaker in pairs(speakers) do -- here we do assume we always have a list of speakers that have global alert set if speaker and speaker.valid and speaker.alert_parameters and speaker.alert_parameters.show_alert then local behavior = speaker.get_control_behavior() if behavior and behavior["circuit_condition"] then if behavior.circuit_condition.fulfilled and speaker.alert_parameters.icon_signal_id then found_any = true left_column_for(tbl, speaker, behavior.circuit_condition) right_column_for(tbl, speaker, behavior.circuit_condition) remove_list[format_guielement_name("alerts_list_icons_", speaker.surface, speaker.position)] = nil remove_list[format_guielement_name("alerts_list_info_", speaker.surface, speaker.position)] = nil end end end end for k, v in pairs(remove_list) do v.destroy() end if not found_any then -- get rid of GUI if there's nothing to be shown for k, player in pairs(game.players) do if player.gui.left["alerts_list_frame_main"] then player.gui.left["alerts_list_frame_main"].destroy() end end end end -- return GUI function build_gui(player) if not player.gui.left["alerts_list_frame_main"] then -- TODO: make it transparent background? local gui = player.gui.left.add {type = "frame", name = "alerts_list_frame_main", direction = "vertical"} local columns = 2 if display_mode == "alerts-list-display-mode-icon-only" then columns = columns_for_compact_mode end gui.add { type = "label", name = "alerts_list_label", caption = {"gui-alerts-list.label-caption"}, tooltip = {"tooltip-alerts-list.label-caption"} } local tbl = gui.add {type = "table", name = "alerts_list_alert_table", column_count = columns} return gui else return player.gui.left["alerts_list_frame_main"] end end -- builds GUI and fills it with current speakers data function draw_gui(player) local gui = build_gui(player) local tbl = gui.alerts_list_alert_table fill_table_with_speakers(tbl, global["speaker_cache"], player) end function reload_settings(player) display_mode = settings.get_player_settings(player)["alerts-list-display-mode"].value show_percentage = settings.get_player_settings(player)["alerts-list-show-percentage"].value columns_for_compact_mode = settings.get_player_settings(player)["alerts-list-columns-for-compact-mode"].value if player.gui.left["alerts_list_frame_main"] then player.gui.left["alerts_list_frame_main"].destroy() end draw_gui(player) end -- register events script.on_init( function() for k, player in pairs(game.players) do reload_settings(player) end if not global["speaker_cache"] then global["speaker_cache"] = {} end refresh_speakers() end ) script.on_configuration_changed( function() for k, player in pairs(game.players) do reload_settings(player) end if not global["speaker_cache"] then global["speaker_cache"] = {} end refresh_speakers() end ) script.on_nth_tick( settings.startup["alerts-list-refresh-rate"].value, function(e) for k, player in pairs(game.players) do draw_gui(player) end end ) script.on_event( defines.events.on_player_joined_game, function(e) local player = game.players[e.player_index] reload_settings(player) if not global["speaker_cache"] then global["speaker_cache"] = {} end refresh_speakers() end ) script.on_event( defines.events.on_runtime_mod_setting_changed, function(e) if e.player_index ~= nil then local player = game.players[e.player_index] reload_settings(player) end end ) script.on_event( {defines.events.on_built_entity, defines.events.on_robot_built_entity}, function(e) on_built(e) end ) script.on_event( {defines.events.on_pre_player_mined_item, defines.events.on_robot_mined_entity, defines.events.on_entity_died}, function(e) on_destroyed(e) end ) script.on_event( {defines.events.on_gui_click}, function(e) local player = game.players[e.player_index] if player ~= nil and e.button == defines.mouse_button_type.left then on_click(e.element, player) end end )