Merge pull request #145 from explosivegaming/feature/gui-v5

Gui v5
This commit is contained in:
Cooldude2606
2020-03-21 13:07:12 +00:00
committed by GitHub
135 changed files with 5761 additions and 15918 deletions

View File

@@ -9,6 +9,6 @@ local Commands = require 'expcore.commands' --- @dep expcore.commands
--- Opens the debug pannel for viewing tables.
-- @command debug
Commands.new_command('debug','Opens the debug pannel for viewing tables.')
:register(function(player,raw)
:register(function(player)
DebugView.open_dubug(player)
end)

View File

@@ -89,8 +89,8 @@ Tasks.remove_task(task_id)
function Tasks.remove_task(task_id)
local task = Store.get(task_store,task_id)
local force_name = task.force_name
Store.clear(task_store,task_id)
table.remove_element(force_tasks[force_name],task_id)
Store.clear(task_store,task_id)
end
--[[-- Update the message and last edited information for a task

View File

@@ -150,7 +150,7 @@ function Warps.make_warp_area(warp_id)
local position = warp.position
local posx = position.x
local posy = position.y
local radius = config.activation_range
local radius = config.standard_proximity_radius
local radius2 = radius^2
-- Get the tile that is being replaced, store.update not needed as we dont want it to trigger
@@ -203,7 +203,7 @@ function Warps.remove_warp_area(warp_id)
local warp = Store.get(warp_store,warp_id)
local position = warp.position
local surface = warp.surface
local radius = config.activation_range
local radius = config.standard_proximity_radius
local radius2 = radius^2
-- Check that a warp area was created previously

View File

@@ -1,7 +1,7 @@
local Event = require 'utils.event' --- @dep utils.event
local table = require 'utils.table' --- @dep utils.table
local Gui = require 'utils.gui' --- @dep utils.gui
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local Event = require 'utils.event'
local table = require 'utils.table'
local Gui = require 'utils.gui'
local Model = require 'modules.gui.debug.model'
local format = string.format
local insert = table.insert
@@ -19,13 +19,16 @@ local name_lookup = {}
-- GUI names
local checkbox_name = Gui.uid_name()
local filter_name = Gui.uid_name()
local clear_filter_name = Gui.uid_name()
-- global tables
local enabled = {}
local last_events = {}
global.debug_event_view = {
enabled = enabled,
last_events = last_events
last_events = last_events,
filter = ''
}
function Public.on_open_debug()
@@ -90,24 +93,73 @@ end
table.sort(grid_builder)
function Public.show(container)
local main_frame_flow = container.add({type = 'flow', direction = 'vertical'})
local scroll_pane = main_frame_flow.add({type = 'scroll-pane'})
local gui_table = scroll_pane.add({type = 'table', column_count = 3, draw_horizontal_lines = true})
local function redraw_event_table(gui_table, filter)
for _, event_name in pairs(grid_builder) do
local index = events[event_name]
gui_table.add({type = 'flow'}).add {
name = checkbox_name,
type = 'checkbox',
state = enabled[index] or false,
caption = event_name
}
if filter == '' or event_name:find(filter) then
local index = events[event_name]
gui_table.add({type = 'flow'}).add {
name = checkbox_name,
type = 'checkbox',
state = enabled[index] or false,
caption = event_name
}
end
end
end
function Public.show(container)
local filter = global.debug_event_view.filter
local main_frame_flow = container.add({type = 'flow', direction = 'vertical'})
local filter_flow = main_frame_flow.add({type = 'flow', direction = 'horizontal'})
filter_flow.add({type = 'label', caption = 'filter'})
local filter_textfield = filter_flow.add({type = 'textfield', name = filter_name, text = filter})
local clear_button = filter_flow.add({type = 'button', name = clear_filter_name, caption = 'clear'})
local scroll_pane = main_frame_flow.add({type = 'scroll-pane'})
local gui_table = scroll_pane.add({type = 'table', column_count = 3, draw_horizontal_lines = true})
Gui.set_data(filter_textfield, gui_table)
Gui.set_data(clear_button, {gui_table = gui_table, filter_textfield = filter_textfield})
redraw_event_table(gui_table, filter)
end
Gui.on_checked_state_changed(checkbox_name, on_gui_checked_state_changed)
Gui.on_text_changed(
filter_name,
function(event)
local element = event.element
local gui_table = Gui.get_data(element)
local filter = element.text:gsub(' ', '_')
global.debug_event_view.filter = filter
element.text = filter
gui_table.clear()
redraw_event_table(gui_table, filter)
end
)
Gui.on_click(
clear_filter_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local filter_textfield = data.filter_textfield
local gui_table = data.gui_table
filter_textfield.text = ''
global.debug_event_view.filter = ''
gui_table.clear()
redraw_event_table(gui_table, '')
end
)
-- Event registers (TODO: turn to removable hooks.. maybe)
for name, id in pairs(events) do
name_lookup[id] = name

View File

@@ -0,0 +1,128 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local ExpGui = require 'expcore.gui' --- @dep utils.global
local Color = require 'resources.color_presets' --- @dep resources.color_presets
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'Elements'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for element_id, file_path in pairs(ExpGui.file_paths) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = element_id..' - '..file_path}
Gui.set_data(header, element_id)
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local element_id = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {'Gui.defines[', element_id, ']'}
input_text_box.style.font_color = Color.black
local content = dump(ExpGui.debug_info[element_id]) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -24,8 +24,8 @@ function Public.show(container)
local left_panel_style = left_panel.style
left_panel_style.width = 300
for store_id, token_name in pairs(Store.file_paths) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = store_id..' - '..token_name}
for store_id, file_path in pairs(Store.file_paths) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = store_id..' - '..file_path}
Gui.set_data(header, store_id)
end

View File

