1
0
This repository has been archived on 2023-04-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
IndustrialDisplayPlates-Arc…/control.lua
2023-04-10 03:20:44 +02:00

633 lines
22 KiB
Lua

------------------------------------------------------------------------------------------------------------------------------------------------------
-- 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
local map_type = (icon_type == "virtual-signal") and "virtual" or icon_type
entity.force.add_chart_tag(entity.surface, { icon = { type = map_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 map_type = (icon_type == "virtual-signal") and "virtual" or icon_type
local markers = get_map_markers(entity)
if markers then
for _,marker in pairs(markers) do
marker.icon = { type = map_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), "/")
return strings[1], strings[2], strings[1] == 'virtual-signal' and 'virtual' or strings[1]
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 = ""
if prototype_type == "virtual-signal" then
group = prototype.subgroup.group.name
elseif prototype_type == "fluids" then
group = "intermediate-products"
else
group = prototype.group.name
end
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
remote.add_interface(
"IndustrialDisplayPlates",
{
get_sprite = function(event)
if event and event.entity and event.entity.valid then
local spritetype, spritename = get_render_sprite_info(event.entity)
return {spritetype=spritetype, spritename=spritename}
else
return nil
end
end,
set_sprite = function(event)
if event and event.entity and event.entity.valid and event.sprite and game.is_valid_sprite_path(event.sprite) then
destroy_render(event.entity)
render_overlay_sprite(event.entity, event.sprite)
if get_has_map_marker(event.entity) then
local spritetype, spritename = get_render_sprite_info(event.entity)
change_map_markers(event.entity, spritetype, spritename)
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
-- Workaround for blueprint update bug
if blueprint.get_blueprint_entity_count() == 0 then
player.print({"display-plates.blueprint-wont-have-icons"})
return
end
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
-- mod migration info
local mod_migration_info = get_global_player_info(event.player_index, "mod_migration_info")
if not mod_migration_info then
player.print({"", "[img=entity.copper-display] ",{"display-plates.mod-migration-info"}})
set_global_player_info(event.player_index, "mod_migration_info", true)
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)
------------------------------------------------------------------------------------------------------------------------------------------------------