diff --git a/config/_file_loader.lua b/config/_file_loader.lua index 0c1b476c..24c135c7 100644 --- a/config/_file_loader.lua +++ b/config/_file_loader.lua @@ -33,6 +33,7 @@ return { 'modules.addons.pollution-grading', 'modules.addons.random-player-colours', -- GUI + 'modules.gui.player-list', 'modules.commands.debug', -- Config Files 'config.expcore-commands.auth_admin', -- commands tagged with admin_only are blocked for non admins diff --git a/config/action_buttons.lua b/config/action_buttons.lua new file mode 100644 index 00000000..402d6481 --- /dev/null +++ b/config/action_buttons.lua @@ -0,0 +1,42 @@ +local Gui = require 'expcore.gui' +local Roles = require 'expcore.roles' +local Store = require 'expcore.store' +local Game = require 'utils.game' + +local action_player_store = 'gui.left.player-list.action-player' + +local function tool_button_style(style) + Gui.set_padding_style(style,-1,-1,-1,-1) + style.height = 28 + style.width = 28 +end + +local function auth_lower_role(player,action_player) + local player_highest = Roles.get_player_highest_role(player) + local action_player_highest = Roles.get_player_highest_role(action_player) + if player_highest.index < action_player_highest.index then + return true + end +end + +local function get_action_player(player) + local action_player_name = Store.get_child(action_player_store,player.name) + local action_player = Game.get_player_from_any(action_player_name) + return action_player +end + +local report_player = +Gui.new_button() +:set_sprites('utility/warning') +:set_tooltip('Report player') +:set_style('tool_button',tool_button_style) +:on_click(function(player,element) + local action_player = get_action_player(player) +end) + +return { + ['command/report'] = { + auth=auth_lower_role, + report_player + } +} \ No newline at end of file diff --git a/config/roles.lua b/config/roles.lua index b1863ea7..6ea8ddc9 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -181,6 +181,7 @@ local default = Roles.new_role('Guest','') 'command/chelp', 'command/list-roles', 'command/report', + 'gui/player-list', } --- Jail role diff --git a/expcore/gui.lua b/expcore/gui.lua index 5831acf8..70f0ffc3 100644 --- a/expcore/gui.lua +++ b/expcore/gui.lua @@ -13,6 +13,8 @@ local Gui = require 'expcore.gui.core' Gui._prototype:debug_name(name) --- Sets a debug alias for the define Gui._prototype:set_caption(caption) --- Sets the caption for the element define Gui._prototype:set_tooltip(tooltip) --- Sets the tooltip for the element define + Gui._prototype:set_style(style,callback) --- Sets the style for the element define + Gui._prototype:set_embeded_flow(state) --- Sets the element to be drawn inside a nameless flow, can be given a name using a function Gui._prototype:on_element_update(callback) --- Add a hander to run on the general value update event, different classes will handle this event differently Gui._prototype:set_pre_authenticator(callback) --- Sets an authenticator that blocks the draw function if check fails @@ -34,6 +36,7 @@ local Gui = require 'expcore.gui.core' Gui.toggle_enable(element) --- Will toggle the enabled state of an element Gui.toggle_visible(element) --- Will toggle the visiblity of an element + Gui.set_padding(element,up,down,left,right) --- Sets the padding for a gui element ]] local Instances = require 'expcore.gui.instances' @@ -195,6 +198,7 @@ Gui.classes.left_frames = LeftFrames LeftFrames.new_frame(permision_name) --- Creates a new left frame define LeftFrames._prototype:set_open_by_default(state) --- Sets if the frame is visible when a player joins, can also be a function to return a boolean + LeftFrames._prototype:set_direction(direction) --- Sets the direction of the frame, either vertical or horizontal LeftFrames._prototype:get_frame(player) --- Gets the frame for this define from the left frame flow LeftFrames._prototype:is_open(player) --- Returns if the player currently has this define visible LeftFrames._prototype:toggle(player) --- Toggles the visiblty of the left frame diff --git a/expcore/gui/core.lua b/expcore/gui/core.lua index 07d7e8f7..8eb407cf 100644 --- a/expcore/gui/core.lua +++ b/expcore/gui/core.lua @@ -128,6 +128,8 @@ Gui._prototype:debug_name(name) --- Sets a debug alias for the define Gui._prototype:set_caption(caption) --- Sets the caption for the element define Gui._prototype:set_tooltip(tooltip) --- Sets the tooltip for the element define + Gui._prototype:set_style(style,callback) --- Sets the style for the element define + Gui._prototype:set_embeded_flow(state) --- Sets the element to be drawn inside a nameless flow, can be given a name using a function Gui._prototype:on_element_update(callback) --- Add a hander to run on the general value update event, different classes will handle this event differently Gui._prototype:set_pre_authenticator(callback) --- Sets an authenticator that blocks the draw function if check fails @@ -149,6 +151,8 @@ Gui.toggle_enable(element) --- Will toggle the enabled state of an element Gui.toggle_visible(element) --- Will toggle the visiblity of an element + Gui.set_padding(element,up,down,left,right) --- Sets the padding for a gui element + Gui.set_padding_style(style,up,down,left,right) --- Sets the padding for a gui style ]] local Gui = require 'utils.gui' local Game = require 'utils.game' @@ -316,6 +320,28 @@ function Gui._prototype:set_tooltip(tooltip) return self end +--- Sets the style for the element define +-- @tparam style string the style that will be used for this element when drawn +-- @tapram[opt] callback function function is called when element is drawn to alter its style +-- @treturn self the element define to allow chaining +function Gui._prototype:set_style(style,callback) + self.draw_data.style = style + self.events.on_style = callback + return self +end + +--- Sets the element to be drawn inside a nameless flow, can be given a name using a function +-- @tparam state ?boolean|function when true a padless flow is created to contain the element +-- @treturn self the element define to allow chaining +function Gui._prototype:set_embeded_flow(state) + if state == false or type(state) == 'function' then + self.embeded_flow = state + else + self.embeded_flow = true + end + return self +end + --- Sets an authenticator that blocks the draw function if check fails -- @tparam callback function the function that will be ran to test if the element should be drawn or not -- callback param - player LuaPlayer - the player that the element is being drawn to @@ -358,8 +384,21 @@ function Gui._prototype:draw_to(element,...) if not self.pre_authenticator(player,self.name) then return end end + if self.embeded_flow then + local embeded_name + if type(self.embeded_flow) == 'function' then + embeded_name = self.embeded_flow(element,...) + end + element = element.add{type='flow',name=embeded_name} + Gui.set_padding(element) + end + local new_element = element.add(self.draw_data) + if self.events.on_style then + self.events.on_style(new_element.style) + end + if self.post_authenticator then new_element.enabled = self.post_authenticator(player,self.name) end @@ -510,4 +549,17 @@ function Gui.set_padding(element,up,down,left,right) style.right_padding = right or 0 end +--- Sets the padding for a gui style +-- @tparam element LuaStyle the element to set the padding for +-- @tparam[opt=0] up number the amount of padding on the top +-- @tparam[opt=0] down number the amount of padding on the bottom +-- @tparam[opt=0] left number the amount of padding on the left +-- @tparam[opt=0] right number the amount of padding on the right +function Gui.set_padding_style(style,up,down,left,right) + style.top_padding = up or 0 + style.bottom_padding = down or 0 + style.left_padding = left or 0 + style.right_padding = right or 0 +end + return Gui \ No newline at end of file diff --git a/expcore/gui/left.lua b/expcore/gui/left.lua index fb62761c..63efb267 100644 --- a/expcore/gui/left.lua +++ b/expcore/gui/left.lua @@ -33,6 +33,7 @@ LeftFrames.new_frame(permision_name) --- Creates a new left frame define LeftFrames._prototype:set_open_by_default(state) --- Sets if the frame is visible when a player joins, can also be a function to return a boolean + LeftFrames._prototype:set_direction(direction) --- Sets the direction of the frame, either vertical or horizontal LeftFrames._prototype:get_frame(player) --- Gets the frame for this define from the left frame flow LeftFrames._prototype:is_open(player) --- Returns if the player currently has this define visible LeftFrames._prototype:toggle(player) --- Toggles the visiblty of the left frame @@ -159,6 +160,13 @@ function LeftFrames._prototype:set_open_by_default(state) return self end +--- Sets the direction of the frame, either vertical or horizontal +-- @tparam direction string the direction to have the elements be added to thef frame +function LeftFrames._prototype:set_direction(direction) + self.direction = direction + return self +end + --- Gets the frame for this define from the left frame flow -- @tparam player LuaPlayer the player to get the frame of -- @treturn LuaGuiElement the frame in the left frame flow for this define @@ -212,7 +220,7 @@ end -- @tparam player LuaPlayer the player to update the frame of function LeftFrames._prototype:redraw(player) local frame = self:get_frame(player) - frame.claer() + frame.clear() if self.events.on_draw then self.events.on_draw(player,frame) end @@ -263,7 +271,8 @@ Event.add(defines.events.on_player_created,function(event) for _,define in pairs(LeftFrames.frames) do local frame = flow.add{ type='frame', - name=define.name + name=define.name, + direction=define.direction } if define.events.on_draw then diff --git a/locale/en/expcore.cfg b/locale/en/expcore.cfg index 58830616..ddde52d4 100644 --- a/locale/en/expcore.cfg +++ b/locale/en/expcore.cfg @@ -1,6 +1,10 @@ time-symbol-days-short=__1__d color-tag=[color=__1__]__2__[/color] +[time-format] +simple-format-tagged=__1__ __2__ +simple-format-div=__1__:__2__ + [expcore-commands] unauthorized=Unauthorized, Access is denied due to invalid credentials reject-string-options=Invalid Option, Must be one of: __1__ diff --git a/modules/gui/player-list.lua b/modules/gui/player-list.lua new file mode 100644 index 00000000..a434f1eb --- /dev/null +++ b/modules/gui/player-list.lua @@ -0,0 +1,242 @@ +--- Gui left frame define for a player list +local Gui = require 'expcore.gui' +local Roles = require 'expcore.roles' +local Store = require 'expcore.store' +local Game = require 'utils.game' +local Event = require 'utils.event' +local format_time = ext_require('expcore.common','format_time') +local config = require 'config.action_buttons' +local Colors = require 'resources.color_presets' + +local action_player_store = 'gui.left.player-list.action-player' + +--- Button used to open the action bar +local open_action_bar = +Gui.new_button() +:set_sprites('utility/expand_dots_white') +:set_tooltip('Options') +:set_embeded_flow(function(element,action_player_name) + return action_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) + Store.set_child(action_player_store,player.name,element.parent.name) +end) + +--- Button used to close the action bar +local close_action_bar = +Gui.new_button() +:set_sprites('utility/close_black') +:set_tooltip('Close options') +: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) + Store.set_child(action_player_store,player.name,nil) +end) + +--[[ Creates the main gui areas for the player list + element + > container + >> scroll + >>> table + >> action_bar +]] +local function generate_container(element) + Gui.set_padding(element,2,2,2,2) + element.style.minimal_width = 200 + + -- 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) + + -- a scroll bar which allows 8 players to be seen at once + local list_scroll = + container.add{ + name='scroll', + type='scroll-pane', + direction='vertical', + horizontal_scroll_policy='never', + vertical_scroll_policy='auto-and-reserve-space' + } + Gui.set_padding(list_scroll,1,1,2,2) + list_scroll.style.horizontally_stretchable = true + list_scroll.style.maximal_height = 200 + + -- 3 wide table to contain: action button, player name, and play time + local list_table = + list_scroll.add{ + name='table', + type='table', + column_count=3 + } + Gui.set_padding(list_table) + list_table.style.horizontally_stretchable = true + list_table.style.vertical_align = 'center' + list_table.style.cell_padding = 0 + + -- action bar which contains the different action buttons + local action_bar = + container.add{ + name='action_bar', + type='frame', + style='subfooter_frame' + } + Gui.set_padding(action_bar,1,1,3,3) + action_bar.style.horizontally_stretchable = true + action_bar.style.height = 35 + + return list_table, action_bar +end + +--- Adds buttons and permission flows to the action bar +local function generate_action_bar(player,element) + close_action_bar(element) + local action_player = Store.get_child(action_player_store,player.name) + + for action_name,buttons in pairs(config) do + local permission_flow = + element.add{ + type='flow', + name=action_name + } + + for _,button in ipairs(buttons) do + button(permission_flow) + end + + if not Roles.player_allowed(player,action_name) then + permission_flow.visible = false + end + + if buttons.auth and action_player and not buttons.auth(player,action_player) then + permission_flow.visible = false + end + end + + if not action_player then + element.visible = false + end +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 + local action_player = Store.get_child(action_player_store,player.name) + + if not action_player then + element.visible = false + else + element.visible = true + for action_name,buttons in pairs(config) do + if buttons.auth and not buttons.auth(player,action_player) then + element[action_name].visible = false + else + element[action_name].visible = true + end + end + end +end + +local function add_player(list_table,player,role_name) + open_action_bar(list_table,player.name) + + -- player name with the tooltip of their highest role and in they colour + local player_name = + list_table.add{ + type='label', + caption=player.name, + tooltip=role_name + } + Gui.set_padding(player_name,0,0,0,2) + player_name.style.font_color = player.chat_color + + -- flow which allows right align for the play time + local time_flow = + list_table.add{ + type='flow' + } + Gui.set_padding(time_flow) + time_flow.style.horizontal_align = 'right' + time_flow.style.horizontally_stretchable = true + + -- time given in Xh Ym and is right aligned + local time = + time_flow.add{ + type='label', + caption=format_time(player.online_time) + } + Gui.set_padding(time) +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, + online_time=math.random(0,game.tick), + chat_color=table.get_random_dictionary_entry(Colors) + },role_name) + end +end + +--- Registers the player list +local player_list = +Gui.new_left_frame('gui/player-list') +:set_sprites('entity/character') +:set_open_by_default() +:set_direction('vertical') +:on_draw(function(player,element) + local list_table,action_bar = generate_container(element) + generate_action_bar(player,action_bar) + + local players = {} + for _,next_player in pairs(game.connected_players) do + local highest_role = Roles.get_player_highest_role(next_player) + if not players[highest_role.name] then + players[highest_role.name] = {} + end + table.insert(players[highest_role.name],next_player) + end + + 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) + end + end + end + + add_fake_players(list_table,20) +end) + +player_list_name = player_list:uid() + +--- When the action player is changed the action bar will update +Store.register(action_player_store,function(value,category) + local player = Game.get_player_from_any(category) + update_action_bar(player) +end) + +--- Many events which trigger the gui to be re drawn +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.player_role_assigned,player_list 'redraw_all') +Event.add(Roles.player_role_unassigned,player_list 'redraw_all') + +return player_list \ No newline at end of file