@@ -6,6 +6,7 @@ local Public = {}
local pages = {
require 'modules.gui.debug.redmew_global_view',
require 'modules.gui.debug.expcore_store_view',
require 'modules.gui.debug.expcore_gui_view',
require 'modules.gui.debug.global_view',
require 'modules.gui.debug.package_view',
require 'modules.gui.debug._g_view',
@@ -31,7 +32,7 @@ function Public.open_dubug(player)
return
end
frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3001', direction = 'vertical'}
frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3002', direction = 'vertical'}
local frame_style = frame.style
frame_style.height = 600
frame_style.width = 900

View File

@@ -1,5 +1,5 @@
--[[-- Gui Module - Player List
- Adds a player list to show names and play time; also includes action buttons which can apply to players
- Adds a player list to show names and play time; also includes action buttons which can preform actions to players
@gui Player-List
@alias player_list
]]
@@ -26,42 +26,21 @@ end)
-- Set the config to use these stores
config.set_store_uids(selected_player_store,selected_action_store)
--- Used to open the map on a player or toggle the settings
local zoom_to_map_name = Gui.uid_name()
Gui.on_click(zoom_to_map_name,function(event)
local selected_player_name = event.element.caption
local selected_player = Game.get_player_from_any(selected_player_name)
if event.button == defines.mouse_button_type.left then
-- LMB will open the map to the selected player
local position = selected_player.position
event.player.zoom_to_world(position,1.75)
else
-- RMB will toggle the settings
local player = event.player
local old_selected_player_name = Store.get(selected_player_store,player)
if selected_player_name == old_selected_player_name then
Store.clear(selected_player_store,player)
else
Store.set(selected_player_store,player,selected_player_name)
end
end
end)
--- Button used to open the action bar
-- @element open_action_bar
local open_action_bar =
Gui.new_button()
:set_sprites('utility/expand_dots_white')
:set_tooltip{'player-list.open-action-bar'}
:set_embedded_flow(function(element,selected_player_name)
return selected_player_name
end)
:set_style('frame_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.width = 8
style.height = 14
end)
:on_click(function(player,element)
Gui.element{
type = 'sprite-button',
sprite = 'utility/expand_dots_white',
tooltip = {'player-list.open-action-bar'},
style = 'frame_button'
}
:style{
padding = -2,
width = 8,
height = 14
}
:on_click(function(player,element,_)
local selected_player_name = element.parent.name
local old_selected_player_name = Store.get(selected_player_store,player)
if selected_player_name == old_selected_player_name then
@@ -74,15 +53,14 @@ end)
--- Button used to close the action bar
-- @element close_action_bar
local close_action_bar =
Gui.new_button()
:set_sprites('utility/close_black','utility/close_white')
:set_tooltip{'player-list.close-action-bar'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-1,-1,-1,-1)
style.height = 28
style.width = 28
end)
:on_click(function(player,element)
Gui.element{
type = 'sprite-button',
sprite = 'utility/close_black',
tooltip = {'player-list.close-action-bar'},
style = 'shortcut_bar_button_red'
}
:style(Gui.sprite_style(30,-1,{ top_margin = -1, right_margin = -1 }))
:on_click(function(player,_)
Store.clear(selected_player_store,player)
Store.clear(selected_action_store,player)
end)
@@ -90,14 +68,13 @@ end)
--- Button used to confirm a reason
-- @element reason_confirm
local reason_confirm =
Gui.new_button()
:set_sprites('utility/confirm_slot')
:set_tooltip{'player-list.reason-confirm'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-1,-1,-1,-1)
style.height = 28
style.width = 28
end)
Gui.element{
type = 'sprite-button',
sprite = 'utility/confirm_slot',
tooltip = {'player-list.reason-confirm'},
style = 'shortcut_bar_button_green'
}
:style(Gui.sprite_style(30,-1,{ left_margin = -2, right_margin = -1 }))
:on_click(function(player,element)
local reason = element.parent.entry.text or 'Non Given'
local action_name = Store.get(selected_action_store,player)
@@ -108,109 +85,97 @@ end)
element.parent.entry.text = ''
end)
--[[ Creates the main gui areas for the player list
element
> container
>> scroll
>>> table
>> action_bar
]]
local function generate_container(player,element)
Gui.set_padding(element,2,2,2,2)
element.style.minimal_width = 200
--- Set of elements that are used to make up a row of the player table
-- @element add_player_base
local add_player_base =
Gui.element(function(event_trigger,parent,player_data)
-- Add the button to open the action bar
local toggle_action_bar_flow = parent.add{ type = 'flow', name = player_data.name }
open_action_bar(toggle_action_bar_flow)
-- main container which contains the other elements
local container =
element.add{
name='container',
type='frame',
direction='vertical',
style='window_content_frame_packed'
-- Add the player name
local player_name_flow = parent.add{ type = 'flow', 'player-name-'..player_data.index }
local player_name = player_name_flow.add{
type = 'label',
name = event_trigger,
caption = player_data.name,
tooltip = {'player-list.open-map',player_data.name,player_data.tag,player_data.role_name}
}
Gui.set_padding(container)
player_name.style.padding = {0,2,0,0}
player_name.style.font_color = player_data.chat_color
-- 3 wide table to contain: action button, player name, and play time
local list_table = Gui.create_scroll_table(container,3,188)
-- action bar which contains the different action buttons
local action_bar =
container.add{
name='action_bar',
type='frame',
style='subfooter_frame'
-- Add the time played label
local alignment = Gui.alignment(parent,'player-time-'..player_data.index)
local time_label = alignment.add{
name = 'label',
type = 'label',
caption = player_data.caption,
tooltip = player_data.tooltip
}
Gui.set_padding(action_bar,1,1,3,3)
action_bar.style.horizontally_stretchable = true
action_bar.style.height = 35
time_label.style.padding = 0
-- reason bar which contains the reason text field and confirm button
local reason_bar =
container.add{
name='reason_bar',
type='frame',
style='subfooter_frame'
}
Gui.set_padding(reason_bar,-1,-1,3,3)
reason_bar.style.horizontally_stretchable = true
reason_bar.style.height = 35
local action_name = Store.get(selected_action_store,player)
reason_bar.visible = action_name ~= nil
return time_label
end)
:on_click(function(player,element,event)
local selected_player_name = element.caption
local selected_player = Game.get_player_from_any(selected_player_name)
if event.button == defines.mouse_button_type.left then
-- LMB will open the map to the selected player
local position = selected_player.position
event.player.zoom_to_world(position,1.75)
else
-- RMB will toggle the settings
local old_selected_player_name = Store.get(selected_player_store,player)
if selected_player_name == old_selected_player_name then
Store.clear(selected_player_store,player)
else
Store.set(selected_player_store,player,selected_player_name)
end
end
end)
-- text entry for the reason bar
local reason_field =
reason_bar.add{
name='entry',
type='textfield',
style='stretchable_textfield',
tooltip={'player-list.reason-entry'}
}
Gui.set_padding(reason_field)
reason_field.style.height = 28
reason_field.style.minimal_width = 160
reason_confirm(reason_bar)
return list_table, action_bar
-- Removes the three elements that are added as part of the base
local function remove_player_base(parent,player)
Gui.destroy_if_valid(parent[player.name])
Gui.destroy_if_valid(parent['player-name-'..player.index])
Gui.destroy_if_valid(parent['player-time-'..player.index])
end
--- Adds buttons and permission flows to the action bar
local function generate_action_bar(player,element)
close_action_bar(element)
local selected_player_name = Store.get(selected_player_store,player)
-- Update the time label for a player using there player time data
local function update_player_base(parent,player_time)
local time_element = parent[player_time.element_name]
if time_element and time_element.valid then
time_element.label.caption = player_time.caption
time_element.label.tooltip = player_time.tooltip
end
end
for action_name,buttons in pairs(config.buttons) do
local permission_flow =
element.add{
type='flow',
name=action_name
}
for _,button in ipairs(buttons) do
--- Adds all the buttons and flows that make up the action bar
-- @element add_action_bar
local add_action_bar_buttons =
Gui.element(function(_,parent)
close_action_bar(parent)
-- Loop over all the buttons in the config
for action_name,button_data in pairs(config.buttons) do
-- Added the permission flow
local permission_flow = parent.add{ type = 'flow', name = action_name }
permission_flow.visible = false
-- Add the buttons under that permission
for _,button in ipairs(button_data) do
button(permission_flow)
end
if not Roles.player_allowed(player,action_name) then
permission_flow.visible = false
end
if buttons.auth and selected_player_name and not buttons.auth(player,selected_player_name) then
permission_flow.visible = false
end
end
if not selected_player_name then
element.visible = false
end
end
return parent
end)
--- Updates the action bar
local player_list_name
local function update_action_bar(player)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local element = frame.container.action_bar
--- Updates the visible state of the action bar buttons
local function update_action_bar(element)
local player = Gui.get_player_from_element(element)
local selected_player_name = Store.get(selected_player_store,player)
if not selected_player_name then
-- Hide the action bar when no player is selected
element.visible = false
else
@@ -236,132 +201,213 @@ local function update_action_bar(player)
end
end
--- Adds a player to the player list
local function add_player(list_table,player,role_name)
open_action_bar(list_table,player.name)
--- Main player list container for the left flow
-- @element player_list_container
local player_list_container =
Gui.element(function(event_trigger,parent)
-- Draw the internal container
local container = Gui.container(parent,event_trigger,200)
-- flow to contain player_name to allow all to have trigger for zoom to map
local player_name_flow =
list_table.add{
type='flow'
-- Draw the scroll table for the players
local scroll_table = Gui.scroll_table(container,184,3)
-- Change the style of the scroll table
local scroll_table_style = scroll_table.style
scroll_table_style.padding = {1,0,1,2}
-- Add the action bar
local action_bar = Gui.footer(container,nil,nil,false,'action_bar')
-- Change the style of the action bar
local action_bar_style = action_bar.style
action_bar_style.height = 35
action_bar_style.padding = {1,3}
action_bar.visible = false
-- Add the buttons to the action bar
add_action_bar_buttons(action_bar)
-- Add the reason bar
local reason_bar = Gui.footer(container,nil,nil,false,'reason_bar')
-- Change the style of the reason bar
local reason_bar_style = reason_bar.style
reason_bar_style.height = 35
reason_bar_style.padding = {-1,3}
reason_bar.visible = false
-- Add the text entry for the reason bar
local reason_field =
reason_bar.add{
name = 'entry',
type = 'textfield',
style = 'stretchable_textfield',
tooltip = {'player-list.reason-entry'}
}
Gui.set_padding(player_name_flow)
-- player name with the tooltip of their highest role and in they colour
local player_name =
player_name_flow.add{
name=zoom_to_map_name,
type='label',
caption=player.name,
tooltip={'player-list.open-map',player.name,player.tag,role_name}
}
Gui.set_padding(player_name,0,0,0,2)
player_name.style.font_color = player.chat_color
-- Change the style of the text entry
local reason_entry_style = reason_field.style
reason_entry_style.padding = 0
reason_entry_style.height = 28
reason_entry_style.minimal_width = 160
-- flow which allows right align for the play time
local time_flow = Gui.create_alignment(list_table,'player-time-'..player.index)
-- Add the confirm reason button
reason_confirm(reason_bar)
-- time given in Xh Ym and is right aligned
-- Return the exteral container
return container.parent
end)
:add_to_left_flow(true)
--- Button on the top flow used to toggle the player list container
-- @element toggle_left_element
Gui.left_toolbar_button('entity/character', {'player-list.main-tooltip'}, player_list_container, function(player)
return Roles.player_allowed(player,'gui/player-list')
end)
-- Get caption and tooltip format for a player
local function get_time_formats(online_time,afk_time)
local tick = game.tick > 0 and game.tick or 1
local percent = math.round(player.online_time/tick,3)*100
local time =
time_flow.add{
name='label',
type='label',
caption=format_time(player.online_time),
tooltip={'player-list.afk-time',percent,format_time(player.afk_time,{minutes=true,long=true})}
}
Gui.set_padding(time)
local percent = math.round(online_time/tick,3)*100
local caption = format_time(online_time)
local tooltip = {'player-list.afk-time', percent, format_time(afk_time,{minutes=true,long=true})}
return caption, tooltip
end
--- Adds fake players to the player list
local function add_fake_players(list_table,count)
local role_name = 'Fake Player'
for i = 1,count do
add_player(list_table,{
name='Player '..i,
index=0-i,
tag='',
online_time=math.random(0,game.tick),
afk_time=math.random(0,game.tick),
chat_color=table.get_random_dictionary_entry(Colors)
},role_name)
-- Get the player time to be used to update time label
local function get_player_times()
local ctn = 0
local player_times = {}
for _, player in pairs(game.connected_players) do
ctn = ctn + 1
-- Add the player time details to the array
local caption, tooltip = get_time_formats(player.online_time, player.afk_time)
player_times[ctn] = {
element_name = 'player-time-'..player.index,
caption = caption,
tooltip = tooltip
}
end
return player_times
end
--- Registers the player list
-- @element player_list
local player_list =
Gui.new_left_frame('gui/player-list')
:set_sprites('entity/character')
:set_tooltip{'player-list.main-tooltip'}
:set_open_by_default()
:set_direction('vertical')
:on_creation(function(player,element)
local list_table,action_bar = generate_container(player,element)
generate_action_bar(player,action_bar)
-- Get a sorted list of all online players
local function get_player_list_order()
-- Sort all the online players into roles
local players = {}
for _,next_player in pairs(game.connected_players) do
local highest_role = Roles.get_player_highest_role(next_player)
for _,player in pairs(game.connected_players) do
local highest_role = Roles.get_player_highest_role(player)
if not players[highest_role.name] then
players[highest_role.name] = {}
end
table.insert(players[highest_role.name],next_player)
table.insert(players[highest_role.name],player)
end
-- Sort the players from roles into a set order
local ctn = 0
local player_list_order = {}
for _,role_name in pairs(Roles.config.order) do
if players[role_name] then
for _,next_player in pairs(players[role_name]) do
add_player(list_table,next_player,role_name)
for _,player in pairs(players[role_name]) do
ctn = ctn + 1
-- Add the player data to the array
local caption, tooltip = get_time_formats(player.online_time, player.afk_time)
player_list_order[ctn] = {
name = player.name,
index = player.index,
tag = player.tag,
role_name = role_name,
chat_color = player.chat_color,
caption = caption,
tooltip = tooltip
}
end
end
end
--add_fake_players(list_table,6)
--add_fake_players(list_table,20)
end)
:on_update(function(player,element)
local list = element.container.scroll.table
for _,next_player in pairs(game.connected_players) do
local time_element_name = 'player-time-'..next_player.index
local time_element = list[time_element_name]
if time_element and time_element.valid then
time_element.label.caption = format_time(next_player.online_time)
local tick = game.tick > 0 and game.tick or 1
local percent = math.round(next_player.online_time/tick,3)*100
time_element.label.tooltip = {'player-list.afk-time',percent,format_time(next_player.afk_time,{minutes=true,long=true})}
--[[Adds fake players to the player list
for i = 1, 10 do
local online_time = math.random(1,tick)
local afk_time = math.random(online_time-(tick/10),tick)
local caption, tooltip = get_time_formats(online_time, afk_time)
player_list_order[ctn+i] = {
name='Player '..i,
index=0-i,
tag='',
role_name = 'Fake Player',
chat_color = table.get_random_dictionary_entry(Colors),
caption = caption,
tooltip = tooltip
}
end]]
return player_list_order
end
--- Update the play times every 30 sections
Event.on_nth_tick(1800,function()
local player_times = get_player_times()
for _,player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player,player_list_container)
local scroll_table = frame.container.scroll.table
for _,player_time in pairs(player_times) do
update_player_base(scroll_table,player_time)
end
end
end)
player_list_name = player_list:uid()
--- When a player leaves only remove they entry
Event.add(defines.events.on_player_left_game,function(event)
local remove_player = event.player
for _,player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player,player_list_container)
local scroll_table = frame.container.scroll.table
remove_player_base(scroll_table,remove_player)
end
end)
--- All other events require a full redraw of the table
local function redraw_player_list()
local player_list_order = get_player_list_order()
for _,player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player,player_list_container)
local scroll_table = frame.container.scroll.table
scroll_table.clear()
for _,next_player_data in ipairs(player_list_order) do
add_player_base(scroll_table,next_player_data)
end
end
end
Event.add(defines.events.on_player_joined_game,redraw_player_list)
Event.add(Roles.events.on_role_assigned,redraw_player_list)
Event.add(Roles.events.on_role_unassigned,redraw_player_list)
--- When the action player is changed the action bar will update
Store.watch(selected_player_store,function(value,player_name)
local player = Game.get_player_from_any(player_name)
update_action_bar(player)
-- Change the style of the option buttons
local frame = player_list:get_frame(player)
local data_table = frame.container.scroll.table
local frame = Gui.get_left_element(player,player_list_container)
local scroll_table = frame.container.scroll.table
update_action_bar(frame.container.action_bar)
for _,next_player in pairs(game.connected_players) do
local element = data_table[next_player.name][open_action_bar.name]
local element = scroll_table[next_player.name][open_action_bar.name]
local style = 'frame_button'
if next_player.name == value then
style = 'tool_button'
end
element.style = style
Gui.set_padding(element,-2,-2,-2,-2)
element.style.width = 8
element.style.height = 14
local element_style = element.style
element_style.padding = -2
element_style.width = 8
element_style.height = 14
end
end)
--- When the action name is changed the reason input will update
Store.watch(selected_action_store,function(value,player_name)
local player = Game.get_player_from_any(player_name)
local frame = Gui.classes.left_frames.get_frame(player_list_name,player)
local frame = Gui.get_left_element(player,player_list_container)
local element = frame.container.reason_bar
if value then
-- if there is a new value then check the player is still online
@@ -379,13 +425,4 @@ Store.watch(selected_action_store,function(value,player_name)
element.visible = false
end
end)
--- Many events which trigger the gui to be re drawn, it will also update the times every 30 seconds
Event.on_nth_tick(1800,player_list 'update_all')
Event.add(defines.events.on_player_joined_game,player_list 'redraw_all')
Event.add(defines.events.on_player_left_game,player_list 'redraw_all')
Event.add(Roles.events.on_role_assigned,player_list 'redraw_all')
Event.add(Roles.events.on_role_unassigned,player_list 'redraw_all')
return player_list
end)

View File

@@ -12,8 +12,15 @@ local format_time = ext_require('expcore.common','format_time') --- @dep expcore
local Colors = require 'resources.color_presets' --- @dep resources.color_presets
local Rockets = require 'modules.control.rockets' --- @dep modules.control.rockets
--- Gets if a player is allowed to use the action buttons
local function player_allowed(player,action)
local time_formats = {
caption = function(value) return format_time(value, {minutes=true, seconds=true}) end,
caption_hours = function(value) return format_time(value) end,
tooltip = function(value) return format_time(value, {minutes=true, seconds=true, long=true}) end,
tooltip_hours = function(value) return format_time(value, {hours=true, minutes=true, seconds=true, long=true}) end
}
--- Check if a player is allowed to use certain interactions
local function check_player_permissions(player,action)
if not config.progress['allow_'..action] then
return false
end
@@ -22,38 +29,48 @@ local function player_allowed(player,action)
return false
end
if config.progress[action..'_role_permission'] and not Roles.player_allowed(player,config.progress[action..'_role_permission']) then
if config.progress[action..'_role_permission']
and not Roles.player_allowed(player,config.progress[action..'_role_permission']) then
return false
end
return true
end
--- Used on the name label to allow zoom to map
-- @element zoom_to_map
local zoom_to_map_name = Gui.uid_name()
Gui.on_click(zoom_to_map_name,function(event)
local rocket_silo_name = event.element.parent.caption
local rocket_silo = Rockets.get_silo_entity(rocket_silo_name)
event.player.zoom_to_world(rocket_silo.position,2)
--- Button to toggle the auto launch on a rocket silo
-- @elemeent toggle_launch
local toggle_launch =
Gui.element{
type = 'sprite-button',
sprite = 'utility/play',
tooltip = {'rocket-info.toggle-rocket-tooltip'}
}
:style(Gui.sprite_style(16))
:on_click(function(_,element,_)
local rocket_silo_name = element.parent.name:sub(8)
local rocket_silo = Rockets.get_silo_entity(rocket_silo_name)
if rocket_silo.auto_launch then
element.sprite = 'utility/play'
element.tooltip = {'rocket-info.toggle-rocket-tooltip'}
rocket_silo.auto_launch = false
else
element.sprite = 'utility/stop'
element.tooltip = {'rocket-info.toggle-rocket-tooltip-disabled'}
rocket_silo.auto_launch = true
end
end)
--- Used to launch the rocket, when it is ready
--- Button to remotely launch a rocket from a silo
-- @element launch_rocket
local launch_rocket =
Gui.new_button()
:set_sprites('utility/center')
:set_tooltip{'rocket-info.launch-tooltip'}
:set_embedded_flow(function(element,rocket_silo_name)
return 'launch-'..rocket_silo_name
end)
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.width = 16
style.height = 16
end)
:on_click(function(player,element)
local rocket_silo_name = element.parent.name:sub(8)
Gui.element{
type = 'sprite-button',
sprite = 'utility/center',
tooltip = {'rocket-info.launch-tooltip'}
}
:style(Gui.sprite_style(16,-1))
:on_click(function(player,element,_)
local rocket_silo_name = element.parent.name:sub(8)
local silo_data = Rockets.get_silo_data_by_name(rocket_silo_name)
if silo_data.entity.launch_rocket() then
silo_data.awaiting_reset = true
@@ -66,457 +83,519 @@ end)
end
end)
--- Used to toggle the auto launch on a rocket
-- @element toggle_rocket
local toggle_rocket =
Gui.new_button()
:set_sprites('utility/play')
:set_tooltip{'rocket-info.toggle-rocket-tooltip'}
:set_embedded_flow(function(element,rocket_silo_name)
return 'toggle-'..rocket_silo_name
--- XY cords that allow zoom to map when pressed
-- @element silo_cords
local silo_cords =
Gui.element(function(event_trigger,parent,silo_data)
local silo_name = silo_data.silo_name
local pos = silo_data.position
local name = config.progress.allow_zoom_to_map and event_trigger or nil
local tooltip = config.progress.allow_zoom_to_map and {'rocket-info.progress-label-tooltip'} or nil
-- Add the x cord flow
local flow_x = parent.add{
type ='flow',
name = 'label-x-'..silo_name,
caption = silo_name
}
flow_x.style.padding = {0,2,0,1}
-- Add the x cord label
flow_x.add{
type = 'label',
name = name,
caption = {'rocket-info.progress-x-pos',pos.x},
tooltip = tooltip
}
-- Add the y cord flow
local flow_y = parent.add{
type ='flow',
name = 'label-y-'..silo_name,
caption = silo_name
}
flow_y.style.padding = {0,2,0,1}
-- Add the y cord label
flow_y.add{
type = 'label',
name = name,
caption = {'rocket-info.progress-y-pos',pos.y},
tooltip = tooltip
}
end)
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.width = 16
style.height = 16
end)
:on_click(function(player,element)
local rocket_silo_name = element.parent.name:sub(8)
:on_click(function(player,element,_)
local rocket_silo_name = element.parent.caption
local rocket_silo = Rockets.get_silo_entity(rocket_silo_name)
if rocket_silo.auto_launch then
element.sprite = 'utility/play'
element.tooltip = {'rocket-info.toggle-rocket-tooltip'}
rocket_silo.auto_launch = false
else
element.sprite = 'utility/stop'
element.tooltip = {'rocket-info.toggle-rocket-tooltip-disabled'}
rocket_silo.auto_launch = true
end
player.zoom_to_world(rocket_silo.position,2)
end)
--- Used to toggle the visibility of the different sections
-- @element toggle_section
local toggle_section =
Gui.new_button()
:set_sprites('utility/expand_dark','utility/expand')
:set_tooltip{'rocket-info.toggle-section-tooltip'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
end)
:on_click(function(player,element)
local flow_name = element.parent.caption
local flow = element.parent.parent.parent[flow_name]
if Gui.toggle_visible(flow) then
element.sprite = 'utility/collapse_dark'
element.hovered_sprite = 'utility/collapse'
element.tooltip = {'rocket-info.toggle-section-collapse-tooltip'}
else
element.sprite = 'utility/expand_dark'
element.hovered_sprite = 'utility/expand'
element.tooltip = {'rocket-info.toggle-section-tooltip'}
end
--- Base element for each rocket in the progress list
-- @element rocket_entry
local rocket_entry =
Gui.element(function(_,parent,silo_data)
local silo_name = silo_data.silo_name
local player = Gui.get_player_from_element(parent)
-- Add the toggle auto launch if the player is allowed it
if check_player_permissions(player,'toggle_active') then
local flow = parent.add{ type = 'flow', name = 'toggle-'..silo_name}
local button = toggle_launch(flow)
button.tooltip = silo_data.toggle_tooltip
button.sprite = silo_data.toggle_sprite
end
-- Add the remote launch if the player is allowed it
if check_player_permissions(player,'remote_launch') then
local flow = parent.add{ type = 'flow', name = 'launch-'..silo_name}
local button = launch_rocket(flow)
button.enabled = silo_data.allow_launch
end
-- Draw the silo cords element
silo_cords(parent,silo_data)
-- Add a progress label
local alignment = Gui.alignment(parent,silo_name)
local element =
alignment.add{
type = 'label',
name = 'label',
caption = silo_data.progress_caption,
tooltip = silo_data.progress_tooltip
}
-- Return the progress label
return element
end)
--- Used to create the three different sections
local function create_section(container,section_name,table_size)
-- Header for the section
local header_area = Gui.create_header(
container,
{'rocket-info.section-caption-'..section_name},
{'rocket-info.section-tooltip-'..section_name},
true,
section_name..'-header'
)
--- Data label which contains a name and a value label pair
-- @element data_label
local data_label =
Gui.element(function(_,parent,label_data)
local data_name = label_data.name
local data_subname = label_data.subname
local data_fullname = data_subname and data_name..data_subname or data_name
-- Right aligned button to toggle the section
header_area.caption = section_name
toggle_section(header_area)
-- Add the name label
local name_label = parent.add{
type = 'label',
name = data_fullname..'-label',
caption = {'rocket-info.data-caption-'..data_name,data_subname},
tooltip = {'rocket-info.data-tooltip-'..data_name,data_subname}
}
name_label.style.padding = {0,2}
-- Table used to store the data
local flow_table = Gui.create_scroll_table(container,table_size,215,section_name)
flow_table.parent.visible = false
--- Right aligned label to store the data
local alignment = Gui.alignment(parent,data_fullname)
local element =
alignment.add{
type = 'label',
name = 'label',
caption = label_data.value,
tooltip = label_data.tooltip
}
element.style.padding = {0,2}
return element
end)
-- Used to update the captions and tooltips on the data labels
local function update_data_labels(parent,data_label_data)
for _, label_data in ipairs(data_label_data) do
local data_name = label_data.subname and label_data.name..label_data.subname or label_data.name
if not parent[data_name] then
data_label(parent,label_data)
else
local data_label_element = parent[data_name].label
data_label_element.tooltip = label_data.tooltip
data_label_element.caption = label_data.value
end
end
end
--[[ Creates the main structure for the gui
element
> container
local function get_progress_data(force_name)
local force_silos = Rockets.get_silos(force_name)
local progress_data = {}
>> stats-header
>>> stats
>>>> toggle_section.name
>> stats
>>> table
for _, silo_data in pairs(force_silos) do
local rocket_silo = silo_data.entity
if not rocket_silo or not rocket_silo.valid then
-- Remove from list if not valid
force_silos[silo_data.name] = nil
table.insert(progress_data,{
silo_name = silo_data.name,
remove = true
})
>> milestones-header
>>> milestones
>>>> toggle_section.name
>> milestones
>>> table
else
-- Get the progress caption and tooltip
local progress_color = Colors.white
local progress_caption = {'rocket-info.progress-caption',rocket_silo.rocket_parts}
local progress_tooltip = {'rocket-info.progress-tooltip',silo_data.launched or 0}
local status = rocket_silo.status == defines.entity_status.waiting_to_launch_rocket
if status and silo_data.awaiting_reset then
progress_caption = {'rocket-info.progress-launched'}
progress_color = Colors.green
elseif status then
progress_caption = {'rocket-info.progress-caption',100}
progress_color = Colors.cyan
else
silo_data.awaiting_reset = false
end
>> progress-header
>>> progress
>>>> toggle_section.name
>> progress
>>> table
]]
local function generate_container(player,element)
Gui.set_padding(element,1,2,2,2)
element.style.minimal_width = 200
-- Get the toggle button data
local toggle_tooltip = {'rocket-info.toggle-rocket-tooltip-disabled'}
local toggle_sprite = 'utility/play'
if rocket_silo.auto_launch then
toggle_tooltip = {'rocket-info.toggle-rocket-tooltip'}
toggle_sprite = 'utility/stop'
end
-- main container which contains the other elements
local container =
element.add{
name='container',
type='frame',
direction='vertical',
style='window_content_frame_packed'
}
Gui.set_padding(container)
if config.stats.show_stats then
create_section(container,'stats',2)
end
if config.milestones.show_milestones then
create_section(container,'milestones',2)
end
if config.progress.show_progress then
local col_count = 3
if player_allowed(player,'remote_launch') then col_count = col_count+1 end
if player_allowed(player,'toggle_active') then col_count = col_count+1 end
create_section(container,'progress',col_count)
--- label used when no active silos
container.progress.add{
type='label',
name='no_silos',
caption={'rocket-info.progress-no-silos'}
}
end
-- Insert the gui data
table.insert(progress_data,{
silo_name = silo_data.name,
position = rocket_silo.position,
allow_launch = not silo_data.awaiting_reset and status or false,
progress_color = progress_color,
progress_caption = progress_caption,
progress_tooltip = progress_tooltip,
toggle_tooltip = toggle_tooltip,
toggle_sprite = toggle_sprite
})
end
end
return progress_data
end
--[[ Creates a text label followed by a data label, or updates them if already present
element
> "data_name_extra"-label
> "data_name_extra"
>> label
]]
local function create_label_value_pair(element,data_name,value,tooltip,extra)
local data_name_extra = extra and data_name..extra or data_name
if element[data_name_extra] then
element[data_name_extra].label.caption = value
element[data_name_extra].label.tooltip = tooltip
else
--- Label used with the data
element.add{
type='label',
name=data_name_extra..'-label',
caption={'rocket-info.data-caption-'..data_name,extra},
tooltip={'rocket-info.data-tooltip-'..data_name,extra}
}
--- Right aligned label to store the data
local right_flow = Gui.create_alignment(element,data_name_extra)
right_flow.add{
type='label',
name='label',
caption=value,
tooltip=tooltip
}
end
--- Update the build progress section
local function update_build_progress(parent,progress_data)
local show_message = true
for _, silo_data in ipairs(progress_data) do
parent.no_silos.visible = false
local silo_name = silo_data.silo_name
local progress_label = parent[silo_name]
if silo_data.remove then
-- Remove the rocket from the list
Gui.destroy_if_valid(parent['toggle-'..silo_name])
Gui.destroy_if_valid(parent['launch-'..silo_name])
Gui.destroy_if_valid(parent['label-x-'..silo_name])
Gui.destroy_if_valid(parent['label-y-'..silo_name])
Gui.destroy_if_valid(parent[silo_name])
elseif not progress_label then
-- Add the rocket to the list
show_message = false
rocket_entry(parent,silo_data)
else
show_message = false
-- Update the existing labels
progress_label = progress_label.label
progress_label.caption = silo_data.progress_caption
progress_label.tooltip = silo_data.progress_tooltip
progress_label.style.font_color = silo_data.progress_color
-- Update the toggle button
local toggle_button = parent['toggle-'..silo_name]
if toggle_button then
toggle_button = toggle_button[toggle_launch.name]
toggle_button.tooltip = silo_data.toggle_tooltip
toggle_button.sprite = silo_data.toggle_sprite
end
-- Update the launch button
local launch_button = parent['launch-'..silo_name]
if launch_button then
launch_button = launch_button[launch_rocket.name]
launch_button.enabled = silo_data.allow_launch
end
end
end
if show_message then parent.no_silos.visible = true end
end
--- Creates a text and data label using times as the data
local function create_label_value_pair_time(element,data_name,raw_value,no_hours,extra)
local value = no_hours and format_time(raw_value,{minutes=true,seconds=true}) or format_time(raw_value)
local tooltip = format_time(raw_value,{hours=not no_hours,minutes=true,seconds=true,long=true})
create_label_value_pair(element,data_name,value,tooltip,extra)
end
--- Adds the different data values to the stats section
local function generate_stats(player,frame)
if not config.stats.show_stats then return end
local element = frame.container.stats.table
local force_name = player.force.name
--- Gets the label data for all the different stats
local function get_stats_data(force_name)
local force_rockets = Rockets.get_rocket_count(force_name)
local stats = Rockets.get_stats(force_name)
local stats = Rockets.get_stats(force_name)
local stats_data = {}
if config.stats.show_first_rocket then
create_label_value_pair_time(element,'first-launch',stats.first_launch or 0)
-- Format the first launch data
if config.stats.show_first_rocket then
local value = stats.first_launch or 0
table.insert(stats_data,{
name = 'first-launch',
value = time_formats.caption_hours(value),
tooltip = time_formats.tooltip_hours(value)
})
end
-- Format the last launch data
if config.stats.show_last_rocket then
local value = stats.last_launch or 0
table.insert(stats_data,{
name = 'last-launch',
value = time_formats.caption_hours(value),
tooltip = time_formats.tooltip_hours(value)
})
end
if config.stats.show_last_rocket then
create_label_value_pair_time(element,'last-launch',stats.last_launch or 0)
end
if config.stats.show_fastest_rocket then
create_label_value_pair_time(element,'fastest-launch',stats.fastest_launch or 0,true)
-- Format fastest launch data
if config.stats.show_fastest_rocket then
local value = stats.fastest_launch or 0
table.insert(stats_data,{
name = 'fastest-launch',
value = time_formats.caption_hours(value),
tooltip = time_formats.tooltip_hours(value)
})
end
-- Format total rocket data
if config.stats.show_total_rockets then
local total_rockets = Rockets.get_game_rocket_count()
total_rockets = total_rockets == 0 and 1 or total_rockets
local percentage = math.round(force_rockets/total_rockets,3)*100
create_label_value_pair(element,'total-rockets',force_rockets,{'rocket-info.value-tooltip-total-rockets',percentage})
local percentage = math.round(force_rockets/total_rockets,3)*100
table.insert(stats_data,{
name = 'total-rockets',
value = force_rockets,
tooltip = {'rocket-info.value-tooltip-total-rockets',percentage}
})
end
-- Format game avg data
if config.stats.show_game_avg then
local avg = force_rockets > 0 and math.floor(game.tick/force_rockets) or 0
create_label_value_pair_time(element,'avg-launch',avg,true)
local avg = force_rockets > 0 and math.floor(game.tick/force_rockets) or 0
table.insert(stats_data,{
name = 'avg-launch',
value = time_formats.caption(avg),
tooltip = time_formats.tooltip(avg)
})
end
-- Format rolling avg data
for _,avg_over in pairs(config.stats.rolling_avg) do
local avg = Rockets.get_rolling_average(force_name,avg_over)
create_label_value_pair_time(element,'avg-launch-n',avg,true,avg_over)
end
local avg = Rockets.get_rolling_average(force_name,avg_over)
table.insert(stats_data,{
name = 'avg-launch-n',
subname = avg_over,
value = time_formats.caption(avg),
tooltip = time_formats.tooltip(avg)
})
end
-- Return formated data
return stats_data
end
--- Creates the list of milestones
local function generate_milestones(player,frame)
if not config.milestones.show_milestones then return end
local element = frame.container.milestones.table
local force_name = player.force.name
local force_rockets = Rockets.get_rocket_count(force_name)
--- Gets the label data for the milestones
local function get_milestone_data(force_name)
local force_rockets = Rockets.get_rocket_count(force_name)
local milestone_data = {}
for _,milestone in ipairs(config.milestones) do
for _,milestone in ipairs(config.milestones) do
if milestone <= force_rockets then
local time = Rockets.get_rocket_time(force_name,milestone)
create_label_value_pair_time(element,'milestone-n',time,false,milestone)
else
create_label_value_pair_time(element,'milestone-n',0,false,milestone)
local time = Rockets.get_rocket_time(force_name,milestone)
table.insert(milestone_data,{
name = 'milestone-n',
subname = milestone,
value = time_formats.caption_hours(time),
tooltip = time_formats.tooltip_hours(time)
})
else
table.insert(milestone_data,{
name = 'milestone-n',
subname = milestone,
value = {'rocket-info.data-caption-milestone-next'},
tooltip = {'rocket-info.data-tooltip-milestone-next'}
})
break
end
end
end
return milestone_data
end
--- Creats the different buttons used with the rocket silos
local function generate_progress_buttons(player,element,silo_data)
local silo_name = silo_data.name
local rocket_silo = silo_data.entity
local status = rocket_silo.status == defines.entity_status.waiting_to_launch_rocket
local active = rocket_silo.auto_launch
-- Button to toggle a section dropdown
-- @element toggle_section
local toggle_section =
Gui.element{
type = 'sprite-button',
sprite = 'utility/expand_dark',
hovered_sprite = 'utility/expand',
tooltip = {'rocket-info.toggle-section-tooltip'}
}
:style(Gui.sprite_style(20))
:on_click(function(_,element,_)
local header_flow = element.parent
local flow_name = header_flow.caption
local flow = header_flow.parent.parent[flow_name]
if Gui.toggle_visible_state(flow) then
element.sprite = 'utility/collapse_dark'
element.hovered_sprite = 'utility/collapse'
element.tooltip = {'rocket-info.toggle-section-collapse-tooltip'}
else
element.sprite = 'utility/expand_dark'
element.hovered_sprite = 'utility/expand'
element.tooltip = {'rocket-info.toggle-section-tooltip'}
end
end)
if player_allowed(player,'toggle_active') then
local button_element = element['toggle-'..silo_name]
-- Draw a section header and main scroll
-- @element rocket_list_container
local section =
Gui.element(function(_,parent,section_name,table_size)
-- Draw the header for the section
local header = Gui.header(
parent,
{'rocket-info.section-caption-'..section_name},
{'rocket-info.section-tooltip-'..section_name},
true,
section_name..'-header'
)
if button_element then
button_element = button_element[toggle_rocket.name]
else
button_element = toggle_rocket(element,silo_name)
end
-- Right aligned button to toggle the section
header.caption = section_name
toggle_section(header)
if active then
button_element.tooltip = {'rocket-info.toggle-rocket-tooltip'}
button_element.sprite = 'utility/stop'
else
button_element.tooltip = {'rocket-info.toggle-rocket-tooltip-disabled'}
button_element.sprite = 'utility/play'
end
end
-- Table used to store the data
local scroll_table = Gui.scroll_table(parent,215,table_size,section_name)
scroll_table.parent.visible = false
if player_allowed(player,'remote_launch') then
local button_element = element['launch-'..silo_name]
-- Return the flow table
return scroll_table
end)
if button_element then
button_element = button_element[launch_rocket.name]
else
button_element = launch_rocket(element,silo_name)
end
--- Main gui container for the left flow
-- @element rocket_list_container
local rocket_list_container =
Gui.element(function(event_trigger,parent)
-- Draw the internal container
local container = Gui.container(parent,event_trigger,200)
if silo_data.awaiting_reset then
button_element.enabled = false
else
button_element.enabled = status
end
end
-- Set the container style
local style = container.style
style.padding = 0
local player = Gui.get_player_from_element(parent)
local force_name = player.force.name
-- Draw stats section
if config.stats.show_stats then
update_data_labels(section(container,'stats',2),get_stats_data(force_name))
end
-- Draw milestones section
if config.milestones.show_milestones then
update_data_labels(section(container,'milestones',2),get_milestone_data(force_name))
end
-- Draw build progress list
if config.progress.show_progress then
local col_count = 3
if check_player_permissions(player,'remote_launch') then col_count = col_count+1 end
if check_player_permissions(player,'toggle_active') then col_count = col_count+1 end
local progress = section(container,'progress',col_count)
-- Label used when there are no active silos
local no_silos = progress.add{
type = 'label',
name = 'no_silos',
caption = {'rocket-info.progress-no-silos'}
}
no_silos.style.padding = {1,2}
update_build_progress(progress,get_progress_data(force_name))
end
-- Return the exteral container
return container.parent
end)
:add_to_left_flow(function(player)
return player.force.rockets_launched > 0 and Roles.player_allowed(player,'gui/rocket-info')
end)
--- Button on the top flow used to toggle the container
-- @element toggle_left_element
Gui.left_toolbar_button('entity/rocket-silo', {'rocket-info.main-tooltip'}, rocket_list_container, function(player)
return Roles.player_allowed(player,'gui/rocket-info')
end)
--- Update the gui for all players on a force
local function update_rocket_gui_all(force_name)
local stats = get_stats_data(force_name)
local milestones = get_milestone_data(force_name)
local progress = get_progress_data(force_name)
for _,player in pairs(game.forces[force_name].players) do
local frame = Gui.get_left_element(player,rocket_list_container)
local container = frame.container
update_data_labels(container.stats.table,stats)
update_data_labels(container.milestones.table,milestones)
update_build_progress(container.progress.table,progress)
end
end
--[[ Creates build progress section
element
> toggle-"silo_name" (generate_progress_buttons)
> launch-"silo_name" (generate_progress_buttons)
> label-x-"silo_name"
>> "silo_name"
> label-y-"silo_name"
>> "silo_name"
> "silo_name"
>> label
]]
local function generate_progress(player,frame)
if not config.progress.show_progress then return end
local element = frame.container.progress.table
local force = player.force
local force_name = force.name
local force_silos = Rockets.get_silos(force_name)
if not force_silos or table.size(force_silos) == 0 then
element.parent.no_silos.visible = true
else
element.parent.no_silos.visible = false
for _,silo_data in pairs(force_silos) do
local silo_name = silo_data.name
if not silo_data.entity or not silo_data.entity.valid then
force_silos[silo_name] = nil
Gui.destroy_if_valid(element['toggle-'..silo_name])
Gui.destroy_if_valid(element['launch-'..silo_name])
Gui.destroy_if_valid(element['label-x-'..silo_name])
Gui.destroy_if_valid(element['label-y-'..silo_name])
Gui.destroy_if_valid(element[silo_name])
elseif not element[silo_name] then
local entity = silo_data.entity
local progress = entity.rocket_parts
local pos = {
x=entity.position.x,
y=entity.position.y
}
generate_progress_buttons(player,element,silo_data)
--- Creates two flows and two labels for the X and Y position
local name = config.progress.allow_zoom_to_map and zoom_to_map_name or nil
local tooltip = config.progress.allow_zoom_to_map and {'rocket-info.progress-label-tooltip'} or nil
local flow_x = element.add{
type='flow',
name='label-x-'..silo_name,
caption=silo_name
}
Gui.set_padding(flow_x,0,0,1,2)
flow_x.add{
type='label',
name=name,
caption={'rocket-info.progress-x-pos',pos.x},
tooltip=tooltip
}
local flow_y = element.add{
type='flow',
name='label-y-'..silo_name,
caption=silo_name
}
Gui.set_padding(flow_y,0,0,1,2)
flow_y.add{
type='label',
name=name,
caption={'rocket-info.progress-y-pos',pos.y},
tooltip=tooltip
}
--- Creates the progress value which is right aligned
local right_flow = Gui.create_alignment(element,silo_name)
right_flow.add{
type='label',
name='label',
caption={'rocket-info.progress-caption',progress},
tooltip={'rocket-info.progress-tooltip',silo_data.launched or 0}
}
else
local entity = silo_data.entity
local progress = entity.rocket_parts
local status = entity.status == 21
local label = element[silo_name].label
label.caption = {'rocket-info.progress-caption',progress}
label.tooltip = {'rocket-info.progress-tooltip',silo_data.launched or 0}
if status and silo_data.awaiting_reset then
label.caption = {'rocket-info.progress-launched'}
label.style.font_color = Colors.green
elseif status then
label.caption = {'rocket-info.progress-caption',100}
label.style.font_color = Colors.cyan
else
silo_data.awaiting_reset = false
label.style.font_color = Colors.white
end
generate_progress_buttons(player,element,silo_data)
end
end
end
end
--- Registers the rocket info
-- @element rocket_info
local rocket_info =
Gui.new_left_frame('gui/rocket-info')
:set_sprites('entity/rocket-silo')
:set_post_authenticator(function(player,define_name)
return player.force.rockets_launched > 0 and Gui.classes.toolbar.allowed(player,define_name)
end)
:set_open_by_default(function(player,define_name)
return player.force.rockets_launched > 0
end)
:set_direction('vertical')
:on_creation(function(player,element)
generate_container(player,element)
generate_stats(player,element)
generate_milestones(player,element)
generate_progress(player,element)
end)
:on_update(function(player,element)
generate_stats(player,element)
generate_milestones(player,element)
generate_progress(player,element)
end)
--- Event used to update the stats and the hui when a rocket is launched
--- Event used to update the stats when a rocket is launched
Event.add(defines.events.on_rocket_launched,function(event)
local force = event.rocket_silo.force
local rockets_launched = force.rockets_launched
local first_rocket = rockets_launched == 1
update_rocket_gui_all(force.name)
if force.rockets_launched == 1 then
for _,player in pairs(force.players) do
Gui.update_top_flow(player)
end
end
end)
--- Updates all the guis (and toolbar since the button may now be visible)
for _,player in pairs(force.players) do
rocket_info:update(player)
if first_rocket then
Gui.update_toolbar(player)
rocket_info:toggle(player)
end
end
--- Update only the progress gui for a force
local function update_rocket_gui_progress(force_name)
local progress = get_progress_data(force_name)
for _,player in pairs(game.forces[force_name].players) do
local frame = Gui.get_left_element(player,rocket_list_container)
local container = frame.container
update_build_progress(container.progress.table,progress)
end
end
Event.on_nth_tick(150,function()
for _,force in pairs(game.forces) do
if #Rockets.get_silos(force.name) > 0 then
update_rocket_gui_progress(force.name)
end
end
end)
--- Adds a silo to the list when it is built
local function on_built(event)
local entity = event.created_entity
if entity.valid and entity.name == 'rocket-silo' then
local force = entity.force
for _,player in pairs(force.players) do
local frame = rocket_info:get_frame(player)
generate_progress(player,frame)
end
update_rocket_gui_progress(entity.force.name)
end
end
Event.add(defines.events.on_built_entity,on_built)
Event.add(defines.events.on_robot_built_entity,on_built)
--- Optimised update for only the build progress
Event.on_nth_tick(150,function()
for _,force in pairs(game.forces) do
local silos = Rockets.get_silos(force.name)
if #silos > 0 then
for _,player in pairs(force.connected_players) do
local frame = rocket_info:get_frame(player)
generate_progress(player,frame)
end
end
end
end)
--- Redraw the progress section on role change
local function role_update_event(event)
local player = game.players[event.player_index]
local container = Gui.get_left_element(player,rocket_list_container).container
local progress = container.progress
if config.progress.show_progress then
progress.destroy()
local col_count = 3
if check_player_permissions(player,'remote_launch') then col_count = col_count+1 end
if check_player_permissions(player,'toggle_active') then col_count = col_count+1 end
progress = section(container,'progress',col_count)
-- Label used when there are no active silos
progress.add{
type = 'label',
name = 'no_silos',
caption = {'rocket-info.progress-no-silos'}
}
update_build_progress(progress,get_progress_data(player.force.name))
end
end
--- Makes sure the right buttons are present when role changes
Event.add(Roles.events.on_role_assigned,rocket_info 'redraw')
Event.add(Roles.events.on_role_unassigned,rocket_info 'redraw')
Event.add(Roles.events.on_role_assigned,role_update_event)
Event.add(Roles.events.on_role_unassigned,role_update_event)
return rocket_info
return rocket_list_container

View File

@@ -5,6 +5,7 @@
]]
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.gui
local Event = require 'utils.event' --- @dep utils.event
local format_time = ext_require('expcore.common','format_time') --- @dep expcore.common
local config = require 'config.science' --- @dep config.science
@@ -13,275 +14,360 @@ local Production = require 'modules.control.production' --- @dep modules.control
local null_time_short = {'science-info.eta-time',format_time(0,{hours=true,minutes=true,seconds=true,time=true,null=true})}
local null_time_long = format_time(0,{hours=true,minutes=true,seconds=true,long=true,null=true})
--[[ Generates the main structure for the gui
element
> container
>> header
>> scroll
>>> non_made
>>> table
>> footer (when eta is enabled)
>>> eta-label
>>> eta
>>>> label
]]
local function generate_container(element)
Gui.set_padding(element,1,2,2,2)
element.style.minimal_width = 200
--- Data label that contains the value and the surfix
-- @element production_label
local production_label =
Gui.element(function(_,parent,production_label_data)
local name = production_label_data.name
local tooltip = production_label_data.tooltip
local color = production_label_data.color
-- main container which contains the other elements
local container =
element.add{
name='container',
type='frame',
direction='vertical',
style='window_content_frame_packed'
-- Add an alignment for the number
local alignment = Gui.alignment(parent,name)
-- Add the main value label
local element =
alignment.add{
name = 'label',
type = 'label',
caption = production_label_data.caption,
tooltip = tooltip
}
Gui.set_padding(container)
-- main header for the gui
Gui.create_header(
container,
{'science-info.main-caption'},
{'science-info.main-tooltip'}
)
-- Change the style
element.style.font_color = color
-- table that stores all the data
local flow_table = Gui.create_scroll_table(container,4,185)
-- message to say that you have not made any packs yet
local non_made =
flow_table.parent.add{
name='non_made',
type='label',
caption={'science-info.no-packs'}
-- Add the surfix label
local surfix_element =
parent.add{
name = 'surfix-'..name,
type = 'label',
caption = {'science-info.unit',production_label_data.surfix},
tooltip = tooltip
}
non_made.style.width = 200
non_made.style.single_line = false
local eta
if config.show_eta then
-- footer used to store the eta
local footer =
container.add{
name='footer',
type='frame',
style='subheader_frame'
}
Gui.set_padding(footer,2,2,4,4)
footer.style.horizontally_stretchable = true
-- Change the style
local surfix_element_style = surfix_element.style
surfix_element_style.font_color = color
surfix_element_style.right_margin = 1
-- label for the footer
footer.add{
name='eta-label',
type='label',
caption={'science-info.eta-caption'},
tooltip={'science-info.eta-tooltip'},
style='heading_1_label'
}
-- Return the value label
return element
end)
-- data for the footer
local right_align = Gui.create_alignment(footer,'eta')
eta =
right_align.add{
name='label',
type='label',
caption=null_time_short,
tooltip=null_time_long,
style='heading_1_label'
}
end
return flow_table, eta
end
--[[ Adds two labels where one is right aligned and the other is a unit
element
> "name"
>> label
> spm-"name"
]]
local function add_data_label(element,name,value,secondary,tooltip)
-- Get the data that is used with the production label
local function get_production_label_data(name,tooltip,value,secondary)
local data_colour = Production.get_color(config.color_clamp, value, secondary)
local surfix,caption = Production.format_number(value)
if element[name] then
local data = element[name].label
data.caption = caption
data.tooltip = tooltip
data.style.font_color = data_colour
local label = element['spm-'..name]
label.caption = {'science-info.unit',surfix}
label.tooltip = tooltip
label.style.font_color = data_colour
else
-- right aligned number
local right_align = Gui.create_alignment(element,name)
local data =
right_align.add{
name='label',
type='label',
caption=caption,
tooltip=tooltip
}
data.style.font_color = data_colour
-- adds the unit onto the end
local label =
element.add{
name='spm-'..name,
type='label',
caption={'science-info.unit',surfix},
tooltip=tooltip
}
label.style.font_color = data_colour
end
return {
name = name,
caption = caption,
surfix = surfix,
tooltip = tooltip,
color = data_colour
}
end
--[[ Adds a science pack to the list
element
> icon-"science_pack"
> delta-"science_pack"
>> table
>>> pos-"science_pack" (add_data_label)
>>> neg-"science_pack" (add_data_label)
> net-"science_pack" (add_data_label)
]]
local function generate_science_pack(player,element,science_pack)
local total = Production.get_production_total(player.force, science_pack)
local minute = Production.get_production(player.force, science_pack, defines.flow_precision_index.one_minute)
if total.made > 0 then
element.parent.non_made.visible = false
-- Updates a prodution label to match the current data
local function update_production_label(parent,production_label_data)
local name = production_label_data.name
local tooltip = production_label_data.tooltip
local color = production_label_data.color
local icon_style = 'quick_bar_slot_button'
local flux = Production.get_fluctuations(player.force, science_pack, defines.flow_precision_index.one_minute)
if flux.net > -config.color_flux/2 then
icon_style = 'green_slot_button'
elseif flux.net < -config.color_flux then
icon_style = 'red_slot_button'
elseif minute.made > 0 then
icon_style = 'selected_slot_button'
end
-- Update the production label
local production_label_element = parent[name] and parent[name].label or production_label(parent,production_label_data)
production_label_element.caption = production_label_data.caption
production_label_element.tooltip = production_label_data.tooltip
production_label_element.style.font_color = color
local icon = element['icon-'..science_pack]
-- Update the surfix label
local surfix_element = parent['surfix-'..name]
surfix_element.caption = {'science-info.unit',production_label_data.surfix}
surfix_element.tooltip = tooltip
surfix_element.style.font_color = color
if icon then
icon.style = icon_style
icon.style.height = 55
if icon_style == 'quick_bar_slot_button' then
icon.style.width = 36
Gui.set_padding(icon,0,0,-2,-2)
end
else
icon =
element.add{
name='icon-'..science_pack,
type='sprite-button',
sprite='item/'..science_pack,
tooltip={'item-name.'..science_pack},
style=icon_style
}
icon.style.height = 55
if icon_style == 'quick_bar_slot_button' then
icon.style.width = 36
Gui.set_padding(icon,0,0,-2,-2)
end
end
local delta = element['delta-'..science_pack]
if not delta then
delta =
element.add{
name='delta-'..science_pack,
type='frame',
style='bordered_frame'
}
Gui.set_padding(delta,0,0,3,3)
local delta_table =
delta.add{
name='table',
type='table',
column_count=2
}
Gui.set_padding(delta_table)
end
add_data_label(delta.table,'pos-'..science_pack,minute.made,nil,{'science-info.pos-tooltip',total.made})
add_data_label(delta.table,'neg-'..science_pack,-minute.used,nil,{'science-info.neg-tooltip',total.used})
add_data_label(element,'net-'..science_pack,minute.net,minute.made+minute.used,{'science-info.net-tooltip',total.net})
end
end
--- Updates the eta label that was created with generate_container
local function update_eta(player,element)
if not config.show_eta then return end
--- Adds 4 elements that show the data for a science pack
-- @element science_pack_base
local science_pack_base =
Gui.element(function(_,parent,science_pack_data)
local science_pack = science_pack_data.science_pack
-- Draw the icon for the science pack
local icon_style = science_pack_data.icon_style
local pack_icon =
parent.add{
name = 'icon-'..science_pack,
type = 'sprite-button',
sprite = 'item/'..science_pack,
tooltip = {'item-name.'..science_pack},
style = icon_style
}
-- Change the style of the icon
local pack_icon_style = pack_icon.style
pack_icon_style.height = 55
if icon_style == 'quick_bar_slot_button' then
pack_icon_style.padding = {0,-2}
pack_icon_style.width = 36
end
-- Draw the delta flow
local delta_flow =
parent.add{
name = 'delta-'..science_pack,
type = 'frame',
style = 'bordered_frame'
}
delta_flow.style.padding = {0,3}
-- Draw the delta flow table
local delta_table =
delta_flow.add{
name = 'table',
type = 'table',
column_count = 2
}
delta_table.style.padding = 0
-- Draw the production labels
update_production_label(delta_table,science_pack_data.positive)
update_production_label(delta_table,science_pack_data.negative)
update_production_label(parent,science_pack_data.net)
-- Return the pack icon
return pack_icon
end)
local function get_science_pack_data(player,science_pack)
local force = player.force
-- Check that some packs have been made
local total = Production.get_production_total(force, science_pack)
local minute = Production.get_production(force, science_pack, defines.flow_precision_index.one_minute)
if total.made == 0 then
return
end
-- Get the icon style
local icon_style = 'quick_bar_slot_button'
local flux = Production.get_fluctuations(force, science_pack, defines.flow_precision_index.one_minute)
if minute.net > 0 and flux.net > -config.color_flux/2 then
icon_style = 'green_slot_button'
elseif flux.net < -config.color_flux then
icon_style = 'red_slot_button'
elseif minute.made > 0 then
icon_style = 'selected_slot_button'
end
-- Return the pack data
return {
science_pack = science_pack,
icon_style = icon_style,
positive = get_production_label_data(
'pos-'..science_pack,
{'science-info.pos-tooltip', total.made},
minute.made
),
negative = get_production_label_data(
'neg-'..science_pack,
{'science-info.neg-tooltip', total.used},
-minute.used
),
net = get_production_label_data(
'net-'..science_pack,
{'science-info.net-tooltip', total.net},
minute.net,
minute.made+minute.used
)
}
end
local function update_science_pack(pack_table,science_pack_data)
if not science_pack_data then return end
local science_pack = science_pack_data.science_pack
pack_table.parent.non_made.visible = false
-- Update the icon
local pack_icon = pack_table['icon-'..science_pack] or science_pack_base(pack_table,science_pack_data)
local icon_style = science_pack_data.icon_style
pack_icon.style = icon_style
local pack_icon_style = pack_icon.style
pack_icon_style.height = 55
if icon_style == 'quick_bar_slot_button' then
pack_icon_style.padding = {0,-2}
pack_icon_style.width = 36
end
-- Update the production labels
local delta_table = pack_table['delta-'..science_pack].table
update_production_label(delta_table,science_pack_data.positive)
update_production_label(delta_table,science_pack_data.negative)
update_production_label(pack_table,science_pack_data.net)
end
--- Gets the data that is used with the eta label
local function get_eta_label_data(player)
local force = player.force
-- If there is no current research then return no research
local research = force.current_research
if not research then
element.caption = null_time_short
element.tooltip = null_time_long
return { research = false }
end
else
local progress = force.research_progress
local remaining = research.research_unit_count*(1-progress)
local limit
local stats = player.force.item_production_statistics
for _,ingredient in pairs(research.research_unit_ingredients) do
local pack_name = ingredient.name
local required = ingredient.amount * remaining
local time = Production.get_consumsion_eta(force, pack_name, defines.flow_precision_index.one_minute, required)
if not limit or limit < time then
limit = time
end
end
if not limit or limit == -1 then
element.caption = null_time_short
element.tooltip = null_time_long
else
element.caption = {'science-info.eta-time',format_time(limit,{hours=true,minutes=true,seconds=true,time=true})}
element.tooltip = format_time(limit,{hours=true,minutes=true,seconds=true,long=true})
local limit
local progress = force.research_progress
local remaining = research.research_unit_count*(1-progress)
-- Check for the limiting science pack
for _,ingredient in pairs(research.research_unit_ingredients) do
local pack_name = ingredient.name
local required = ingredient.amount * remaining
local time = Production.get_consumsion_eta(force, pack_name, defines.flow_precision_index.one_minute, required)
if not limit or limit < time then
limit = time
end
end
-- Return the caption and tooltip
return limit and limit > 0 and {
research = true,
caption = format_time(limit,{hours=true,minutes=true,seconds=true,time=true}),
tooltip = format_time(limit,{hours=true,minutes=true,seconds=true,long=true})
} or { research = false }
end
--- Registers the science info
-- @element science_info
local science_info =
Gui.new_left_frame('gui/science-info')
:set_sprites('entity/lab')
:set_direction('vertical')
:set_tooltip{'science-info.main-tooltip'}
:on_creation(function(player,element)
local table, eta = generate_container(element)
for _,science_pack in ipairs(config) do
generate_science_pack(player,table,science_pack)
-- Updates the eta label
local function update_eta_label(element,eta_label_data)
-- If no research selected show null
if not eta_label_data.research then
element.caption = null_time_short
element.tooltip = null_time_long
return
end
update_eta(player,eta)
-- Update the element
element.caption = {'science-info.eta-time',eta_label_data.caption}
element.tooltip = eta_label_data.tooltip
end
--- Main task list container for the left flow
-- @element task_list_container
local science_info_container =
Gui.element(function(event_trigger,parent)
local player = Gui.get_player_from_element(parent)
-- Draw the internal container
local container = Gui.container(parent,event_trigger,200)
-- Draw the header
Gui.header(container, {'science-info.main-caption'}, {'science-info.main-tooltip'})
-- Draw the scroll table for the tasks
local scroll_table = Gui.scroll_table(container,178,4)
-- Draw the no packs label
local no_packs_label =
scroll_table.parent.add{
name = 'non_made',
type = 'label',
caption = {'science-info.no-packs'}
}
-- Change the style of the no packs label
local no_packs_style = no_packs_label.style
no_packs_style.padding = {2,4}
no_packs_style.single_line = false
no_packs_style.width = 200
-- Add the footer and eta
if config.show_eta then
-- Draw the footer
local footer = Gui.footer(container, {'science-info.eta-caption'}, {'science-info.eta-tooltip'}, true)
-- Draw the eta label
local eta_label =
footer.add{
name = 'label',
type = 'label',
caption = null_time_short,
tooltip = null_time_long,
style = 'heading_1_label'
}
-- Update the eta
update_eta_label(eta_label,get_eta_label_data(player))
end
-- Add packs which have been made
for _,science_pack in ipairs(config) do
update_science_pack(scroll_table,get_science_pack_data(player,science_pack))
end
-- Return the exteral container
return container.parent
end)
:on_update(function(player,element)
local container = element.container
local table = container.scroll.table
local eta = container.footer.eta.label
:add_to_left_flow()
for _,science_pack in ipairs(config) do
generate_science_pack(player,table,science_pack)
end
update_eta(player,eta)
--- Button on the top flow used to toggle the task list container
-- @element toggle_left_element
Gui.left_toolbar_button('entity/lab', {'science-info.main-tooltip'}, science_info_container, function(player)
return Roles.player_allowed(player,'gui/science-info')
end)
--- Updates the gui every 1 second
Event.on_nth_tick(60,science_info 'update_all')
Event.on_nth_tick(60,function()
local force_pack_data = {}
local force_eta_data = {}
for _,player in pairs(game.connected_players) do
local force_name = player.force.name
local frame = Gui.get_left_element(player,science_info_container)
local container = frame.container
return science_info
-- Update the science packs
local scroll_table = container.scroll.table
local pack_data = force_pack_data[force_name]
if not pack_data then
-- No data in chache so it needs to be generated
pack_data = {}
force_pack_data[force_name] = pack_data
for _,science_pack in ipairs(config) do
local next_data = get_science_pack_data(player,science_pack)
pack_data[science_pack] = next_data
update_science_pack(scroll_table,next_data)
end
else
-- Data found in chache is no need to generate it
for _,next_data in ipairs(pack_data) do
update_science_pack(scroll_table,next_data)
end
end
-- Update the eta times
if not config.show_eta then return end
local eta_label = container.footer.alignment.label
local eta_data = force_eta_data[force_name]
if not eta_data then
-- No data in chache so it needs to be generated
eta_data = get_eta_label_data(player)
force_eta_data[force_name] = eta_data
update_eta_label(eta_label,eta_data)
else
-- Data found in chache is no need to generate it
update_eta_label(eta_label,eta_data)
end
end
end)

View File

@@ -1,5 +1,5 @@
--[[-- Gui Module - Task List
- Adds a task list to the game which players can add remove and edit items on
- Adds a task list to the game which players can add, remove and edit items on
@gui Task-List
@alias task_list
]]
@@ -11,338 +11,406 @@ local config = require 'config.tasks' --- @dep config.tasks
local format_time,table_keys = ext_require('expcore.common','format_time','table_keys') --- @dep expcore.common
local Tasks = require 'modules.control.tasks' --- @dep modules.control.tasks
-- Styles used for sprite buttons
local Styles = {
sprite20 = Gui.sprite_style(20),
sprite22 = Gui.sprite_style(20, nil, { right_margin = -3 })
}
--- If a player is allowed to use the edit buttons
local function player_allowed_edit(player,task)
local function check_player_permissions(player,task)
if task then
-- When a task is given check if the player can edit it
local allow_edit_task = config.allow_edit_task
-- Check if the player being the last to edit will override existing permisisons
if config.user_can_edit_own_tasks and task.last_edit_name == player.name then
return true
end
else
if config.any_user_can_add_new_task then
-- Check player has permisison based on value in the config
if allow_edit_task == 'all' then
return true
elseif allow_edit_task == 'admin' then
return player.admin
elseif allow_edit_task == 'expcore.roles' then
return Roles.player_allowed(player,config.edit_tasks_role_permission)
end
end
if config.only_admins_can_edit and not player.admin then
-- Return false as all other condidtions have not been met
return false
else
-- When a task is not given check if the player can add a new task
local allow_add_task = config.allow_add_task
-- Check player has permisison based on value in the config
if allow_add_task == 'all' then
return true
elseif allow_add_task == 'admin' then
return player.admin
elseif allow_add_task == 'expcore.roles' then
return Roles.player_allowed(player,config.expcore_roles_add_permission)
end
-- Return false as all other condidtions have not been met
return false
end
if config.edit_tasks_role_permission and not Roles.player_allowed(player,config.edit_tasks_role_permission) then
return false
end
return true
end
--- Button in the header to add a new task
--- Button displayed in the ehader bar, used to add a new task
-- @element add_new_task
local add_new_task =
Gui.new_button()
:set_sprites('utility/add')
:set_tooltip{'task-list.add-tooltip'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
end)
:on_click(function(player,element)
Gui.element{
type = 'sprite-button',
sprite = 'utility/add',
tooltip = {'task-list.add-tooltip'},
style = 'tool_button'
}
:style(Styles.sprite20)
:on_click(function(player,_,_)
Tasks.add_task(player.force.name,nil,player.name)
end)
--- Used to save changes to a task
-- @element confirm_edit
local confirm_edit =
Gui.new_button()
:set_sprites('utility/downloaded')
:set_tooltip{'task-list.confirm-tooltip'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
--- Button displayed next to tasks which the user is can edit, used to start editing a task
-- @element edit_task
local edit_task =
Gui.element{
type = 'sprite-button',
sprite = 'utility/rename_icon_normal',
tooltip = {'task-list.edit-tooltip-none'},
style = 'tool_button'
}
:style(Styles.sprite20)
:on_click(function(player,element,_)
local task_id = element.parent.name:sub(6)
Tasks.set_editing(task_id,player.name,true)
end)
:on_click(function(player,element)
--- Button displayed next to tasks which the user is can edit, used to delete a task from the list
-- @element discard_task
local discard_task =
Gui.element{
type = 'sprite-button',
sprite = 'utility/trash',
tooltip = {'task-list.discard-tooltip'},
style = 'tool_button'
}
:style(Styles.sprite20)
:on_click(function(_,element,_)
local task_id = element.parent.name:sub(6)
Tasks.remove_task(task_id)
end)
--- Set of three elements which make up each row of the task table
-- @element add_task_base
local add_task_base =
Gui.element(function(_,parent,task_id)
-- Add the task number label
local task_number = parent.add{
name = 'count-'..task_id,
type = 'label',
caption = '0)'
}
task_number.style.left_margin = 1
-- Add a flow which will contain the task message and edit buttons
local task_flow = parent.add{ name = task_id, type = 'flow', }
task_flow.style.padding = 0
-- Add the two edit buttons outside the task flow
local edit_flow = Gui.alignment(parent,'edit-'..task_id)
edit_task(edit_flow)
discard_task(edit_flow)
-- Return the task flow as the main element
return task_flow
end)
-- Removes the three elements that are added as part of the task base
local function remove_task_base(parent,task_id)
Gui.destroy_if_valid(parent['count-'..task_id])
Gui.destroy_if_valid(parent['edit-'..task_id])
Gui.destroy_if_valid(parent[task_id])
end
--- Button displayed next to tasks which the user is currently editing, used to save changes
-- @element confirm_edit
local task_editing
local confirm_edit =
Gui.element{
type = 'sprite-button',
sprite = 'utility/downloaded',
tooltip = {'task-list.confirm-tooltip'},
style = 'shortcut_bar_button_green'
}
:style(Styles.sprite22)
:on_click(function(player,element,_)
local task_id = element.parent.name
local new_message = element.parent.task.text
local new_message = element.parent[task_editing.name].text
Tasks.set_editing(task_id,player.name)
Tasks.update_task(task_id,new_message,player.name)
end)
--- Used to cancel any changes you made to a task
--- Button displayed next to tasks which the user is currently editing, used to discard changes
-- @element cancel_edit
local cancel_edit =
Gui.new_button()
:set_sprites('utility/close_black')
:set_tooltip{'task-list.cancel-tooltip'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
end)
:on_click(function(player,element)
Gui.element{
type = 'sprite-button',
sprite = 'utility/close_black',
tooltip = {'task-list.cancel-tooltip'},
style = 'shortcut_bar_button_red'
}
:style(Styles.sprite22)
:on_click(function(player,element,_)
local task_id = element.parent.name
Tasks.set_editing(task_id,player.name)
end)
--- Removes the task from the list
-- @element discard_task
local discard_task =
Gui.new_button()
:set_sprites('utility/trash')
:set_tooltip{'task-list.discard-tooltip'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
--- Editing state for a task, contrins a text field and the two edit buttons
-- @element task_editing
task_editing =
Gui.element(function(event_trigger,parent,task)
local message = task.message
-- Draw the element
local element =
parent.add{
name = event_trigger,
type = 'textfield',
text = message,
clear_and_focus_on_right_click = true
}
-- Add the edit buttons
cancel_edit(parent)
confirm_edit(parent)
-- Return the element
return element
end)
:on_click(function(player,element)
:style{
maximal_width = 110,
height = 20
}
:on_confirmed(function(player,element,_)
local task_id = element.parent.name
Tasks.remove_task(task_id)
local new_message = element.text
Tasks.set_editing(task_id,player.name)
Tasks.update_task(task_id,new_message,player.name)
end)
--- Opens edit mode for the task
-- @element edit_task
local edit_task =
Gui.new_button()
:set_sprites('utility/rename_icon_normal')
:set_tooltip{'task-list.edit-tooltip-none'}
:set_style('tool_button',function(style)
Gui.set_padding_style(style,-2,-2,-2,-2)
style.height = 20
style.width = 20
end)
:on_click(function(player,element)
local task_id = element.parent.name
Tasks.set_editing(task_id,player.name,true)
--- Default state for a task, contains only a label with the task message
-- @element task_label
local task_label =
Gui.element(function(_,parent,task)
local message = task.message
local last_edit_name = task.last_edit_name
local last_edit_time = task.last_edit_time
-- Draw the element
return parent.add{
name = task_editing.name,
type = 'label',
caption = message,
tooltip = {'task-list.last-edit', last_edit_name, format_time(last_edit_time)}
}
end)
:style{
single_line = false,
maximal_width = 150
}
--[[ Generates each task, handles both view and edit mode
element
> count-"task_id"
>> label
> "task_id"
>> task
>> cancel_edit (edit mode)
>> confirm_edit (edit mode)
> edit-"task_id"
>> edit_task
>> discard_task
]]
local function generate_task(player,element,task_id)
--- Updates a task for a player
local function update_task(player,task_table,task_id)
local task = Tasks.get_task(task_id)
local task_ids = Tasks.get_force_task_ids(player.force.name)
local task_number = table.index_of(task_ids, task_id)
-- Task no longer exists so should be removed from the list
if not task then
-- task is nil so remove it from the list
element.parent.no_tasks.visible = #task_ids == 1
Gui.destroy_if_valid(element['count-'..task_id])
Gui.destroy_if_valid(element['edit-'..task_id])
Gui.destroy_if_valid(element[task_id])
else
local message = task.message
local editing = task.curently_editing[player.name]
local last_edit_name = task.last_edit_name
local last_edit_time = task.last_edit_time
element.parent.no_tasks.visible = false
-- if it is not already present then add it now
local task_area = element[task_id]
if not task_area then
-- label to show the task number
element.add{
name='count-'..task_id,
type='label',
caption=task_number..')'
}
-- area which stores the task and buttons
task_area =
element.add{
name=task_id,
type='flow',
}
Gui.set_padding(task_area)
-- if the player can edit then it adds the edit and delete button
local flow = Gui.create_alignment(element,'edit-'..task_id)
local sub_flow = flow.add{type='flow',name=task_id}
edit_task(sub_flow)
discard_task(sub_flow)
end
-- update the number indexes and the current editing players
element['count-'..task_id].caption = task_number..')'
local edit_area = element['edit-'..task_id][task_id]
local players = table_keys(task.editing)
local allowed = player_allowed_edit(player,task)
edit_area.visible = allowed
if #players > 0 then
edit_area[edit_task.name].tooltip = {'task-list.edit-tooltip',table.concat(players,', ')}
else
edit_area[edit_task.name].tooltip = {'task-list.edit-tooltip-none'}
end
-- draws/updates the task area
local element_type = task_area.task and task_area.task.type or nil
if not editing and element_type == 'label' then
-- update the label already present
task_area.task.caption = message
task_area.task.tooltip = {'task-list.last-edit',last_edit_name,format_time(last_edit_time)}
elseif not editing then
-- create the label, view mode
if edit_area then
edit_area[edit_task.name].enabled = true
end
task_area.clear()
local label =
task_area.add{
name='task',
type='label',
caption=message,
tooltip={'task-list.last-edit',last_edit_name,format_time(last_edit_time)}
}
label.style.single_line = false
label.style.maximal_width = 150
elseif editing and element_type ~= 'textfield' then
-- create the text field, edit mode, update it omitted as value is being edited
if edit_area then
edit_area[edit_task.name].enabled = false
end
task_area.clear()
local entry =
task_area.add{
name='task',
type='textfield',
text=message
}
entry.style.maximal_width = 150
entry.style.height = 20
cancel_edit(task_area)
confirm_edit(task_area)
end
task_table.parent.no_tasks.visible = #task_ids == 0
remove_task_base(task_table,task_id)
return
end
-- Get the task flow for this task
local task_flow = task_table[task_id] or add_task_base(task_table,task_id)
task_table.parent.no_tasks.visible = false
task_table['count-'..task_id].caption = task_number..')'
-- Update the edit flow
local edit_flow = task_table['edit-'..task_id]
local player_allowed_edit = check_player_permissions(player,task)
local players_editing = table_keys(task.curently_editing)
local edit_task_element = edit_flow[edit_task.name]
local discard_task_element = edit_flow[discard_task.name]
edit_task_element.visible = player_allowed_edit
discard_task_element.visible = player_allowed_edit
if #players_editing > 0 then
edit_task_element.hovered_sprite = 'utility/warning_icon'
edit_task_element.tooltip = {'task-list.edit-tooltip',table.concat(players_editing,', ')}
else
edit_task_element.hovered_sprite = edit_task_element.sprite
edit_task_element.tooltip = {'task-list.edit-tooltip-none'}
end
-- Check if the player is was editing and/or currently editing
local task_entry = task_flow[task_editing.name] or task_label(task_flow,task)
local player_was_editing = task_entry.type == 'textfield'
local player_is_editing = task.curently_editing[player.name]
-- Update the task flow
if not player_was_editing and not player_is_editing then
-- Update the task message label
local message = task.message
local last_edit_name = task.last_edit_name
local last_edit_time = task.last_edit_time
task_entry.caption = message
task_entry.tooltip = {'task-list.last-edit', last_edit_name, format_time(last_edit_time)}
elseif player_was_editing and not player_is_editing then
-- Player was editing but is no longer, remove text field and add label
edit_task_element.enabled = true
task_flow.clear()
task_label(task_flow,task)
elseif not player_was_editing and player_is_editing then
-- Player was not editing but now is, remove label and add text field
edit_task_element.enabled = false
task_flow.clear()
task_editing(task_flow,task).focus()
task_table.parent.scroll_to_element(task_flow,'top-third')
end
end
--[[ generates the main gui structure
element
> container
>> header
>>> right aligned add_new_task
>> scroll
>>> no_tasks
>>> table
]]
local function generate_container(player,element)
Gui.set_padding(element,2,2,2,2)
element.style.minimal_width = 200
-- Update all the tasks for a player
local function update_all_tasks(player,scroll_table)
local task_ids = Tasks.get_force_task_ids(player.force.name)
if #task_ids > 0 then
for _,task_id in ipairs(task_ids) do
update_task(player,scroll_table,task_id)
end
end
end
-- main container which contains the other elements
local container =
element.add{
name='container',
type='frame',
direction='vertical',
style='window_content_frame_packed'
}
Gui.set_padding(container)
container.style.vertically_stretchable = false
--- Main task list container for the left flow
-- @element task_list_container
local task_list_container =
Gui.element(function(event_trigger,parent)
-- Draw the internal container
local container = Gui.container(parent,event_trigger,200)
-- main header for the gui
local header_area = Gui.create_header(
-- Draw the header
local header = Gui.header(
container,
{'task-list.main-caption'},
{'task-list.sub-tooltip'},
true
)
--- Right aligned button to toggle the section
if player_allowed_edit(player) then
add_new_task(header_area)
end
-- Draw the new task button
local player = Gui.get_player_from_element(parent)
local add_new_task_element = add_new_task(header)
add_new_task_element.visible = check_player_permissions(player)
-- table that stores all the data
local flow_table = Gui.create_scroll_table(container,3,185)
flow_table.draw_horizontal_lines = true
flow_table.vertical_centering = false
flow_table.style.top_cell_padding = 3
flow_table.style.bottom_cell_padding = 3
-- Draw the scroll table for the tasks
local scroll_table = Gui.scroll_table(container,190,3)
scroll_table.draw_horizontal_lines = true
scroll_table.vertical_centering = false
-- message to say that you have no tasks
local non_made =
flow_table.parent.add{
name='no_tasks',
type='label',
caption={'task-list.no-tasks'}
-- Change the style of the scroll table
local scroll_table_style = scroll_table.style
scroll_table_style.top_cell_padding = 3
scroll_table_style.bottom_cell_padding = 3
-- Draw the no tasks label
local no_tasks_label =
scroll_table.parent.add{
name = 'no_tasks',
type = 'label',
caption = {'task-list.no-tasks'}
}
non_made.style.width = 200
non_made.style.single_line = false
return flow_table
end
-- Change the style of the no tasks label
local no_tasks_style = no_tasks_label.style
no_tasks_style.padding = {2,4}
no_tasks_style.single_line = false
no_tasks_style.width = 200
--- Registers the task list
-- @element task_list
local task_list =
Gui.new_left_frame('gui/task-list')
:set_sprites('utility/not_enough_repair_packs_icon')
:set_direction('vertical')
:set_tooltip{'task-list.main-tooltip'}
:set_open_by_default()
:on_creation(function(player,element)
local data_table = generate_container(player,element)
-- Add any existing tasks
local task_ids = Tasks.get_force_task_ids(player.force.name)
for _,task_id in pairs(task_ids) do
generate_task(player,data_table,task_id)
if #task_ids > 0 then
no_tasks_style.visible = false
for _,task_id in ipairs(task_ids) do
update_task(player,scroll_table,task_id)
end
end
-- Return the exteral container
return container.parent
end)
:on_update(function(player,element)
local data_table = element.container.scroll.table
:add_to_left_flow(function(player)
local task_ids = Tasks.get_force_task_ids(player.force.name)
return #task_ids > 0
end)
for _,task_id in pairs(task_ids) do
generate_task(player,data_table,task_id)
end
--- Button on the top flow used to toggle the task list container
-- @element toggle_left_element
Gui.left_toolbar_button('utility/not_enough_repair_packs_icon', {'task-list.main-tooltip'}, task_list_container, function(player)
return Roles.player_allowed(player,'gui/task-list')
end)
--- When a new task is added it will udpate the task list for everyone on that force
Tasks.on_update(function(task,task_id)
local players
Tasks.on_update(function(task,task_id,removed_task)
-- Get the force to update, task is nil when removed
local force
if task then
local force = game.forces[task.force_name]
players = force.connected_players
force = game.forces[task.force_name]
else
players = game.connected_players
force = game.forces[removed_task.force_name]
end
for _,player in pairs(players) do
local frame = task_list:get_frame(player)
local element = frame.container.scroll.table
generate_task(player,element,task_id)
-- Update the task for all the players on the force
local task_ids = Tasks.get_force_task_ids(force.name)
for _,player in pairs(force.connected_players) do
local frame = Gui.get_left_element(player,task_list_container)
local scroll_table = frame.container.scroll.table
-- Update the task that was changed
update_task(player,scroll_table,task_id)
-- Update the numbering of the other tasks if the task was removed
if not task then
for task_number, next_task_id in pairs(task_ids) do
scroll_table['count-'..next_task_id].caption = task_number..')'
end
end
end
end)
--- Update the tasks when the player joins
Event.add(defines.events.on_player_joined_game,task_list 'redraw')
Event.add(defines.events.on_player_joined_game,function(event)
local player = game.players[event.player_index]
local frame = Gui.get_left_element(player,task_list_container)
local scroll_table = frame.container.scroll.table
update_all_tasks(player,scroll_table)
end)
--- Makes sure the right buttons are present when roles change
Event.add(Roles.events.on_role_assigned,task_list 'redraw')
Event.add(Roles.events.on_role_unassigned,task_list 'redraw')
local function role_update_event(event)
local player = game.players[event.player_index]
local container = Gui.get_left_element(player,task_list_container).container
return task_list
-- Update the tasks, incase the user can now edit them
local scroll_table = container.scroll.table
update_all_tasks(player,scroll_table)
-- Update the new task button incase the user can now add them
local add_new_task_element = container.header.alignment[add_new_task.name]
add_new_task_element.visible = check_player_permissions(player)
end
Event.add(Roles.events.on_role_assigned,role_update_event)
Event.add(Roles.events.on_role_unassigned,role_update_event)

File diff suppressed because it is too large Load Diff