From 370d353f5b4d47a07c085eaf1ece418afc08ebf4 Mon Sep 17 00:00:00 2001 From: Leslie Krause Date: Wed, 30 Sep 2020 19:15:50 -0400 Subject: [PATCH] Build 01 - initial beta version --- README.txt | 90 ++++++++++++ depends.txt | 2 + init.lua | 355 +++++++++++++++++++++++++++++++++++++++++++++ textures/debug.png | Bin 0 -> 1360 bytes 4 files changed, 447 insertions(+) create mode 100644 README.txt create mode 100644 depends.txt create mode 100644 init.lua create mode 100644 textures/debug.png diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..47a71f9 --- /dev/null +++ b/README.txt @@ -0,0 +1,90 @@ +Debugging Console Mod v1.1 +by Leslie Krause + +Debugging Console provides an in-game HUD for developers and administrators to monitor +debug output more conveniently than via the terminal or minetest.chat_send_all(). + + +Repository +---------------------- + +Browse source code... + https://bitbucket.org/sorcerykid/console + +Download archive... + https://bitbucket.org/sorcerykid/console/get/master.zip + https://bitbucket.org/sorcerykid/console/get/master.tar.gz + +Compatability +---------------------- + +Minetest 0.4.14+ required + +Installation +---------------------- + + 1) Unzip the archive into the mods directory of your game. + 2) Rename the console-master directory to "console". + 3) Add "console" as a dependency to any mods using the API. + +Source Code License +---------------------------------------------------------- + +The MIT License (MIT) + +Copyright (c) 2020, Leslie Krause (leslie@searstower.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +For more details: +https://opensource.org/licenses/MIT + + +Multimedia License (textures, sounds, and models) +---------------------------------------------------------- + +Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) + + /textures/debug.png + by Flat Icons + obtained from https://www.flaticon.com/free-icon/debug_1485257 + licensed to sorcerykid + +You are free to: +Share — copy and redistribute the material in any medium or format. +Adapt — remix, transform, and build upon the material for any purpose, even commercially. +The licensor cannot revoke these freedoms as long as you follow the license terms. + +Under the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and +indicate if changes were made. You may do so in any reasonable manner, but not in any way +that suggests the licensor endorses you or your use. + +No additional restrictions — You may not apply legal terms or technological measures that +legally restrict others from doing anything the license permits. + +Notices: + +You do not have to comply with the license for elements of the material in the public +domain or where your use is permitted by an applicable exception or limitation. +No warranties are given. The license may not give you all of the permissions necessary +for your intended use. For example, other rights such as publicity, privacy, or moral +rights may limit how you use the material. + +For more details: +http://creativecommons.org/licenses/by-sa/3.0/ diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..55fef04 --- /dev/null +++ b/depends.txt @@ -0,0 +1,2 @@ +timekeeper +config diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..57acd65 --- /dev/null +++ b/init.lua @@ -0,0 +1,355 @@ +-------------------------------------------------------- +-- Minetest :: Debug Console Mod (console) +-- +-- See README.txt for licensing and release notes. +-- Copyright (c) 2020, Leslie E. Krause +-- +-- ./games/minetest_game/mods/console/init.lua +-------------------------------------------------------- + +local config = minetest.load_config( { + default_size = "none", + max_output_lines = 17, + max_review_lines = 100, + has_line_numbers = false, +} ) +local player_huds = { } +local buffer = { } +local output = "" +local pipes = { } +local log_file + +local unsafe_funcs = { + ["pairs"] = true, + ["ipairs"] = true, + ["next"] = true, + ["tonumber"] = true, + ["tostring"] = true, + ["printf"] = true, + ["assert"] = true, + ["error"] = true, +} + +---------------- + +local gsub = string.gsub +local join = table.join +local max = math.max +local sprintf = string.format +local insert = table.insert + +function printf( str, ... ) + if type( str ) == "table" then + str = join( str, " ", function ( i, v ) + return tostring( v ) + end, true ) + elseif type( str ) ~= "string" then + str = tostring( str ) + end + if #{ ... } > 0 then + str = sprintf( str, ... ) + end + + gsub( str .. "\n", "(.-)\n", function ( line ) + insert( buffer, line ) + end ) + + output = "" + + for i = max( 1, #buffer - config.max_output_lines + 1 ), #buffer do + if config.has_line_numbers then + output = output .. sprintf( "%03d: %s\n", i, buffer[ i ], "\n" ) + else + output = output .. buffer[ i ] .. "\n" + end + end + + for name, data in pairs( player_huds ) do + if data.size ~= "none" then + data.player:hud_change( data.refs.body_text, "text", output ) + end + end +end + +---------------- + +local _ = nil + +local function is_match( text, glob ) + -- use underscore variable to preserve captures + _ = { string.match( text, glob ) } + return #_ > 0 +end + +local function parse_id( param ) + if is_match( param, "^([a-zA-Z][a-zA-Z0-9_]+)$" ) then + return { method = _[ 1 ] } + elseif is_match( param, "^([a-zA-Z][a-zA-Z0-9_]+)%.([a-zA-Z][a-zA-Z0-9_]+)$" ) then + return { parent = _[ 1 ], method = _[ 2 ] } + end + return nil +end + +local function resize_hud( name, size ) + local data = player_huds[ name ] + local player = data.player + local refs = data.refs + + if size == data.size then return end + + if refs then + player:hud_remove( refs.head_bg ) + player:hud_remove( refs.head_text ) + player:hud_remove( refs.head_icon ) + player:hud_remove( refs.body_bg ) + player:hud_remove( refs.body_text ) + end + + if size == "none" then + refs = nil + else + refs = { } + + refs.body_bg = player:hud_add( { + hud_elem_type = "image", + text = "default_cloud.png^[colorize:#000000DD", + position = { x = size == "full" and 0.0 or 0.6, y = 0.5 }, + scale = { x = -100, y = -50 }, + alignment = { x = 1, y = 0 }, + } ) + + refs.body_text = player:hud_add( { + hud_elem_type = "text", + text = output, + number = 0xFFFFFF, + position = { x = size == "full" and 0.0 or 0.6, y = 0.25 }, + alignment = { x = 1, y = 1 }, + offset = { x = 8, y = 38 }, + } ) + + refs.head_bg = player:hud_add( { + hud_elem_type = "image", + text = "default_cloud.png^[colorize:#222222CC", + position = { x = size == "full" and 0.0 or 0.6, y = 0.25 }, + scale = { x = -100, y = 2 }, + alignment = { x = 1, y = 1 }, + } ) + + refs.head_text = player:hud_add( { + hud_elem_type = "text", + text = "Debug Console", + number = 0x999999, + position = { x = size == "full" and 0.0 or 0.6, y = 0.25 }, + alignment = { x = 1, y = 1 }, + offset = { x = 36, y = 8 }, + } ) + + refs.head_icon = player:hud_add( { + hud_elem_type = "image", + text = "debug.png", + position = { x = size == "full" and 0.0 or 0.6, y = 0.25 }, + scale = { x = 1, y = 1 }, + alignment = { x = 1, y = 1 }, + offset = { x = 6, y = 4 }, + } ) + end + + data.refs = refs + data.size = size +end + +---------------- + +minetest.register_privilege( "debug", { + description = "Manage and review the debugging console.", + give_to_singleplayer = true, +} ) + +minetest.register_on_joinplayer( function( player ) + local pname = player:get_player_name( ) + + if minetest.check_player_privs( pname, "debug" ) then + player_huds[ pname ] = { player = player } + resize_hud( pname, config.default_size ) + end +end ) + +minetest.register_on_leaveplayer( function( player ) + local pname = player:get_player_name( ) + + if player_huds[ pname ] then + player_huds[ pname ] = nil + end +end ) + +---------------- + +minetest.register_chatcommand( "debug", { + description = "Open the debug history viewer", + privs = { server = true }, + func = function( name, param ) + local formspec = "size[11.0,7.8]" + .. minetest.gui_bg + .. minetest.gui_bg_img + + formspec = formspec .. "textarea[0.3,0.5;11.0,7.5;buffer;Debug History;" + + for i = max( 1, #buffer - config.max_review_lines + 1 ), #buffer do + formspec = formspec .. minetest.formspec_escape( buffer[ i ] ) .. "\n" + end + + formspec = formspec .. "]" + .. "label[9.0,0.0;" .. os.date( "%X" ) .. "]" + .. "button_exit[0.0,7.1;2.0,1.0;clear;Clear]" + .. "button_exit[9.0,7.1;2.0,1.0;close;Close]" + + minetest.create_form( nil, name, formspec, function( state, player, fields ) + if fields.clear then + buffer = { } + output = "" + + for name, data in pairs( player_huds ) do + if data.size ~= "none" then + data.player:hud_change( data.refs.body_text, "text", "" ) + end + end + end + end ) + end, +} ) + +minetest.register_chatcommand( "tail", { + description = "Continuously follow the output stream of a plain-text log file", + privs = { server = true }, + func = function( name, param ) + if param == "" then + if log_file then + log_file:close( ) + log_file = nil + + return true, "Log file closed." + end + else + log_file = io.open( param, "r" ) + + if not log_file then + return false, "Failed to open log file" + end + + log_file:seek( "end", 0 ) + return true, "Log file opened." + end + end +} ) + +minetest.register_chatcommand( "unpipe", { + description = "Unpipe a function from the debugging console", + privs = { server = true }, + func = function( name, param ) + if param == "" then + for k, v in pairs( pipes ) do + if v.class.parent then + _G[ v.class.parent ][ v.class.method ] = v.func + else + _G[ v.class.method ] = v.func + end + end + pipes = { } + + return true, "Removed all function pipes." + + elseif pipes[ param ] then + local v = pipes[ param ] + + if v.class.parent then + _G[ v.class.parent ][ v.class.method ] = v.func + else + _G[ v.class.method ] = v.func + end + pipes[ param ] = nil + + return true, "Removed function pipe." + else + + return false, "Failed to remove function pipe." + end + end, +} ) + +minetest.register_chatcommand( "pipe", { + description = "Pipe a function to the debugging console", + privs = { server = true }, + func = function( name, param ) + if param == "" then + local list = { } + for k, v in pairs( pipes ) do + table.insert( list, k ) + end + table.sort( list ) + return true, "Piped Functions: " .. table.concat( list, ", " ) + + elseif not unsafe_funcs[ param ] then + local class = parse_id( param ) + + if class and not pipes[ param ] then + local func + + if not class.parent then + func = _G[ class.method ] + elseif _G[ class.parent ] then + func = _G[ class.parent ][ class.method ] + end + + if func then + pipes[ param ] = { func = func, class = class } + + local new_func = function( ... ) + local args = { "[" .. param .. "]", ... } + for i = 2, #args do + args[ i ] = tostring( args[ i ] ) + end + printf( args ) + + return func( ... ) + end + + if class.parent then + _G[ class.parent ][ class.method ] = new_func + else + _G[ class.method ] = new_func + end + + return true, "Function pipe created." + end + end + end + + return false, "Failed to create function pipe." + end +} ) + +globaltimer.start( 1.0, "console:slurp_file", function ( ) + if log_file then + local str = log_file:read( "*a" ) + if str ~= "" then + printf( string.match( str, "(.-)\n?$" ) ) -- remove trailing newline + end + end +end ) + +globaltimer.start( 0.2, "console:resize_huds", function( ) + for name, data in pairs( player_huds ) do + local controls = data.player:get_player_control( ) + + if controls.sneak and controls.aux1 then + if data.size == "half" then + resize_hud( name, "full" ) + elseif data.size == "full" then + resize_hud( name, "none" ) + else + resize_hud( name, "half" ) + end + + end + end +end ) diff --git a/textures/debug.png b/textures/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..07943def6f7065597e3baeca7efe0f5357bcd81e GIT binary patch literal 1360 zcmV-W1+V&vP)phLO-Z*Qf&(NH7|TnutV5q?ZZ^gcbx_h0s!H=z~6P?>+aN zy&RnOLQ9MI&vyRxIR83({af*rSh1o$YK;ue1@LlmJ1<(T*rY_Oe*#4gl zURtu}HN+fIsQj=MB&dVa$`ruy98$&sWKb!oO|&)~pSD4DeAnxOK5i*(MXd;0>FJbD zfI&#Qqj=)EqV$HA){lQ4=du+lE9w>t`mk2oirLW##g#HtuI$O4D_8cQl;L3!MUEZ8 zQrbq$k?Q!aMUOSmwr&KK7qg<1g(0G;DDUI&7pFM<^8jhL>BWjL}LH(m{YXS1sLL4Pg79Xx>pu5%fs{ z0gqt7Fp>-aP#(0n`q6rZ??nN~DNIrpviU3R`sZi{C!@(^EP_^mv`OO`sRa^xOy34u zTiL-ZUviiIdlM)vl!OAjn-igUU?I!aRk3Vs6(tWAvOOn4Q7C}Y!v4JpUWnh#j>=Xx z%HJK-n)nB?6p^8@6&ja^Ak^UlI)|pL2dS{c``aEvP@)f3BLWeGrn%&F&)yElp zAk=^IN_J1uotHzp zw6$EMblGhBPFyBjR^*SMy@!H1L2mzYiF?b3F#Q?&2SV9=BYrKJ;oh;jyH9227OvW^ zPbl$9|8*w8o5K{pUqdt;A~HKhtmawzKW(P}-dz@L?%-r|IcLtz z&o*$x!HBta_K$`G*bnhSq zLx#+a9&WaGawityhX*0vrb@u(ANG$KOMO(961r)Bp;ZVZXG#&%J9izmh%$&6w!JPyTFZ;wAC$ zTv2Q}cct;~an5=)u&bxJqqL}|MKBvP#*F}2`r%798G_&#hfyz$5$O}fTP>{zPdpl9 zO-}0SY3`U;SoN)-7P{hvN0% literal 0 HcmV?d00001