diff --git a/changelog.txt b/changelog.txt index 3ca33c1fa..a1bc915e9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -36,6 +36,7 @@ Template for new versions: - `gui/spectate`: added "Prefer nicknamed" to the list of options - `gui/mod-manager`: when run in a loaded world, shows a list of active mods -- click to export the list to the clipboard for easy sharing or posting - `gui/blueprint`: now records zone designations +- `gui/journal`: now working on worldmap and legends, before embark -- journal is per world map, stored even if you decided to not embark ## Fixes - `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session diff --git a/gui/journal.lua b/gui/journal.lua index 4faacb218..45e78f5e9 100644 --- a/gui/journal.lua +++ b/gui/journal.lua @@ -313,15 +313,17 @@ function JournalScreen:onDismiss() end function main(options) - if not dfhack.isMapLoaded() or (not dfhack.world.isFortressMode() - and not dfhack.world.isAdventureMode()) then - qerror('journal requires a fortress/adventure map to be loaded') + local journal_context_mode = journal_context.detect_journal_context_mode() + + if journal_context_mode == nil then + qerror('journal requires a fortress/adventure/world/legends map to be loaded') end local save_layout = options and options.save_layout local overrided_context_mode = options and options.context_mode + local context_mode = overrided_context_mode == nil and - journal_context.detect_journal_context_mode() or overrided_context_mode + journal_context_mode or overrided_context_mode view = view and view:raise() or JournalScreen{ save_prefix=options and options.save_prefix or '', @@ -330,6 +332,23 @@ function main(options) }:show() end +local last_viewscreen_focus = nil +local SCREEN_SETUP_FORTRESS = 'setupdwarfgame/Default' + +dfhack.onStateChange['gui/journal'] = function (sc) + if view and sc == SC_VIEWSCREEN_CHANGED then + local scr = dfhack.gui.getDFViewscreen(true) + local curr_viewscreen_focus = dfhack.gui.getFocusStrings(scr)[1] + if last_viewscreen_focus == SCREEN_SETUP_FORTRESS and + curr_viewscreen_focus ~= last_viewscreen_focus then + -- hide worldmap journal when embark on fortress is done + view:dismiss() + end + + last_viewscreen_focus = curr_viewscreen_focus + end +end + if not dfhack_flags.module then main() end diff --git a/internal/journal/contexts/adventure.lua b/internal/journal/contexts/adventure.lua index 9957cbb61..aa0e68ac8 100644 --- a/internal/journal/contexts/adventure.lua +++ b/internal/journal/contexts/adventure.lua @@ -25,7 +25,8 @@ Those headers will appear here, and you can click on them to jump to them in the AdventurerJournalContext = defclass(AdventurerJournalContext) AdventurerJournalContext.ATTRS{ save_prefix='', - adventurer_id=DEFAULT_NIL + adventurer_id=DEFAULT_NIL, + worldmap_journal_context=DEFAULT_NIL } function get_adventurer_context_key(prefix, adventurer_id) @@ -52,6 +53,10 @@ function AdventurerJournalContext:load_content() ) or {} if not world_data.text then + if self.worldmap_journal_context then + return self.worldmap_journal_context:load_content() + end + world_data.text={''} world_data.show_tutorial = true end diff --git a/internal/journal/contexts/fortress.lua b/internal/journal/contexts/fortress.lua index 46a28f568..0410d2e77 100644 --- a/internal/journal/contexts/fortress.lua +++ b/internal/journal/contexts/fortress.lua @@ -24,7 +24,8 @@ Those headers will appear here, and you can click on them to jump to them in the FortressJournalContext = defclass(FortressJournalContext) FortressJournalContext.ATTRS{ - save_prefix='' + save_prefix='', + worldmap_journal_context=DEFAULT_NIL } function get_fort_context_key(prefix) @@ -47,9 +48,14 @@ function FortressJournalContext:load_content() ) or {} if not site_data.text then - site_data.text={''} - site_data.show_tutorial = true + if self.worldmap_journal_context then + return self.worldmap_journal_context:load_content() + end + + site_data.text={''} + site_data.show_tutorial = true end + site_data.cursor = site_data.cursor or {#site_data.text[1] + 1} return site_data end diff --git a/internal/journal/contexts/worldmap.lua b/internal/journal/contexts/worldmap.lua new file mode 100644 index 000000000..7924b2512 --- /dev/null +++ b/internal/journal/contexts/worldmap.lua @@ -0,0 +1,74 @@ +--@ module = true + +local json = require 'json' + +local JOURNAL_WELCOME_COPY = [=[ +Welcome to gui/journal, your planning scroll for the world of Dwarf Fortress! + +Here, you can outline your fortress ideas, compare embark sites, or record thoughts before founding your settlement. +The text you write here is saved together with your world - even if you cancel the embark. + +For guidance on navigation and hotkeys, tap the ? button in the upper right corner. +Strike the earth! +]=] + +local TOC_WELCOME_COPY = [=[ +Start a line with # symbols and a space to create a header. For example: + +# My section heading + +or + +## My section subheading + +Those headers will appear here, and you can click on them to jump to them in the text.]=] + +worldmap_config = worldmap_config or json.open('dfhack-config/journal-context.json') + +WorldmapJournalContext = defclass(WorldmapJournalContext) +WorldmapJournalContext.ATTRS{ + save_prefix='', + world_id=DEFAULT_NIL +} + +function get_worldmap_context_key(prefix, world_id) + return prefix .. 'world:' .. world_id +end + +function WorldmapJournalContext:save_content(text, cursor) + if dfhack.isWorldLoaded() then + local key = get_worldmap_context_key(self.save_prefix, self.world_id) + worldmap_config.data[key] = {text={text}, cursor={cursor}} + worldmap_config:write() + end +end + +function WorldmapJournalContext:load_content() + if dfhack.isWorldLoaded() then + local key = get_worldmap_context_key(self.save_prefix, self.world_id) + local worldmap_data = copyall(worldmap_config.data[key] or {}) + + if not worldmap_data.text or #worldmap_data.text[1] == 0 then + worldmap_data.text={''} + worldmap_data.show_tutorial = true + end + worldmap_data.cursor = worldmap_data.cursor or {#worldmap_data.text[1] + 1} + return worldmap_data + end +end + +function WorldmapJournalContext:delete_content() + if dfhack.isWorldLoaded() then + local key = get_worldmap_context_key(self.save_prefix, self.world_id) + worldmap_config.data[key] = nil + worldmap_config:write() + end +end + +function WorldmapJournalContext:welcomeCopy() + return JOURNAL_WELCOME_COPY +end + +function WorldmapJournalContext:tocWelcomeCopy() + return TOC_WELCOME_COPY +end diff --git a/internal/journal/journal_context.lua b/internal/journal/journal_context.lua index 989e10f06..2bd9162bd 100644 --- a/internal/journal/journal_context.lua +++ b/internal/journal/journal_context.lua @@ -1,30 +1,43 @@ --@ module = true local widgets = require 'gui.widgets' -local utils = require('utils') +local utils = require 'utils' local DummyJournalContext = reqscript('internal/journal/contexts/dummy').DummyJournalContext local FortressJournalContext = reqscript('internal/journal/contexts/fortress').FortressJournalContext +local WorldmapJournalContext = reqscript('internal/journal/contexts/worldmap').WorldmapJournalContext local AdventurerJournalContext = reqscript('internal/journal/contexts/adventure').AdventurerJournalContext JOURNAL_CONTEXT_MODE = { FORTRESS='fortress', ADVENTURE='adventure', + WORLDMAP='worldmap', + LEGENDS='legends', DUMMY='dummy' } function detect_journal_context_mode() - if dfhack.world.isFortressMode() then + if not dfhack.isMapLoaded() and dfhack.world.isFortressMode() then + return JOURNAL_CONTEXT_MODE.WORLDMAP + elseif dfhack.isMapLoaded() and dfhack.world.isFortressMode() then return JOURNAL_CONTEXT_MODE.FORTRESS - elseif dfhack.world.isAdventureMode() then + elseif dfhack.isMapLoaded() and dfhack.world.isAdventureMode() then return JOURNAL_CONTEXT_MODE.ADVENTURE + elseif dfhack.world.isLegends() then + return JOURNAL_CONTEXT_MODE.LEGENDS else - qerror('unsupported game mode') + return nil end end function journal_context_factory(journal_context_mode, save_prefix) + local world_id = df.global.world.cur_savegame.world_header.id1 + local worldmap_journal_context = WorldmapJournalContext{save_prefix=save_prefix, world_id=world_id} + if journal_context_mode == JOURNAL_CONTEXT_MODE.FORTRESS then - return FortressJournalContext{save_prefix} + return FortressJournalContext{ + save_prefix=save_prefix, + worldmap_journal_context=worldmap_journal_context + } elseif journal_context_mode == JOURNAL_CONTEXT_MODE.ADVENTURE then local interactions = df.global.adventure.interactions if #interactions.party_core_members == 0 or interactions.party_core_members[0] == nil then @@ -34,9 +47,14 @@ function journal_context_factory(journal_context_mode, save_prefix) local adventurer_id = interactions.party_core_members[0] return AdventurerJournalContext{ - save_prefix, - adventurer_id=adventurer_id + save_prefix=save_prefix, + adventurer_id=adventurer_id, + worldmap_journal_context=worldmap_journal_context } + elseif journal_context_mode == JOURNAL_CONTEXT_MODE.WORLDMAP then + return worldmap_journal_context + elseif journal_context_mode == JOURNAL_CONTEXT_MODE.LEGENDS then + return worldmap_journal_context elseif journal_context_mode == JOURNAL_CONTEXT_MODE.DUMMY then return DummyJournalContext{} else diff --git a/test/gui/journal.lua b/test/gui/journal.lua index 7d3d101bb..de023770c 100644 --- a/test/gui/journal.lua +++ b/test/gui/journal.lua @@ -237,6 +237,88 @@ function test.restore_text_between_sessions() journal:dismiss() end +function test.restore_text_between_worldmap_sessions() + local journal, text_area = arrange_empty_journal({ + w=80, + context_mode=gui_journal.JOURNAL_CONTEXT_MODE.WORLDMAP + }) + + simulate_input_keys('CUSTOM_CTRL_A') + simulate_input_keys('CUSTOM_DELETE') + + local text = table.concat({ + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + '112: Sed consectetur, urna sit amet aliquet egestas,', + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, '\n') + + simulate_input_text(text) + simulate_mouse_click(text_area, 10, 1) + + expect.eq(read_rendered_text(text_area), table.concat({ + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + '112: Sed c_nsectetur, urna sit amet aliquet egestas,', + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, '\n')); + + journal:dismiss() + + journal, text_area = arrange_empty_journal({ + w=80, + context_mode=gui_journal.JOURNAL_CONTEXT_MODE.WORLDMAP + }) + + expect.eq(read_rendered_text(text_area), table.concat({ + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + '112: Sed c_nsectetur, urna sit amet aliquet egestas,', + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, '\n')); + + journal:dismiss() +end + +function test.load_worldmap_text_as_fortress_default() + local save_prefix = 'test:' + local fortress_context = journal_context.journal_context_factory( + gui_journal.JOURNAL_CONTEXT_MODE.FORTRESS, + save_prefix + ) + -- reset saved data + fortress_context:delete_content() + + local worldmap_journal, worldmap_text_area = arrange_empty_journal({ + w=80, + context_mode=gui_journal.JOURNAL_CONTEXT_MODE.WORLDMAP + }) + + local text = table.concat({ + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + '112: Sed c_nsectetur, urna sit amet aliquet egestas,', + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, '\n') + + simulate_input_keys('CUSTOM_CTRL_A') + simulate_input_keys('CUSTOM_DELETE') + + simulate_input_text(text) + simulate_mouse_click(worldmap_text_area, 10, 1) + + worldmap_journal:dismiss() + + local journal, text_area = arrange_empty_journal({ + w=80, + context_mode=gui_journal.JOURNAL_CONTEXT_MODE.FORTRESS + }) + + expect.eq(read_rendered_text(text_area), table.concat({ + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + '112: Sed c_nsectetur, urna sit amet aliquet egestas,', + '60: Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, '\n')); + + journal:dismiss() +end + function test.generate_table_of_contents() local journal, text_area = arrange_empty_journal({w=100, h=10}) @@ -610,6 +692,12 @@ function test.show_fortress_tutorials_on_first_use() ) -- reset saved data context:delete_content() + local worldmap_context = journal_context.journal_context_factory( + gui_journal.JOURNAL_CONTEXT_MODE.WORLDMAP, + save_prefix + ) + -- reset worldmap saved data + worldmap_context:delete_content() local journal, text_area, journal_window = arrange_empty_journal({ w=125, @@ -636,3 +724,25 @@ function test.show_fortress_tutorials_on_first_use() expect.str_find('Section 1\n', read_rendered_text(toc_panel)); journal:dismiss() end + +function test.dismiss_on_embark() + -- setupdwarfgame/Default + local journal, text_area, journal_window = arrange_empty_journal({w=125}) + + simulate_input_text(' ') + + expect.eq(read_rendered_text(text_area), ' _'); + + mock.patch(dfhack.gui, 'getFocusStrings', function (pos) + return {'setupdwarfgame/Default'} + end, function () + -- it would be preferable to trigger real view screen somehow, + -- but such simulation is better than nothing + gui_journal.dfhack.onStateChange['gui/journal'](SC_VIEWSCREEN_CHANGED) + end) + + expect.eq(journal:isDismissed(), false) + gui_journal.dfhack.onStateChange['gui/journal'](SC_VIEWSCREEN_CHANGED) + + expect.eq(journal:isDismissed(), true) +end