From 8dc50e159a0d4959510dd4033d4f3f278f3f2219 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:14:19 -0600 Subject: [PATCH 01/11] Document external TTS usage --- flavor-text.lua | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ flavor-text.rst | 27 ++++++++ 2 files changed, 203 insertions(+) create mode 100644 flavor-text.lua create mode 100644 flavor-text.rst diff --git a/flavor-text.lua b/flavor-text.lua new file mode 100644 index 000000000..9d171e9b7 --- /dev/null +++ b/flavor-text.lua @@ -0,0 +1,176 @@ +-- Writes the currently viewed unit or item flavor text to a file. +-- +-- Usage: +-- flavor-text + +local folder = 'flavor text' +local filename = 'read flavor.txt' +local filepath = folder .. '/' .. filename + +local function clear_output_file() + local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) + if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) + end + local file, open_err = io.open(filepath, 'w') + if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) + end + file:write('') + file:close() +end + +local function reformat(str) + local cleaned = str:gsub('%[B%]', '') + :gsub('%[P%]', '') + :gsub('%[R%]', '') + :gsub('%[C:%d+:%d+:%d+%]', '') + :gsub('%s+', ' ') + :gsub('^%s+', '') + :gsub('%s+$', '') + return cleaned +end + +local function collect_lines(entries) + local lines = {} + for _, entry in ipairs(entries) do + if entry.value ~= '' then + local cleaned = reformat(dfhack.df2utf(entry.value)) + if cleaned ~= '' then + table.insert(lines, cleaned) + end + end + end + return lines +end + +local function get_health_text(view_sheets) + if #view_sheets.unit_health_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.unit_health_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local function get_personality_text(view_sheets) + if #view_sheets.personality_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.personality_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local UNIT_SHEET_SUBTAB = { + HEALTH = 2, + PERSONALITY = 10, +} + +local HEALTH_ACTIVE_TAB = { + STATUS = 0, + WOUNDS = 1, + TREATMENT = 2, + HISTORY = 3, + DESCRIPTION = 4, +} + +local PERSONALITY_ACTIVE_TAB = { + TRAITS = 0, + VALUES = 1, + PREFERENCES = 2, + NEEDS = 3, +} + +local function get_unit_flavor_text(view_sheets) + local unit = df.unit.find(view_sheets.active_id) + if not unit then + qerror('Unable to resolve the active unit.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then + local health_text = get_health_text(view_sheets) + if health_text then + return unit, 'Health', health_text + end + clear_output_file() + qerror('No text found on the Health tab.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY + and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) + then + local text = get_personality_text(view_sheets) + if text then + return unit, 'Personality', text + end + clear_output_file() + qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') + end + + clear_output_file() + qerror('Open Health, Personality or an item window before running this script.') +end + +local function get_item_flavor_text(view_sheets) + local item = dfhack.gui.getSelectedItem(true) + if not item then + qerror('Select an item or open an item view sheet before running this script.') + end + + local description = view_sheets.raw_description or '' + if description == '' then + qerror('No item description text found on the item view sheet.') + end + + return item, 'Item', reformat(dfhack.df2utf(description)) +end + +local view_sheets = df.global.game.main_interface.view_sheets +if not view_sheets.open then + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local screen = dfhack.gui.getDFViewscreen() +local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) +local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) + +local subject, flavor_type, text +if is_unit_sheet then + subject, flavor_type, text = get_unit_flavor_text(view_sheets) +elseif is_item_sheet then + subject, flavor_type, text = get_item_flavor_text(view_sheets) +else + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) +if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) +end + +local file, open_err = io.open(filepath, 'w') +if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) +end + +file:write(text) +file:close() + +local name +if is_unit_sheet then + name = dfhack.df2console(dfhack.units.getReadableName(subject)) +else + name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) +end + +print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) diff --git a/flavor-text.rst b/flavor-text.rst new file mode 100644 index 000000000..9a6a5a4c0 --- /dev/null +++ b/flavor-text.rst @@ -0,0 +1,27 @@ +flavor-text +=========== + +Overview +-------- +The ``flavor-text`` script writes the currently viewed unit or item flavor text to +``flavor text/read flavor.txt``. + +Usage +----- +Run the script from DFHack: + +:: + + flavor-text + +Notes +----- +- The file is overwritten each time the script runs. +- If the wrong window is open, the script clears the output file. +- Supported unit tabs: + - Health (Status/Wounds/Treatment/History/Description) + - Personality (Traits/Values/Preferences/Needs) +- Supported item window: item view sheets. +- You must use your own text-to-speech (TTS) program to read the output. + On Windows 11, Voice Attack works well, and a profile with the launch command + can be included for others to use. From 15251e33018c9c0c15a842fd83d9697a2e9006b8 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:23:43 -0600 Subject: [PATCH 02/11] Revert "Document external TTS usage for flavor-text" --- flavor-text.lua | 176 ------------------------------------------------ flavor-text.rst | 27 -------- 2 files changed, 203 deletions(-) delete mode 100644 flavor-text.lua delete mode 100644 flavor-text.rst diff --git a/flavor-text.lua b/flavor-text.lua deleted file mode 100644 index 9d171e9b7..000000000 --- a/flavor-text.lua +++ /dev/null @@ -1,176 +0,0 @@ --- Writes the currently viewed unit or item flavor text to a file. --- --- Usage: --- flavor-text - -local folder = 'flavor text' -local filename = 'read flavor.txt' -local filepath = folder .. '/' .. filename - -local function clear_output_file() - local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) - if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) - end - local file, open_err = io.open(filepath, 'w') - if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) - end - file:write('') - file:close() -end - -local function reformat(str) - local cleaned = str:gsub('%[B%]', '') - :gsub('%[P%]', '') - :gsub('%[R%]', '') - :gsub('%[C:%d+:%d+:%d+%]', '') - :gsub('%s+', ' ') - :gsub('^%s+', '') - :gsub('%s+$', '') - return cleaned -end - -local function collect_lines(entries) - local lines = {} - for _, entry in ipairs(entries) do - if entry.value ~= '' then - local cleaned = reformat(dfhack.df2utf(entry.value)) - if cleaned ~= '' then - table.insert(lines, cleaned) - end - end - end - return lines -end - -local function get_health_text(view_sheets) - if #view_sheets.unit_health_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.unit_health_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local function get_personality_text(view_sheets) - if #view_sheets.personality_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.personality_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local UNIT_SHEET_SUBTAB = { - HEALTH = 2, - PERSONALITY = 10, -} - -local HEALTH_ACTIVE_TAB = { - STATUS = 0, - WOUNDS = 1, - TREATMENT = 2, - HISTORY = 3, - DESCRIPTION = 4, -} - -local PERSONALITY_ACTIVE_TAB = { - TRAITS = 0, - VALUES = 1, - PREFERENCES = 2, - NEEDS = 3, -} - -local function get_unit_flavor_text(view_sheets) - local unit = df.unit.find(view_sheets.active_id) - if not unit then - qerror('Unable to resolve the active unit.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then - local health_text = get_health_text(view_sheets) - if health_text then - return unit, 'Health', health_text - end - clear_output_file() - qerror('No text found on the Health tab.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY - and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) - then - local text = get_personality_text(view_sheets) - if text then - return unit, 'Personality', text - end - clear_output_file() - qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') - end - - clear_output_file() - qerror('Open Health, Personality or an item window before running this script.') -end - -local function get_item_flavor_text(view_sheets) - local item = dfhack.gui.getSelectedItem(true) - if not item then - qerror('Select an item or open an item view sheet before running this script.') - end - - local description = view_sheets.raw_description or '' - if description == '' then - qerror('No item description text found on the item view sheet.') - end - - return item, 'Item', reformat(dfhack.df2utf(description)) -end - -local view_sheets = df.global.game.main_interface.view_sheets -if not view_sheets.open then - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local screen = dfhack.gui.getDFViewscreen() -local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) -local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) - -local subject, flavor_type, text -if is_unit_sheet then - subject, flavor_type, text = get_unit_flavor_text(view_sheets) -elseif is_item_sheet then - subject, flavor_type, text = get_item_flavor_text(view_sheets) -else - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) -if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) -end - -local file, open_err = io.open(filepath, 'w') -if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) -end - -file:write(text) -file:close() - -local name -if is_unit_sheet then - name = dfhack.df2console(dfhack.units.getReadableName(subject)) -else - name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) -end - -print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) diff --git a/flavor-text.rst b/flavor-text.rst deleted file mode 100644 index 9a6a5a4c0..000000000 --- a/flavor-text.rst +++ /dev/null @@ -1,27 +0,0 @@ -flavor-text -=========== - -Overview --------- -The ``flavor-text`` script writes the currently viewed unit or item flavor text to -``flavor text/read flavor.txt``. - -Usage ------ -Run the script from DFHack: - -:: - - flavor-text - -Notes ------ -- The file is overwritten each time the script runs. -- If the wrong window is open, the script clears the output file. -- Supported unit tabs: - - Health (Status/Wounds/Treatment/History/Description) - - Personality (Traits/Values/Preferences/Needs) -- Supported item window: item view sheets. -- You must use your own text-to-speech (TTS) program to read the output. - On Windows 11, Voice Attack works well, and a profile with the launch command - can be included for others to use. From b2c63f5fb4f46110f927174de372ba9ee172c617 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:25:08 -0600 Subject: [PATCH 03/11] Delete flavor-text.lua --- flavor-text.lua | 176 ------------------------------------------------ 1 file changed, 176 deletions(-) delete mode 100644 flavor-text.lua diff --git a/flavor-text.lua b/flavor-text.lua deleted file mode 100644 index 9d171e9b7..000000000 --- a/flavor-text.lua +++ /dev/null @@ -1,176 +0,0 @@ --- Writes the currently viewed unit or item flavor text to a file. --- --- Usage: --- flavor-text - -local folder = 'flavor text' -local filename = 'read flavor.txt' -local filepath = folder .. '/' .. filename - -local function clear_output_file() - local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) - if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) - end - local file, open_err = io.open(filepath, 'w') - if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) - end - file:write('') - file:close() -end - -local function reformat(str) - local cleaned = str:gsub('%[B%]', '') - :gsub('%[P%]', '') - :gsub('%[R%]', '') - :gsub('%[C:%d+:%d+:%d+%]', '') - :gsub('%s+', ' ') - :gsub('^%s+', '') - :gsub('%s+$', '') - return cleaned -end - -local function collect_lines(entries) - local lines = {} - for _, entry in ipairs(entries) do - if entry.value ~= '' then - local cleaned = reformat(dfhack.df2utf(entry.value)) - if cleaned ~= '' then - table.insert(lines, cleaned) - end - end - end - return lines -end - -local function get_health_text(view_sheets) - if #view_sheets.unit_health_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.unit_health_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local function get_personality_text(view_sheets) - if #view_sheets.personality_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.personality_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local UNIT_SHEET_SUBTAB = { - HEALTH = 2, - PERSONALITY = 10, -} - -local HEALTH_ACTIVE_TAB = { - STATUS = 0, - WOUNDS = 1, - TREATMENT = 2, - HISTORY = 3, - DESCRIPTION = 4, -} - -local PERSONALITY_ACTIVE_TAB = { - TRAITS = 0, - VALUES = 1, - PREFERENCES = 2, - NEEDS = 3, -} - -local function get_unit_flavor_text(view_sheets) - local unit = df.unit.find(view_sheets.active_id) - if not unit then - qerror('Unable to resolve the active unit.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then - local health_text = get_health_text(view_sheets) - if health_text then - return unit, 'Health', health_text - end - clear_output_file() - qerror('No text found on the Health tab.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY - and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) - then - local text = get_personality_text(view_sheets) - if text then - return unit, 'Personality', text - end - clear_output_file() - qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') - end - - clear_output_file() - qerror('Open Health, Personality or an item window before running this script.') -end - -local function get_item_flavor_text(view_sheets) - local item = dfhack.gui.getSelectedItem(true) - if not item then - qerror('Select an item or open an item view sheet before running this script.') - end - - local description = view_sheets.raw_description or '' - if description == '' then - qerror('No item description text found on the item view sheet.') - end - - return item, 'Item', reformat(dfhack.df2utf(description)) -end - -local view_sheets = df.global.game.main_interface.view_sheets -if not view_sheets.open then - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local screen = dfhack.gui.getDFViewscreen() -local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) -local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) - -local subject, flavor_type, text -if is_unit_sheet then - subject, flavor_type, text = get_unit_flavor_text(view_sheets) -elseif is_item_sheet then - subject, flavor_type, text = get_item_flavor_text(view_sheets) -else - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) -if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) -end - -local file, open_err = io.open(filepath, 'w') -if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) -end - -file:write(text) -file:close() - -local name -if is_unit_sheet then - name = dfhack.df2console(dfhack.units.getReadableName(subject)) -else - name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) -end - -print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) From 178cfe15e16dc9baf1a5591bdca5b6d9590931b2 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:25:24 -0600 Subject: [PATCH 04/11] Delete flavor-text.rst --- flavor-text.rst | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 flavor-text.rst diff --git a/flavor-text.rst b/flavor-text.rst deleted file mode 100644 index 9a6a5a4c0..000000000 --- a/flavor-text.rst +++ /dev/null @@ -1,27 +0,0 @@ -flavor-text -=========== - -Overview --------- -The ``flavor-text`` script writes the currently viewed unit or item flavor text to -``flavor text/read flavor.txt``. - -Usage ------ -Run the script from DFHack: - -:: - - flavor-text - -Notes ------ -- The file is overwritten each time the script runs. -- If the wrong window is open, the script clears the output file. -- Supported unit tabs: - - Health (Status/Wounds/Treatment/History/Description) - - Personality (Traits/Values/Preferences/Needs) -- Supported item window: item view sheets. -- You must use your own text-to-speech (TTS) program to read the output. - On Windows 11, Voice Attack works well, and a profile with the launch command - can be included for others to use. From ea8474b10b68fb8857981b4fe0f8001a98092249 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:26:48 -0600 Subject: [PATCH 05/11] Revert "Document external TTS usage for flavor-text" From cf7aab112003fa771d13bd4f821cc47ab274bc92 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:34:19 -0600 Subject: [PATCH 06/11] Add files via upload --- read-flavor.lua | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ read-flavor.rst | 27 ++++++++ 2 files changed, 203 insertions(+) create mode 100644 read-flavor.lua create mode 100644 read-flavor.rst diff --git a/read-flavor.lua b/read-flavor.lua new file mode 100644 index 000000000..11930804e --- /dev/null +++ b/read-flavor.lua @@ -0,0 +1,176 @@ +-- Writes the currently viewed unit or item flavor text to a file. +-- +-- Usage: +-- flavor-text + +local folder = 'flavor text' +local filename = 'read flavor.txt' +local filepath = folder .. '/' .. filename + +local function clear_output_file() + local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) + if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) + end + local file, open_err = io.open(filepath, 'w') + if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) + end + file:write('') + file:close() +end + +local function reformat(str) + local cleaned = str:gsub('%[B%]', '') + :gsub('%[P%]', '') + :gsub('%[R%]', '') + :gsub('%[C:%d+:%d+:%d+%]', '') + :gsub('%s+', ' ') + :gsub('^%s+', '') + :gsub('%s+$', '') + return cleaned +end + +local function collect_lines(entries) + local lines = {} + for _, entry in ipairs(entries) do + if entry.value ~= '' then + local cleaned = reformat(dfhack.df2utf(entry.value)) + if cleaned ~= '' then + table.insert(lines, cleaned) + end + end + end + return lines +end + +local function get_health_text(view_sheets) + if #view_sheets.unit_health_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.unit_health_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local function get_personality_text(view_sheets) + if #view_sheets.personality_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.personality_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local UNIT_SHEET_SUBTAB = { + HEALTH = 2, + PERSONALITY = 10, +} + +local HEALTH_ACTIVE_TAB = { + STATUS = 0, + WOUNDS = 1, + TREATMENT = 2, + HISTORY = 3, + DESCRIPTION = 4, +} + +local PERSONALITY_ACTIVE_TAB = { + TRAITS = 0, + VALUES = 1, + PREFERENCES = 2, + NEEDS = 3, +} + +local function get_unit_flavor_text(view_sheets) + local unit = df.unit.find(view_sheets.active_id) + if not unit then + qerror('Unable to resolve the active unit.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then + local health_text = get_health_text(view_sheets) + if health_text then + return unit, 'Health', health_text + end + clear_output_file() + qerror('No text found on the Health tab.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY + and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) + then + local text = get_personality_text(view_sheets) + if text then + return unit, 'Personality', text + end + clear_output_file() + qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') + end + + clear_output_file() + qerror('Open Health, Personality or an item window before running this script.') +end + +local function get_item_flavor_text(view_sheets) + local item = dfhack.gui.getSelectedItem(true) + if not item then + qerror('Select an item or open an item view sheet before running this script.') + end + + local description = view_sheets.raw_description or '' + if description == '' then + qerror('No item description text found on the item view sheet.') + end + + return item, 'Item', reformat(dfhack.df2utf(description)) +end + +local view_sheets = df.global.game.main_interface.view_sheets +if not view_sheets.open then + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local screen = dfhack.gui.getDFViewscreen() +local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) +local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) + +local subject, flavor_type, text +if is_unit_sheet then + subject, flavor_type, text = get_unit_flavor_text(view_sheets) +elseif is_item_sheet then + subject, flavor_type, text = get_item_flavor_text(view_sheets) +else + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) +if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) +end + +local file, open_err = io.open(filepath, 'w') +if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) +end + +file:write(text) +file:close() + +local name +if is_unit_sheet then + name = dfhack.df2console(dfhack.units.getReadableName(subject)) +else + name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) +end + +print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) diff --git a/read-flavor.rst b/read-flavor.rst new file mode 100644 index 000000000..d6ea04a29 --- /dev/null +++ b/read-flavor.rst @@ -0,0 +1,27 @@ +flavor-text +=========== + +Overview +-------- +The ``flavor-text`` script writes the currently viewed unit or item flavor text to +``flavor text/read flavor.txt``. + +Usage +----- +Run the script from DFHack: + +:: + + flavor-text + +Notes +----- +- The file is overwritten each time the script runs. +- If the wrong window is open, the script clears the output file. +- Supported unit tabs: + - Health (Status/Wounds/Treatment/History/Description) + - Personality (Traits/Values/Preferences/Needs) +- Supported item window: item view sheets. +- You must use your own text-to-speech (TTS) program to read the output. + On Windows 11, Voice Attack works well, and a profile with the launch command + can be included for others to use. From ef77b165ab95bbfc3ff87ef885a70d19308bc63a Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:34:41 -0600 Subject: [PATCH 07/11] Delete read-flavor.rst --- read-flavor.rst | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 read-flavor.rst diff --git a/read-flavor.rst b/read-flavor.rst deleted file mode 100644 index d6ea04a29..000000000 --- a/read-flavor.rst +++ /dev/null @@ -1,27 +0,0 @@ -flavor-text -=========== - -Overview --------- -The ``flavor-text`` script writes the currently viewed unit or item flavor text to -``flavor text/read flavor.txt``. - -Usage ------ -Run the script from DFHack: - -:: - - flavor-text - -Notes ------ -- The file is overwritten each time the script runs. -- If the wrong window is open, the script clears the output file. -- Supported unit tabs: - - Health (Status/Wounds/Treatment/History/Description) - - Personality (Traits/Values/Preferences/Needs) -- Supported item window: item view sheets. -- You must use your own text-to-speech (TTS) program to read the output. - On Windows 11, Voice Attack works well, and a profile with the launch command - can be included for others to use. From 07b833f1d193f280db298576e96d9f34a571a512 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:35:09 -0600 Subject: [PATCH 08/11] Add files via upload --- docs/read-flavor.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/read-flavor.rst diff --git a/docs/read-flavor.rst b/docs/read-flavor.rst new file mode 100644 index 000000000..d6ea04a29 --- /dev/null +++ b/docs/read-flavor.rst @@ -0,0 +1,27 @@ +flavor-text +=========== + +Overview +-------- +The ``flavor-text`` script writes the currently viewed unit or item flavor text to +``flavor text/read flavor.txt``. + +Usage +----- +Run the script from DFHack: + +:: + + flavor-text + +Notes +----- +- The file is overwritten each time the script runs. +- If the wrong window is open, the script clears the output file. +- Supported unit tabs: + - Health (Status/Wounds/Treatment/History/Description) + - Personality (Traits/Values/Preferences/Needs) +- Supported item window: item view sheets. +- You must use your own text-to-speech (TTS) program to read the output. + On Windows 11, Voice Attack works well, and a profile with the launch command + can be included for others to use. From 002679ad2399c582cf8914fd9d682cbdb8e87875 Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:39:43 -0600 Subject: [PATCH 09/11] Update read-flavor.rst --- docs/read-flavor.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/read-flavor.rst b/docs/read-flavor.rst index d6ea04a29..79c2ee195 100644 --- a/docs/read-flavor.rst +++ b/docs/read-flavor.rst @@ -25,3 +25,17 @@ Notes - You must use your own text-to-speech (TTS) program to read the output. On Windows 11, Voice Attack works well, and a profile with the launch command can be included for others to use. + +------------------- +Press ` ~ key and hold for 0.05 seconds and release +Pause 0.05 seconds +Set Windows clipboard to 'read-flavor' +Pause 0.05 seconds +Press Left Ctrl+V keys and hold for 0.05 seconds and release +Pause 0.05 seconds +Press NumPad Enter key and hold for 0.05 seconds and release +Pause 0.05 seconds +Press Escape key and hold for 0.05 seconds and release +Pause 0.05 seconds +Set text [TTS] to [D:\Program Files (x86)\Steam\steamapps\common\Dwarf Fortress\flavor text\read flavor.txt] +Say, '{TXT:TTS}' From 091666f27e179878ad1f53872be7ec62f09bffcc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 23:42:54 +0000 Subject: [PATCH 10/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/read-flavor.rst | 82 +++++----- read-flavor.lua | 352 +++++++++++++++++++++---------------------- 2 files changed, 217 insertions(+), 217 deletions(-) diff --git a/docs/read-flavor.rst b/docs/read-flavor.rst index 79c2ee195..cfd92e93e 100644 --- a/docs/read-flavor.rst +++ b/docs/read-flavor.rst @@ -1,41 +1,41 @@ -flavor-text -=========== - -Overview --------- -The ``flavor-text`` script writes the currently viewed unit or item flavor text to -``flavor text/read flavor.txt``. - -Usage ------ -Run the script from DFHack: - -:: - - flavor-text - -Notes ------ -- The file is overwritten each time the script runs. -- If the wrong window is open, the script clears the output file. -- Supported unit tabs: - - Health (Status/Wounds/Treatment/History/Description) - - Personality (Traits/Values/Preferences/Needs) -- Supported item window: item view sheets. -- You must use your own text-to-speech (TTS) program to read the output. - On Windows 11, Voice Attack works well, and a profile with the launch command - can be included for others to use. - -------------------- -Press ` ~ key and hold for 0.05 seconds and release -Pause 0.05 seconds -Set Windows clipboard to 'read-flavor' -Pause 0.05 seconds -Press Left Ctrl+V keys and hold for 0.05 seconds and release -Pause 0.05 seconds -Press NumPad Enter key and hold for 0.05 seconds and release -Pause 0.05 seconds -Press Escape key and hold for 0.05 seconds and release -Pause 0.05 seconds -Set text [TTS] to [D:\Program Files (x86)\Steam\steamapps\common\Dwarf Fortress\flavor text\read flavor.txt] -Say, '{TXT:TTS}' +flavor-text +=========== + +Overview +-------- +The ``flavor-text`` script writes the currently viewed unit or item flavor text to +``flavor text/read flavor.txt``. + +Usage +----- +Run the script from DFHack: + +:: + + flavor-text + +Notes +----- +- The file is overwritten each time the script runs. +- If the wrong window is open, the script clears the output file. +- Supported unit tabs: + - Health (Status/Wounds/Treatment/History/Description) + - Personality (Traits/Values/Preferences/Needs) +- Supported item window: item view sheets. +- You must use your own text-to-speech (TTS) program to read the output. + On Windows 11, Voice Attack works well, and a profile with the launch command + can be included for others to use. + +------------------- +Press ` ~ key and hold for 0.05 seconds and release +Pause 0.05 seconds +Set Windows clipboard to 'read-flavor' +Pause 0.05 seconds +Press Left Ctrl+V keys and hold for 0.05 seconds and release +Pause 0.05 seconds +Press NumPad Enter key and hold for 0.05 seconds and release +Pause 0.05 seconds +Press Escape key and hold for 0.05 seconds and release +Pause 0.05 seconds +Set text [TTS] to [D:\Program Files (x86)\Steam\steamapps\common\Dwarf Fortress\flavor text\read flavor.txt] +Say, '{TXT:TTS}' diff --git a/read-flavor.lua b/read-flavor.lua index 11930804e..9d171e9b7 100644 --- a/read-flavor.lua +++ b/read-flavor.lua @@ -1,176 +1,176 @@ --- Writes the currently viewed unit or item flavor text to a file. --- --- Usage: --- flavor-text - -local folder = 'flavor text' -local filename = 'read flavor.txt' -local filepath = folder .. '/' .. filename - -local function clear_output_file() - local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) - if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) - end - local file, open_err = io.open(filepath, 'w') - if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) - end - file:write('') - file:close() -end - -local function reformat(str) - local cleaned = str:gsub('%[B%]', '') - :gsub('%[P%]', '') - :gsub('%[R%]', '') - :gsub('%[C:%d+:%d+:%d+%]', '') - :gsub('%s+', ' ') - :gsub('^%s+', '') - :gsub('%s+$', '') - return cleaned -end - -local function collect_lines(entries) - local lines = {} - for _, entry in ipairs(entries) do - if entry.value ~= '' then - local cleaned = reformat(dfhack.df2utf(entry.value)) - if cleaned ~= '' then - table.insert(lines, cleaned) - end - end - end - return lines -end - -local function get_health_text(view_sheets) - if #view_sheets.unit_health_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.unit_health_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local function get_personality_text(view_sheets) - if #view_sheets.personality_raw_str == 0 then - return nil - end - local lines = collect_lines(view_sheets.personality_raw_str) - if #lines == 0 then - return nil - end - return table.concat(lines, '\n') -end - -local UNIT_SHEET_SUBTAB = { - HEALTH = 2, - PERSONALITY = 10, -} - -local HEALTH_ACTIVE_TAB = { - STATUS = 0, - WOUNDS = 1, - TREATMENT = 2, - HISTORY = 3, - DESCRIPTION = 4, -} - -local PERSONALITY_ACTIVE_TAB = { - TRAITS = 0, - VALUES = 1, - PREFERENCES = 2, - NEEDS = 3, -} - -local function get_unit_flavor_text(view_sheets) - local unit = df.unit.find(view_sheets.active_id) - if not unit then - qerror('Unable to resolve the active unit.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then - local health_text = get_health_text(view_sheets) - if health_text then - return unit, 'Health', health_text - end - clear_output_file() - qerror('No text found on the Health tab.') - end - - if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY - and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES - or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) - then - local text = get_personality_text(view_sheets) - if text then - return unit, 'Personality', text - end - clear_output_file() - qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') - end - - clear_output_file() - qerror('Open Health, Personality or an item window before running this script.') -end - -local function get_item_flavor_text(view_sheets) - local item = dfhack.gui.getSelectedItem(true) - if not item then - qerror('Select an item or open an item view sheet before running this script.') - end - - local description = view_sheets.raw_description or '' - if description == '' then - qerror('No item description text found on the item view sheet.') - end - - return item, 'Item', reformat(dfhack.df2utf(description)) -end - -local view_sheets = df.global.game.main_interface.view_sheets -if not view_sheets.open then - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local screen = dfhack.gui.getDFViewscreen() -local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) -local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) - -local subject, flavor_type, text -if is_unit_sheet then - subject, flavor_type, text = get_unit_flavor_text(view_sheets) -elseif is_item_sheet then - subject, flavor_type, text = get_item_flavor_text(view_sheets) -else - clear_output_file() - qerror('Open a unit or item view sheet before running this script.') -end - -local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) -if not ok then - qerror(('Failed to create folder "%s": %s'):format(folder, err)) -end - -local file, open_err = io.open(filepath, 'w') -if not file then - qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) -end - -file:write(text) -file:close() - -local name -if is_unit_sheet then - name = dfhack.df2console(dfhack.units.getReadableName(subject)) -else - name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) -end - -print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) +-- Writes the currently viewed unit or item flavor text to a file. +-- +-- Usage: +-- flavor-text + +local folder = 'flavor text' +local filename = 'read flavor.txt' +local filepath = folder .. '/' .. filename + +local function clear_output_file() + local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) + if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) + end + local file, open_err = io.open(filepath, 'w') + if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) + end + file:write('') + file:close() +end + +local function reformat(str) + local cleaned = str:gsub('%[B%]', '') + :gsub('%[P%]', '') + :gsub('%[R%]', '') + :gsub('%[C:%d+:%d+:%d+%]', '') + :gsub('%s+', ' ') + :gsub('^%s+', '') + :gsub('%s+$', '') + return cleaned +end + +local function collect_lines(entries) + local lines = {} + for _, entry in ipairs(entries) do + if entry.value ~= '' then + local cleaned = reformat(dfhack.df2utf(entry.value)) + if cleaned ~= '' then + table.insert(lines, cleaned) + end + end + end + return lines +end + +local function get_health_text(view_sheets) + if #view_sheets.unit_health_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.unit_health_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local function get_personality_text(view_sheets) + if #view_sheets.personality_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.personality_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + +local UNIT_SHEET_SUBTAB = { + HEALTH = 2, + PERSONALITY = 10, +} + +local HEALTH_ACTIVE_TAB = { + STATUS = 0, + WOUNDS = 1, + TREATMENT = 2, + HISTORY = 3, + DESCRIPTION = 4, +} + +local PERSONALITY_ACTIVE_TAB = { + TRAITS = 0, + VALUES = 1, + PREFERENCES = 2, + NEEDS = 3, +} + +local function get_unit_flavor_text(view_sheets) + local unit = df.unit.find(view_sheets.active_id) + if not unit then + qerror('Unable to resolve the active unit.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.HEALTH then + local health_text = get_health_text(view_sheets) + if health_text then + return unit, 'Health', health_text + end + clear_output_file() + qerror('No text found on the Health tab.') + end + + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.PERSONALITY + and (view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.TRAITS + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.VALUES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.PREFERENCES + or view_sheets.personality_active_tab == PERSONALITY_ACTIVE_TAB.NEEDS) + then + local text = get_personality_text(view_sheets) + if text then + return unit, 'Personality', text + end + clear_output_file() + qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') + end + + clear_output_file() + qerror('Open Health, Personality or an item window before running this script.') +end + +local function get_item_flavor_text(view_sheets) + local item = dfhack.gui.getSelectedItem(true) + if not item then + qerror('Select an item or open an item view sheet before running this script.') + end + + local description = view_sheets.raw_description or '' + if description == '' then + qerror('No item description text found on the item view sheet.') + end + + return item, 'Item', reformat(dfhack.df2utf(description)) +end + +local view_sheets = df.global.game.main_interface.view_sheets +if not view_sheets.open then + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local screen = dfhack.gui.getDFViewscreen() +local is_unit_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/UNIT', screen) +local is_item_sheet = dfhack.gui.matchFocusString('dwarfmode/ViewSheets/ITEM', screen) + +local subject, flavor_type, text +if is_unit_sheet then + subject, flavor_type, text = get_unit_flavor_text(view_sheets) +elseif is_item_sheet then + subject, flavor_type, text = get_item_flavor_text(view_sheets) +else + clear_output_file() + qerror('Open a unit or item view sheet before running this script.') +end + +local ok, err = pcall(dfhack.filesystem.mkdir_recursive, folder) +if not ok then + qerror(('Failed to create folder "%s": %s'):format(folder, err)) +end + +local file, open_err = io.open(filepath, 'w') +if not file then + qerror(('Failed to open file "%s" for writing: %s'):format(filepath, open_err)) +end + +file:write(text) +file:close() + +local name +if is_unit_sheet then + name = dfhack.df2console(dfhack.units.getReadableName(subject)) +else + name = dfhack.df2console(dfhack.items.getDescription(subject, 0, true)) +end + +print(('Wrote %s flavor text for %s to "%s".'):format(flavor_type, name, filepath)) From 4175036e717f714e40449985f87f960b8f609f0a Mon Sep 17 00:00:00 2001 From: unboundlopez <47876628+unboundlopez@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:08:14 -0600 Subject: [PATCH 11/11] Add skill knowledge flavor text --- read-flavor.lua | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/read-flavor.lua b/read-flavor.lua index 9d171e9b7..ade83f122 100644 --- a/read-flavor.lua +++ b/read-flavor.lua @@ -68,6 +68,7 @@ end local UNIT_SHEET_SUBTAB = { HEALTH = 2, + SKILLS = 3, PERSONALITY = 10, } @@ -86,6 +87,21 @@ local PERSONALITY_ACTIVE_TAB = { NEEDS = 3, } +local SKILL_ACTIVE_TAB = { + KNOWLEDGE = 4, +} + +local function get_skill_text(view_sheets) + if #view_sheets.skill_description_raw_str == 0 then + return nil + end + local lines = collect_lines(view_sheets.skill_description_raw_str) + if #lines == 0 then + return nil + end + return table.concat(lines, '\n') +end + local function get_unit_flavor_text(view_sheets) local unit = df.unit.find(view_sheets.active_id) if not unit then @@ -115,8 +131,19 @@ local function get_unit_flavor_text(view_sheets) qerror('No text found on the Personality subtab (Traits/Values/Preferences/Needs).') end + if view_sheets.active_sub_tab == UNIT_SHEET_SUBTAB.SKILLS + and view_sheets.unit_skill_active_tab == SKILL_ACTIVE_TAB.KNOWLEDGE + then + local text = get_skill_text(view_sheets) + if text then + return unit, 'Skill', text + end + clear_output_file() + qerror('No text found on the Knowledge subtab.') + end + clear_output_file() - qerror('Open Health, Personality or an item window before running this script.') + qerror('Open Health, Personality, Skills, or an item window before running this script.') end local function get_item_flavor_text(view_sheets)