From f7d602555735c36744b186edc89c52b487483969 Mon Sep 17 00:00:00 2001 From: Dirk Sohler Date: Sun, 4 Apr 2021 14:23:35 +0200 Subject: [PATCH] adapt custom timer to new style Also implement control buttons closes https://gitlab.com/4w/mtimer/-/issues/12 --- locale/mtimer.de.tr | 12 +++- system/chat_command.lua | 50 ++------------ system/formspecs/custom_timer.lua | 36 ++++++---- system/on_receive_fields.lua | 18 ++++- system/update_timer.lua | 65 ++++++++++++++++++ .../buttons/actions/mtimer_ct_restart.png | Bin 0 -> 4277 bytes textures/buttons/actions/mtimer_ct_start.png | Bin 0 -> 3179 bytes textures/buttons/actions/mtimer_ct_stop.png | Bin 0 -> 1878 bytes 8 files changed, 117 insertions(+), 64 deletions(-) create mode 100644 textures/buttons/actions/mtimer_ct_restart.png create mode 100644 textures/buttons/actions/mtimer_ct_start.png create mode 100644 textures/buttons/actions/mtimer_ct_stop.png diff --git a/locale/mtimer.de.tr b/locale/mtimer.de.tr index 643af8f..0eab6b2 100644 --- a/locale/mtimer.de.tr +++ b/locale/mtimer.de.tr @@ -68,7 +68,7 @@ Session Duration=Sitzungsdauer Session Start Time=Sitzungsstart-Zeit Host Time=Hostzeit -# Custom Timer Things +# Custom Timer Setup Running=Timer läuft Stopped=Angehalten Finished=Abgeschlossen @@ -83,12 +83,22 @@ Configure the custom timer=Individuellen Timer konfigurieren Start the custom timer=Individuellen Timer starten Stop stop custom timer=Individuellen Timer anhalten Restart the custom timer=Individuellen Timer neu starten + +# Custonm timer status messages The custom timer is already running=Der individuelle Timer läuft bereits The custom timer is not running=Der individuelle Timer läuft nicht The custom timer was started=Der individuelle Timer wurde gestartet The custom timer was stopped=Der individuelle Timer wurde angehalten The custom timer was restarted=Der individuelle timer wurde neu gestartet +# Custom Timer Controls +The timer is currently @1=Der Timer ist aktuell @1 +running=aktiv +stopped=angehalten +Start=Starten +Stop=Anhalten +Restart=Neustart + # Default Timer Format Current Date: @1=Aktuelles Datum: @1 Ingame Time: @1=Spielzeit: @1 diff --git a/system/chat_command.lua b/system/chat_command.lua index 41c3703..70001a0 100644 --- a/system/chat_command.lua +++ b/system/chat_command.lua @@ -1,8 +1,6 @@ local m = mtimer local S = m.translator local d = m.dialog -local cs = minetest.chat_send_player -local ds = minetest.deserialize -- Colorize a command sequence @@ -17,46 +15,6 @@ local command = function (command) end -local custom_timer_handling = function (name, action) - local player = minetest.get_player_by_name(name) - local player_meta = player:get_meta() - local current_timestamp = os.time(os.date('!*t')) - local ctv_key = m.meta.custom_timer_settings.key - local ctv = ds(player_meta:get_string(ctv_key)) - - if action == 'start' then - if ctv.running ~= true then - ctv.running = true - ctv.start_timestamp = current_timestamp - cs(name, S('The custom timer was started')) - else - cs(name, S('The custom timer is already running')) - end - end - - if action == 'stop' then - if ctv.running ~= false then - ctv.running = false - ctv.start_timestamp = 0 - cs(name, S('The custom timer was stopped')) - else - cs(name, S('The custom timer is not running')) - end - end - - if action == 'restart' then - if ctv.running == true then - ctv.start_timestamp = current_timestamp - cs(name, S('The custom timer was restarted')) - else - cs(name, S('The custom timer is not running')) - end - end - - player_meta:set_string(ctv_key, minetest.serialize(ctv)) -end - - -- Chat command -- -- The `/mtimer` chat command opens the main menu and allows to directly open @@ -106,9 +64,9 @@ minetest.register_chatcommand('mtimer', { if action == 'tf' then d.timer_format(name) end if action == 'ct' then d.custom_timer(name) end - if action == 'ctstart' then custom_timer_handling(name,'start') end - if action == 'ctstop' then custom_timer_handling(name,'stop') end - if action == 'ctrestart' then custom_timer_handling(name,'restart') end + if action == 'ctstart' then mtimer.update_custom_timer(name, { action = 'start' }) end + if action == 'ctstop' then mtimer.update_custom_timer(name, { action = 'stop' }) end + if action == 'ctrestart' then mtimer.update_custom_timer(name, { action = 'restart' }) end if action == 'help' then local message = { @@ -131,7 +89,7 @@ minetest.register_chatcommand('mtimer', { command('ctstop ')..S('Stop stop custom timer'), command('ctrestart')..S('Restart the custom timer') } - cs(name, table.concat(message, '\n')) + minetest.chat_send_player(name, table.concat(message, '\n')) end end }) diff --git a/system/formspecs/custom_timer.lua b/system/formspecs/custom_timer.lua index e339f5b..ae1d3ed 100644 --- a/system/formspecs/custom_timer.lua +++ b/system/formspecs/custom_timer.lua @@ -19,6 +19,7 @@ local esc = minetest.formspec_escape mtimer.dialog.custom_timer = function (player_name) local player_meta = minetest.get_player_by_name(player_name):get_meta() local ctv = minetest.deserialize(player_meta:get_string(m.meta.custom_timer_settings.key)) + local timer_status = (ctv.running == true) and S('running') or S('stopped') local days = ctv.values.days or 0 local hours = ctv.values.hours or 0 @@ -36,8 +37,8 @@ mtimer.dialog.custom_timer = function (player_name) mtimer.show_formspec('mtimer:custom_timer', { title = S('Custom Timer'), show_to = player_name, - height = 5.3, - width = 9, + height = 6.25, + width = 13, formspec = { 'field_close_on_enter[v_format_running;false]', 'field_close_on_enter[v_format_stopped;false]', @@ -47,20 +48,18 @@ mtimer.dialog.custom_timer = function (player_name) 'field_close_on_enter[v_minutes;false]', 'field_close_on_enter[v_seconds;false]', 'container[0,0]', - ' label[0,0.25;'..S('Running')..'] field[2.25,0;6.5,0.5;v_format_running;;'..esc(format_running)..']', - ' label[0,0.85;'..S('Stopped')..'] field[2.25,0.6;6.5,0.5;v_format_stopped;;'..esc(format_stopped)..']', - ' label[0,1.45;'..S('Finished')..'] field[2.25,1.2;6.5,0.5;v_format_finished;;'..esc(format_finished)..']', + ' label[0,0.25;'..S('Running')..'] field[2.5,0;10.5,0.5;v_format_running;;'..esc(format_running)..']', + ' label[0,0.85;'..S('Stopped')..'] field[2.5,0.6;10.5,0.5;v_format_stopped;;'..esc(format_stopped)..']', + ' label[0,1.45;'..S('Finished')..'] field[2.5,1.2;10.5,0.5;v_format_finished;;'..esc(format_finished)..']', ' box[0,2;+contentWidth,0.04;#ffffff]', 'container_end[]', 'container[3.75,2.4]', - ' label[0,0;'..S('Information')..']', - ' label[1.75,0;'..S('Variable')..']', - ' label[3.25,0;'..S('Used Value')..']', - ' box[0,0.25;5,0.02;#ffffff]', - ' label[0,0.5;'..S('Days')..'] label[1.75,0.5;{days}] label[3.25,0.5;'..days..']', - ' label[0,0.9;'..S('Hours')..'] label[1.75,0.9;{hours}] label[3.25,0.9;'..hours..']', - ' label[0,1.3;'..S('Minutes')..'] label[1.75,1.3;{minutes}] label[3.25,1.3;'..minutes..']', - ' label[0,1.7;'..S('Seconds')..'] label[1.75,1.7;{seconds}] label[3.25,1.7;'..seconds..']', + ' label[0,0;'..S('Information')..'] label[2.5,0;'..S('Variable')..'] label[5,0;'..S('Used Value')..']', + ' box[0,0.25;7,0.02;#ffffff]', + ' label[0,0.5;'..S('Days')..'] label[2.5,0.5;{days}] label[5,0.5;'..days..']', + ' label[0,0.9;'..S('Hours')..'] label[2.5,0.9;{hours}] label[5,0.9;'..hours..']', + ' label[0,1.3;'..S('Minutes')..'] label[2.5,1.3;{minutes}] label[5,1.3;'..minutes..']', + ' label[0,1.7;'..S('Seconds')..'] label[2.5,1.7;{seconds}] label[5,1.7;'..seconds..']', 'container_end[]', 'container[0,2.3]', ' container[0,0]', @@ -84,11 +83,20 @@ mtimer.dialog.custom_timer = function (player_name) ' button[0,0.75;0.75,0.25;c_seconds_m;-]', ' container_end[]', 'container_end[]', - 'container[0,3.6]', + 'container[0,3.75]', ' checkbox[0,0;mode_countdown;'..S('Countdown')..';'..a_countdown..']', ' checkbox[0,0.4;mode_timer;'..S('Timer Mode')..';'..a_timer..']', ' checkbox[0,0.8;mode_continuous;'..S('Continuous Run')..';'..a_continuous..']', 'container_end[]', + 'container[0,5.55]', + ' box[0,-0.25;+contentWidth,0.04;#ffffff]', + ' label[0,0.375;'..esc(S('The timer is currently @1', timer_status))..']', + ' container[+contentWidth,0]', + mtimer.get_icon_button('ct_start', { width = 2.25, label = S('Start'), container = { left = -7.25 } }), + mtimer.get_icon_button('ct_stop', { width = 2.25, label = S('Stop'), container = { left = -4.75 } }), + mtimer.get_icon_button('ct_restart', { width = 2.25, label = S('Restart'), container = { left = -2.25 } }), + ' container_end[]', + 'container_end[]', } }) end diff --git a/system/on_receive_fields.lua b/system/on_receive_fields.lua index 51ffcc9..0e99b65 100644 --- a/system/on_receive_fields.lua +++ b/system/on_receive_fields.lua @@ -39,7 +39,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.set_visible then meta:set_string(attr.key, 'true') end if fields.set_invisible then meta:set_string(attr.key, 'false') end if fields.default then meta:set_string(attr.key, attr.default) end - if not fields.quit then d.set_visibility(name) end end @@ -232,7 +231,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) seconds = seconds } - -- Set default values if requested + -- Set default values if requested and instantly return to prevent the + -- rest of the configuration to be executed. At this point only + -- resetting all values is desired. if fields.default then meta:set_string(attr.key, attr.default) d.custom_timer(name) @@ -243,8 +244,19 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) -- Set values if not quitting if not fields.quit then meta:set_string(attr.key, minetest.serialize(ctv)) - d.custom_timer(name) end + + -- Control timer if one of the control buttons was pressed. This is run + -- after the values safing in order to use the new values instead of + -- the values that were stored before. + local ct_update = false + if fields.ct_start then ct_update = { action = 'start' } end + if fields.ct_stop then ct_update = { action = 'stop' } end + if fields.ct_restart then ct_update = { action = 'restart' } end + if ct_update~=false then mtimer.update_custom_timer(name,ct_update) end + + -- Show the timer formspec if not quitting + if not fields.quit then d.custom_timer(name) end end diff --git a/system/update_timer.lua b/system/update_timer.lua index 9e56cfd..2b83c21 100644 --- a/system/update_timer.lua +++ b/system/update_timer.lua @@ -1,5 +1,7 @@ local m = mtimer local deserialize = minetest.deserialize +local cs = minetest.chat_send_player +local S = m.translator -- Calculate HUD positions and offsets @@ -117,3 +119,66 @@ mtimer.update_timer = function (player_name) player:hud_change(hud_id, 'size', {x=size, y=size}) player:hud_change(hud_id, 'offset', orientation.offset) end + + +-- Update the custom timer +-- +-- This function handles updates for the custom timer for the player referenced +-- by the provided `name` parameter. This needs to be a player name string. +-- +-- The update is performed based on the provided table. +-- +-- update_parameters = { +-- action = 'the_action' +-- } +-- +-- Currently the only actions are `start`, `stop`, and `restart`. +-- +-- @param player_name The name of the player to update the custom timer for +-- @param update_parameters The update parameters table as described +mtimer.update_custom_timer = function (player_name, update_parameters) + local up = update_parameters or {} + local player = minetest.get_player_by_name(player_name) + local player_meta = player:get_meta() + local current_timestamp = os.time(os.date('!*t')) + local ctv_key = m.meta.custom_timer_settings.key + local ctv = minetest.deserialize(player_meta:get_string(ctv_key)) + + print(dump(player_name)) + + -- Start timer if not running + if up.action == 'start' then + if ctv.running ~= true then + ctv.running = true + ctv.start_timestamp = current_timestamp + cs(player_name, S('The custom timer was started')) + else + cs(player_name, S('The custom timer is already running')) + end + end + + -- Stop timer if running + if up.action == 'stop' then + if ctv.running ~= false then + ctv.running = false + ctv.start_timestamp = 0 + cs(player_name, S('The custom timer was stopped')) + else + cs(player_name, S('The custom timer is not running')) + end + end + + -- Restart timer + if up.action == 'restart' then + if ctv.running == true then + ctv.start_timestamp = current_timestamp + cs(player_name, S('The custom timer was restarted')) + else + cs(player_name, S('The custom timer is not running')) + end + end + + -- Write timer update to player meta data + player_meta:set_string(ctv_key, minetest.serialize(ctv)) +end + diff --git a/textures/buttons/actions/mtimer_ct_restart.png b/textures/buttons/actions/mtimer_ct_restart.png new file mode 100644 index 0000000000000000000000000000000000000000..8c6fada3f47f5c6ea9c4a6a3d72ed1829957dc43 GIT binary patch literal 4277 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 XzxY2s$LE@T00000NkvXXu0mjfSASfb literal 0 HcmV?d00001 diff --git a/textures/buttons/actions/mtimer_ct_start.png b/textures/buttons/actions/mtimer_ct_start.png new file mode 100644 index 0000000000000000000000000000000000000000..773d9e88734848e46d19a6328be15399f94d26b9 GIT binary patch literal 3179 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