diff --git a/config/file_loader.lua b/config/file_loader.lua index 7f1d6b18..744c058c 100644 --- a/config/file_loader.lua +++ b/config/file_loader.lua @@ -32,6 +32,8 @@ return { 'modules.addons.scorched-earth', 'modules.addons.pollution-grading', 'modules.addons.random-player-colours', + -- GUI + 'modules.commands.debug', -- Config Files 'config.command_auth_admin', -- commands tagged with admin_only are blocked for non admins 'config.command_auth_roles', -- commands must be allowed via the role config diff --git a/config/roles.lua b/config/roles.lua index 0383bd85..e5ac8e7c 100644 --- a/config/roles.lua +++ b/config/roles.lua @@ -44,6 +44,7 @@ Roles.new_role('Senior Administrator','SAdmin') :set_parent('Administrator') :allow{ 'command/interface', + 'command/debug', 'command/toggle-cheat-mode' } diff --git a/expcore/Gui/buttons.lua b/expcore/Gui/buttons.lua index 6dff22e2..408b307b 100644 --- a/expcore/Gui/buttons.lua +++ b/expcore/Gui/buttons.lua @@ -23,12 +23,13 @@ function Button.new_button(name) local uid = Gui.uid_name() local self = setmetatable({ name=uid, - clean_name=name + clean_name=name, + _draw={ + name=uid, + style=mod_gui.button_style, + type='button' + } },{__index=Button._prototype}) - - self._draw.name = uid - self._draw.style = mod_gui.button_style - self._draw.type = 'button' Button.config[uid] = self if name then diff --git a/expcore/Gui/checkboxs.lua b/expcore/Gui/checkboxs.lua index 3ef2a098..57493319 100644 --- a/expcore/Gui/checkboxs.lua +++ b/expcore/Gui/checkboxs.lua @@ -30,9 +30,10 @@ end local function get_instances(checkbox,category) if not Checkbox.instances[checkbox.name] then return end - local instances = Checkbox.instances + local instances = Checkbox.instances[checkbox.name] if checkbox.categorize then - instances = instances[category] + if not instances[category] then instances[category] = {} end + return instances[category] end return instances end @@ -43,9 +44,12 @@ function Checkbox.new_checkbox(name) local self = setmetatable({ name=uid, clean_name=name, + _draw={ + name=uid, + type='checkbox', + state=false + } },{__index=Checkbox._prototype_checkbox}) - self._draw.name = uid - self._draw.type = 'checkbox' self._post_draw = function(element) local category = self.categorize and self.categorize(element) or nil @@ -53,6 +57,8 @@ function Checkbox.new_checkbox(name) if instances then table.insert(instances,element) end + local state = self:get_store_state(category) + if state then element.state = true end end Checkbox.config[uid] = self @@ -66,7 +72,7 @@ function Checkbox.new_checkbox(name) local element = event.element if self.store then if self.categorize then - Store.set_chlid(self.store,self.categorize(element),element.state) + Store.set_child(self.store,self.categorize(element),element.state) else Store.set(self.store,element.state) end @@ -87,11 +93,12 @@ function Checkbox._prototype_checkbox:add_store(categorize) if self.store then return end self.store = get_store_location(self) self.categorize = categorize + Checkbox.instances[self.name]={} Store.register(self.store,function(value,category) local instances = get_instances(self,category) if instances then for k,element in pairs(instances) do - if element.valid then + if element and element.valid then element.state = value if self._on_state_change then local player = Game.get_player_by_index(element.player_index) @@ -109,7 +116,7 @@ end function Checkbox._prototype_checkbox:get_store_state(category) if not self.store then return end if self.categorize then - return Store.get_chlid(self.store,category) + return Store.get_child(self.store,category) else return Store.get(self.store) end @@ -119,9 +126,9 @@ function Checkbox._prototype_checkbox:set_store_state(category,state) if not self.store then return end state = not not state if self.categorize then - return Store.set_chlid(self.store,category,state) + return Store.set_child(self.store,category,state) else - return Store.set(self.store,state) + return Store.set(self.store,category) end end @@ -151,11 +158,11 @@ function Checkbox._prototype_radiobutton:add_store(categorize) end -function Checkbox._prototype_radiobutton:get_store_value(category) +function Checkbox._prototype_radiobutton:get_store_state(category) end -function Checkbox._prototype_radiobutton:set_store_value(category,value) +function Checkbox._prototype_radiobutton:set_store_state(category,value) end @@ -171,12 +178,14 @@ function Checkbox._prototype_radiobutton:on_state_change(callback) end -function Checkbox.get_stored_value(name,category) - +function Checkbox.get_stored_state(name,category) + local checkbox = get_config(name) + return checkbox:get_store_state(category) end -function Checkbox.set_stored_value(name,category,value) - +function Checkbox.set_stored_state(name,category,value) + local checkbox = get_config(name) + return checkbox:set_store_state(category,value) end return Checkbox \ No newline at end of file diff --git a/expcore/Gui/core.lua b/expcore/Gui/core.lua index 0a1a21a8..c3328425 100644 --- a/expcore/Gui/core.lua +++ b/expcore/Gui/core.lua @@ -1,26 +1,27 @@ local Gui = require 'utils.gui' +local Game = require 'utils.game' -Gui._prototype = {_draw={}} +Gui._prototype = {} Gui.inputs = {} Gui.structure = {} Gui.outputs = {} function Gui._extend_prototype(tbl) for k,v in pairs(Gui._prototype) do - if not tbl[k] then tbl[k] = table.deep_copy(v) end + if not tbl[k] then tbl[k] = v end end return tbl end --- Sets the caption for the element config function Gui._prototype:set_caption(caption) - self.caption = caption + self._draw.caption = caption return self end --- Sets the tooltip for the element config function Gui._prototype:set_tooltip(tooltip) - self.tooltip = tooltip + self._draw.tooltip = tooltip return self end @@ -44,13 +45,14 @@ end --- Draws the element using what is in the _draw table, allows use of authenticator if present function Gui._prototype:draw_to(element) - if element.children[self.name] then return end + if element[self.name] then return end + local player = Game.get_player_by_index(element.player_index) if self.pre_authenticator then - if not self.pre_authenticator(element.player,self.clean_name or self.name) then return end + if not self.pre_authenticator(player,self.clean_name or self.name) then return end end local _element = element.add(self._draw) if self.authenticator then - _element.enabled = not not self.authenticator(element.player,self.clean_name or self.name) + _element.enabled = not not self.authenticator(player,self.clean_name or self.name) end if self._post_draw then self._post_draw(_element) end return _element diff --git a/expcore/Gui/test.lua b/expcore/Gui/test.lua index b4286087..393b3813 100644 --- a/expcore/Gui/test.lua +++ b/expcore/Gui/test.lua @@ -1,4 +1,10 @@ local Gui = require 'expcore.gui' +local format_chat_colour = ext_require('expcore.common','format_chat_colour') +local Colors = require 'resources.color_presets' +local Game = require 'utils.game' +local clean_stack_trace = ext_require('modules.commands.interface','clean_stack_trace') + +local tests = {} Gui.new_toolbar_button('click-1') :set_authenticator(function(player,button_name) @@ -24,4 +30,83 @@ Gui.new_toolbar_button('click-3') end) :on_click(function(player,element,event) player.print('CLICK 3') +end) + +Gui.new_toolbar_button('gui-test-open') +:set_caption('Open Test Gui') +:set_authenticator(function(player,button_name) + return global.show_test_gui +end) +:on_click(function(player,_element,event) + if player.gui.center.TestGui then player.gui.center.TestGui.destroy() return end + local frame = player.gui.center.add{type='frame',caption='Gui Test',name='TestGui'} + frame = frame.add{type='table',column_count=5} + for key,element in pairs(tests) do + local success,err = pcall(element.draw_to,element,frame) + if success then + player.print('Drawing: '..key..format_chat_colour(' SUCCESS',Colors.green)) + else + player.print('Drawing: '..key..format_chat_colour(' FAIL',Colors.red)..' '..clean_stack_trace(err)) + end + end +end) + +tests['Button no display'] = Gui.new_button('test button no display') +:on_click(function(player,element,event) + player.print('Button no display') + global.test_auth_button = not global.test_auth_button + player.print('Auth Button auth state: '..tostring(global.test_auth_button)) +end) + +tests['Button caption'] = Gui.new_button('test button caption') +:set_caption('Button Caption') +:on_click(function(player,element,event) + player.print('Button caption') +end) + +tests['Button icon'] = Gui.new_button('test Bbutton icon') +:set_sprites('utility/warning_icon','utility/warning','utility/warning_white') +:on_click(function(player,element,event) + player.print('Button icon') +end) + +tests['Button auth'] = Gui.new_button('test button auth') +:set_authenticator(function(player,button_name) + return global.test_auth_button +end) +:on_click(function(player,element,event) + player.print('Button auth') +end) + +tests['Checkbox local'] = Gui.new_checkbox('test checkbox local') +:set_caption('Checkbox Local') +:on_state_change(function(player,element) + player.print('Checkbox local: '..tostring(element.state)) +end) + +tests['Checkbox store game'] = Gui.new_checkbox('test checkbox store game') +:set_caption('Checkbox Store Game') +:add_store() +:on_state_change(function(player,element) + player.print('Checkbox store game: '..tostring(element.state)) +end) + +tests['Checkbox store player'] = Gui.new_checkbox('test checkbox store player') +:set_caption('Checkbox Store Player') +:add_store(function(element) + local player = Game.get_player_by_index(element.player_index) + return player.name +end) +:on_state_change(function(player,element) + player.print('Checkbox store player: '..tostring(element.state)) +end) + +tests['Checkbox store force'] = Gui.new_checkbox('test checkbox store force') +:set_caption('Checkbox Store Force') +:add_store(function(element) + local player = Game.get_player_by_index(element.player_index) + return player.force.name +end) +:on_state_change(function(player,element) + player.print('Checkbox store force: '..tostring(element.state)) end) \ No newline at end of file diff --git a/expcore/gui.lua b/expcore/gui.lua index f7945442..0c33e34b 100644 --- a/expcore/gui.lua +++ b/expcore/gui.lua @@ -1,6 +1,4 @@ -- This file is used to require all the different elements of the gui module -local opt_require = ext_require('expcore.common','opt_require') - local Gui = require('./gui/core') local Buttons = require('./gui/buttons') @@ -12,13 +10,8 @@ Gui.new_toolbar_button = Toolbar.new_button Gui.add_button_to_toolbar = Toolbar.add_button Gui.structure.toolbar = Toolbar ---[[local Checkboxs = opt_require('./gui/checkboxs') +local Checkboxs = require('./gui/checkboxs') Gui.new_checkbox = Checkboxs.new_checkbox -Gui.new_radiobutton = Checkboxs.new_radiobutton Gui.inputs.checkboxs = Checkboxs -local TextEntry = opt_require('./gui/text') -Gui.new_text_entry = TextEntry.new_text_entry -Gui.inputs.text_entrys = TextEntry -]] return Gui \ No newline at end of file diff --git a/expcore/locale/en.cfg b/expcore/locale/en.cfg index 82d68681..931ce162 100644 --- a/expcore/locale/en.cfg +++ b/expcore/locale/en.cfg @@ -24,4 +24,9 @@ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ game-message-unassign=__1__ has been unassigned from __2__ by __3__ reject-role=Invalid Role Name. -reject-player-role=Player has a higher role. \ No newline at end of file +reject-player-role=Player has a higher role. + +[gui_util] +button_tooltip=Shows / hides the Toolbar Gui buttons. + +[expcore-gui] diff --git a/expcore/store.lua b/expcore/store.lua index 822e3a65..f9e31f30 100644 --- a/expcore/store.lua +++ b/expcore/store.lua @@ -76,8 +76,8 @@ end) ]] -local Global = require 'util.global' -local Event = require 'util.event' +local Global = require 'utils.global' +local Event = require 'utils.event' local write_json = ext_require('expcore.common','write_json','table_keys') local Store = { @@ -169,7 +169,7 @@ end -- @tparam value any the new value to set at the location, value may be reverted if there is a watch callback -- @treturn boolean true if it was successful function Store.set(location,value) - if not Store.callbacks[location] and not no_error then + if not Store.callbacks[location] then return error('Location is not registered', 2) end @@ -193,7 +193,7 @@ end function Store.get_children(location) local store = Store.get(location) - if type(store) ~= 'table' and table ~= nil then + if type(store) ~= 'table' and store ~= nil then return error('Location has a non table value', 2) end @@ -207,11 +207,11 @@ end function Store.get_child(location,child) local store = Store.get(location) - if type(store) ~= 'table' and table ~= nil then + if type(store) ~= 'table' and store ~= nil then return error('Location has a non table value', 2) end - return store[child] + return store and store[child] end --- Sets the value of the chlid to a location, children can be added and removed during runtime @@ -224,7 +224,7 @@ end function Store.set_child(location,child,value) local store = Store.get(location) - if type(store) ~= 'table' and table ~= nil then + if type(store) ~= 'table' and store ~= nil then return error('Location has a non table value', 2) end @@ -257,14 +257,18 @@ Event.add(defines.events.on_tick,function() if not success then table.insert(errors,store_new) else - if store_old ~= store_new then + if type(store_old) ~= type(store_new) + or type(store_old) == 'table' and not table.compare(store_new,store_new) + or store_old ~= store_new then Store.data[location] = store_new Store.callbacks[location](store_new) end end end - error(errors) + if #errors > 0 then + error(table.concat(errors,'; ')) + end end) return Store \ No newline at end of file diff --git a/locale/en/expcore.cfg b/locale/en/expcore.cfg index e725bd91..931ce162 100644 --- a/locale/en/expcore.cfg +++ b/locale/en/expcore.cfg @@ -1,5 +1,4 @@ time-symbol-days-short=__1__d -color-tag=[color=__1__]__2__[/color] [expcore-commands] unauthorized=Unauthorized, Access is denied due to invalid credentials @@ -25,4 +24,9 @@ error-log-format-promote=[ERROR] rolePromote/__1__ :: __2__ game-message-assign=__1__ has been assigned to __2__ by __3__ game-message-unassign=__1__ has been unassigned from __2__ by __3__ reject-role=Invalid Role Name. -reject-player-role=Player has a higher role. \ No newline at end of file +reject-player-role=Player has a higher role. + +[gui_util] +button_tooltip=Shows / hides the Toolbar Gui buttons. + +[expcore-gui] diff --git a/modules/commands/debug.lua b/modules/commands/debug.lua new file mode 100644 index 00000000..5e9b1496 --- /dev/null +++ b/modules/commands/debug.lua @@ -0,0 +1,7 @@ +local DebugView = require 'modules.gui.debug.main_view' +local Commands = require 'expcore.commands' + +Commands.new_command('debug','Opens the debug pannel for viewing tables.') +:register(function(player,raw) + DebugView.open_dubug(player) +end) \ No newline at end of file diff --git a/modules/commands/interface.lua b/modules/commands/interface.lua index 59138617..e4ba5ec3 100644 --- a/modules/commands/interface.lua +++ b/modules/commands/interface.lua @@ -96,5 +96,6 @@ add_interface_callback('tile',function(player) return player.surface.get_tile(pl return { add_interface_callback=add_interface_callback, interface_env=interface_env, - interface_callbacks=interface_callbacks + interface_callbacks=interface_callbacks, + clean_stack_trace=function(str) return str:gsub('%.%.%..-/temp/currently%-playing','') end } \ No newline at end of file diff --git a/modules/gui/debug/_g_view.lua b/modules/gui/debug/_g_view.lua new file mode 100644 index 00000000..3cf4bbd2 --- /dev/null +++ b/modules/gui/debug/_g_view.lua @@ -0,0 +1,114 @@ +local Gui = require 'utils.gui' +local Model = require 'modules.gui.debug.model' +local Color = require 'resources.color_presets' + +local dump = Model.dump + +local Public = {} + +local ignore = { + _G = true, + assert = true, + collectgarbage = true, + error = true, + getmetatable = true, + ipairs = true, + load = true, + loadstring = true, + next = true, + pairs = true, + pcall = true, + print = true, + rawequal = true, + rawlen = true, + rawget = true, + rawset = true, + select = true, + setmetatable = true, + tonumber = true, + tostring = true, + type = true, + xpcall = true, + _VERSION = true, + module = true, + require = true, + package = true, + unpack = true, + table = true, + string = true, + bit32 = true, + math = true, + debug = true, + serpent = true, + log = true, + table_size = true, + global = true, + remote = true, + commands = true, + settings = true, + rcon = true, + script = true, + util = true, + mod_gui = true, + game = true, + rendering = true +} + +local header_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local right_panel_name = Gui.uid_name() + +Public.name = '_G' + +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 key, value in pairs(_G) do + if not ignore[key] then + local header = + left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)} + Gui.set_data(header, value) + end + end + + local right_panel = main_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 + + Gui.set_data(left_panel, {right_panel = right_panel, selected_header = nil}) +end + +Gui.on_click( + header_name, + function(event) + local element = event.element + local value = Gui.get_data(element) + + local left_panel = element.parent.parent + local left_panel_data = Gui.get_data(left_panel) + local right_panel = left_panel_data.right_panel + local selected_header = left_panel_data.selected_header + + if selected_header then + selected_header.style.font_color = Color.white + end + + element.style.font_color = Color.orange + left_panel_data.selected_header = element + + local content = dump(value) + right_panel.text = content + end +) + +return Public diff --git a/modules/gui/debug/event_view.lua b/modules/gui/debug/event_view.lua new file mode 100644 index 00000000..27f5ac82 --- /dev/null +++ b/modules/gui/debug/event_view.lua @@ -0,0 +1,117 @@ +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 + +local events = defines.events + +-- Constants +local events_to_keep = 10 + +-- Local vars +local Public = { + name = 'Events' +} +local name_lookup = {} + +-- GUI names +local checkbox_name = Gui.uid_name() + +-- global tables +local enabled = {} +local last_events = {} +global.debug_event_view = { + enabled = enabled, + last_events = last_events +} + +function Public.on_open_debug() + local tbl = global.debug_event_view + if tbl then + enabled = tbl.enabled + last_events = tbl.last_events + else + enabled = {} + last_events = {} + + global.debug_event_view = { + enabled = enabled, + last_events = last_events + } + end + + Public.on_open_debug = nil +end + +-- Local functions +local function event_callback(event) + local id = event.name + if not enabled[id] then + return + end + local name = name_lookup[id] + + if not last_events[name] then + last_events[name] = {} + end + + insert(last_events[name], 1, event) + last_events[name][events_to_keep + 1] = nil + event.name = nil + + local str = format('%s (id = %s): %s', name, id, Model.dump(event)) + game.print(str) + log(str) +end + +local function on_gui_checked_state_changed(event) + local element = event.element + local name = element.caption + local id = events[name] + local state = element.state and true or false + element.state = state + if state then + enabled[id] = true + else + enabled[id] = false + end +end + +-- GUI + +-- Create a table with events sorted by their names +local grid_builder = {} +for name, _ in pairs(events) do + grid_builder[#grid_builder + 1] = name +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}) + + 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 + } + end +end + +Gui.on_checked_state_changed(checkbox_name, on_gui_checked_state_changed) + +-- Event registers (TODO: turn to removable hooks.. maybe) +for name, id in pairs(events) do + name_lookup[id] = name + Event.add(id, event_callback) +end + +return Public diff --git a/modules/gui/debug/global_view.lua b/modules/gui/debug/global_view.lua new file mode 100644 index 00000000..6a888377 --- /dev/null +++ b/modules/gui/debug/global_view.lua @@ -0,0 +1,133 @@ +local Gui = require 'utils.gui' +local Model = require 'modules.gui.debug.model' +local Color = require 'resources.color_presets' + +local dump = Model.dump +local dump_text = Model.dump_text +local concat = table.concat + +local Public = {} + +local ignore = {tokens = true} + +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 = 'global' + +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 key, _ in pairs(global) do + if not ignore[key] then + local header = + left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)} + Gui.set_data(header, key) + end + 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, + selected_token_id = 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 key = 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 {"global['", key, "']"} + input_text_box.style.font_color = Color.black + + local content = dump(global[key]) 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 diff --git a/modules/gui/debug/main_view.lua b/modules/gui/debug/main_view.lua new file mode 100644 index 00000000..bac9ffb5 --- /dev/null +++ b/modules/gui/debug/main_view.lua @@ -0,0 +1,103 @@ +local Gui = require 'utils.gui' +local Color = require 'resources.color_presets' + +local Public = {} + +local pages = { + require 'modules.gui.debug.redmew_global_view', + require 'modules.gui.debug.global_view', + require 'modules.gui.debug.package_view', + require 'modules.gui.debug._g_view', + require 'modules.gui.debug.event_view' +} + +local main_frame_name = Gui.uid_name() +local close_name = Gui.uid_name() +local tab_name = Gui.uid_name() + +function Public.open_dubug(player) + for i = 1, #pages do + local page = pages[i] + local callback = page.on_open_debug + if callback then + callback() + end + end + + local center = player.gui.center + local frame = center[main_frame_name] + if frame then + return + end + + frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3001', direction = 'vertical'} + local frame_style = frame.style + frame_style.height = 600 + frame_style.width = 900 + + local tab_flow = frame.add {type = 'flow', direction = 'horizontal'} + local container = frame.add {type = 'flow'} + container.style.vertically_stretchable = true + + local data = {} + + for i = 1, #pages do + local page = pages[i] + local tab_button = tab_flow.add({type = 'flow'}).add {type = 'button', name = tab_name, caption = page.name} + local tab_button_style = tab_button.style + + Gui.set_data(tab_button, {index = i, frame_data = data}) + + if i == 1 then + tab_button_style.font_color = Color.orange + + data.selected_index = i + data.selected_tab_button = tab_button + data.container = container + + Gui.set_data(frame, data) + page.show(container) + end + end + + frame.add {type = 'button', name = close_name, caption = 'Close'} +end + +Gui.on_click( + tab_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + local index = data.index + local frame_data = data.frame_data + local selected_index = frame_data.selected_index + + if selected_index == index then + return + end + + local selected_tab_button = frame_data.selected_tab_button + selected_tab_button.style.font_color = Color.black + + frame_data.selected_tab_button = element + frame_data.selected_index = index + element.style.font_color = Color.orange + + local container = frame_data.container + Gui.clear(container) + pages[index].show(container) + end +) + +Gui.on_click( + close_name, + function(event) + local frame = event.player.gui.center[main_frame_name] + if frame then + Gui.destroy(frame) + end + end +) + +return Public diff --git a/modules/gui/debug/model.lua b/modules/gui/debug/model.lua new file mode 100644 index 00000000..bf04eac9 --- /dev/null +++ b/modules/gui/debug/model.lua @@ -0,0 +1,147 @@ +local Gui = require 'utils.gui' +local table = require 'utils.table' + +local gui_names = Gui.names +local type = type +local concat = table.concat +local inspect = table.inspect +local pcall = pcall +local loadstring = loadstring +local rawset = rawset + +local Public = {} + +local luaObject = {'{', nil, ", name = '", nil, "'}"} +local luaPlayer = {"{LuaPlayer, name = '", nil, "', index = ", nil, '}'} +local luaEntity = {"{LuaEntity, name = '", nil, "', unit_number = ", nil, '}'} +local luaGuiElement = {"{LuaGuiElement, name = '", nil, "'}"} + +local function get(obj, prop) + return obj[prop] +end + +local function get_name_safe(obj) + local s, r = pcall(get, obj, 'name') + if not s then + return 'nil' + else + return r or 'nil' + end +end + +local function get_lua_object_type_safe(obj) + local s, r = pcall(get, obj, 'help') + + if not s then + return + end + + return r():match('Lua%a+') +end + +local function inspect_process(item) + if type(item) ~= 'table' or type(item.__self) ~= 'userdata' then + return item + end + + local suc, valid = pcall(get, item, 'valid') + if not suc then + -- no 'valid' property + return get_lua_object_type_safe(item) or '{NoHelp LuaObject}' + end + + if not valid then + return '{Invalid LuaObject}' + end + + local obj_type = get_lua_object_type_safe(item) + if not obj_type then + return '{NoHelp LuaObject}' + end + + if obj_type == 'LuaPlayer' then + luaPlayer[2] = item.name or 'nil' + luaPlayer[4] = item.index or 'nil' + + return concat(luaPlayer) + elseif obj_type == 'LuaEntity' then + luaEntity[2] = item.name or 'nil' + luaEntity[4] = item.unit_number or 'nil' + + return concat(luaEntity) + elseif obj_type == 'LuaGuiElement' then + local name = item.name + luaGuiElement[2] = gui_names and gui_names[name] or name or 'nil' + + return concat(luaGuiElement) + else + luaObject[2] = obj_type + luaObject[4] = get_name_safe(item) + + return concat(luaObject) + end +end + +local inspect_options = {process = inspect_process} +function Public.dump(data) + return inspect(data, inspect_options) +end +local dump = Public.dump + +function Public.dump_ignore_builder(ignore) + local function process(item) + if ignore[item] then + return nil + end + + return inspect_process(item) + end + + local options = {process = process} + return function(data) + return inspect(data, options) + end +end + +function Public.dump_function(func) + local res = {'upvalues:\n'} + + local i = 1 + while true do + local n, v = debug.getupvalue(func, i) + + if n == nil then + break + elseif n ~= '_ENV' then + res[#res + 1] = n + res[#res + 1] = ' = ' + res[#res + 1] = dump(v) + res[#res + 1] = '\n' + end + + i = i + 1 + end + + return concat(res) +end + +function Public.dump_text(text, player) + local func = loadstring('return ' .. text) + if not func then + return false + end + + rawset(game, 'player', player) + + local suc, var = pcall(func) + + rawset(game, 'player', nil) + + if not suc then + return false + end + + return true, dump(var) +end + +return Public diff --git a/modules/gui/debug/package_view.lua b/modules/gui/debug/package_view.lua new file mode 100644 index 00000000..479fb369 --- /dev/null +++ b/modules/gui/debug/package_view.lua @@ -0,0 +1,161 @@ +local Gui = require 'utils.gui' +local Color = require 'resources.color_presets' +local Model = require 'modules.gui.debug.model' + +local dump_function = Model.dump_function +local loaded = _G.package.loaded + +local Public = {} + +local ignore = { + _G = true, + package = true, + coroutine = true, + table = true, + string = true, + bit32 = true, + math = true, + debug = true, + serpent = true, + ['utils.math'] = true, + util = true, + ['utils.inspect'] = true, + ['mod-gui'] = true +} + +local file_label_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local breadcrumbs_name = Gui.uid_name() +local top_panel_name = Gui.uid_name() +local variable_label_name = Gui.uid_name() +local text_box_name = Gui.uid_name() + +Public.name = 'package' + +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 name, file in pairs(loaded) do + if not ignore[name] then + local file_label = + left_panel.add({type = 'flow'}).add {type = 'label', name = file_label_name, caption = name} + Gui.set_data(file_label, file) + end + end + + local right_flow = main_flow.add {type = 'flow', direction = 'vertical'} + + local breadcrumbs = right_flow.add {type = 'label', name = breadcrumbs_name} + + local top_panel = right_flow.add {type = 'scroll-pane', name = top_panel_name} + local top_panel_style = top_panel.style + top_panel_style.height = 200 + top_panel_style.maximal_width = 1000 + top_panel_style.horizontally_stretchable = true + + local text_box = right_flow.add {type = 'text-box', name = text_box_name} + text_box.read_only = true + text_box.selectable = true + + local text_box_style = text_box.style + text_box_style.vertically_stretchable = true + text_box_style.horizontally_stretchable = true + text_box_style.maximal_width = 1000 + text_box_style.maximal_height = 1000 + + local data = { + left_panel = left_panel, + breadcrumbs = breadcrumbs, + top_panel = top_panel, + text_box = text_box, + selected_file_label = nil, + selected_variable_label = nil + } + + Gui.set_data(left_panel, data) + Gui.set_data(top_panel, data) +end + +Gui.on_click( + file_label_name, + function(event) + local element = event.element + local file = Gui.get_data(element) + + local left_panel = element.parent.parent + local data = Gui.get_data(left_panel) + + local selected_file_label = data.selected_file_label + + if selected_file_label then + selected_file_label.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_file_label = element + + local top_panel = data.top_panel + local text_box = data.text_box + + Gui.clear(top_panel) + + local file_type = type(file) + + if file_type == 'table' then + for k, v in pairs(file) do + local label = + top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k} + Gui.set_data(label, v) + end + elseif file_type == 'function' then + text_box.text = dump_function(file) + else + text_box.text = tostring(file) + end + end +) + +Gui.on_click( + variable_label_name, + function(event) + local element = event.element + local variable = Gui.get_data(element) + + local top_panel = element.parent.parent + local data = Gui.get_data(top_panel) + local text_box = data.text_box + + local variable_type = type(variable) + + if variable_type == 'table' then + Gui.clear(top_panel) + for k, v in pairs(variable) do + local label = + top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k} + Gui.set_data(label, v) + end + return + end + + local selected_label = data.selected_variable_label + + if selected_label and selected_label.valid then + selected_label.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_variable_label = element + + if variable_type == 'function' then + text_box.text = dump_function(variable) + else + text_box.text = tostring(variable) + end + end +) + +return Public diff --git a/modules/gui/debug/redmew_global_view.lua b/modules/gui/debug/redmew_global_view.lua new file mode 100644 index 00000000..51204150 --- /dev/null +++ b/modules/gui/debug/redmew_global_view.lua @@ -0,0 +1,129 @@ +local Gui = require 'utils.gui' +local Global = require 'utils.global' +local Token = require 'utils.token' +local Color = require 'resources.color_presets' +local Model = require '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 = 'Global' + +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 token_id, token_name in pairs(Global.names) do + local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = token_name} + Gui.set_data(header, token_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 token_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 {'global.tokens[', token_id, ']'} + input_text_box.style.font_color = Color.black + + local content = dump(Token.get_global(token_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 diff --git a/utils/core.lua b/utils/core.lua index d8bdc6b3..92fc031f 100644 --- a/utils/core.lua +++ b/utils/core.lua @@ -3,13 +3,11 @@ -- Dependencies local Game = require 'utils.game' local Color = require 'resources.color_presets' -local Server = require 'features.server' -- localized functions local random = math.random local sqrt = math.sqrt local floor = math.floor -local format = string.format local match = string.match local insert = table.insert local concat = table.concat @@ -201,27 +199,6 @@ function Module.set_and_return(tbl, key, value) return value end ---- Takes msg and prints it to all players. Also prints to the log and discord --- @param msg The message to print --- @param warning_prefix The name of the module/warning -function Module.action_warning(warning_prefix, msg) - game.print(prefix .. msg, Color.yellow) - msg = format('%s %s', warning_prefix, msg) - log(msg) - Server.to_discord_bold(msg) -end - ---- Takes msg and prints it to all players except provided player. Also prints to the log and discord --- @param msg The message to print --- @param warning_prefix The name of the module/warning --- @param player the player not to send the message to -function Module.silent_action_warning(warning_prefix, msg, player) - Module.print_except(prefix .. msg, player, Color.yellow) - msg = format('%s %s', warning_prefix, msg) - log(msg) - Server.to_discord_bold(msg) -end - -- add utility functions that exist in base factorio/util require 'util' diff --git a/utils/global.lua b/utils/global.lua index 4d23bd43..a2689e9a 100644 --- a/utils/global.lua +++ b/utils/global.lua @@ -36,7 +36,7 @@ function Global.register_init(tbl, init_handler, callback) ) end -if _DEBUG then +if _DEBUG or true then local concat = table.concat local names = {}