Skip to content

Feat/worldmap journal #1449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
Expand Down
27 changes: 23 additions & 4 deletions gui/journal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 '',
Expand All @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if u see a better way to detect that embark is finished

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In gui/embark-anywhere and exportlegends, I mask the vanilla action buttons and intercept clicks on them to dismiss the window when the vanilla viewscreen is about to change state. Would that be possible here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would be possible, but I am afraid such solution is too volatile.
UX can easly change, which will break the behaviour. And we do not have a way to test such behaviour.
I think it's much more realistic risk that change in the focus string name.

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
74 changes: 74 additions & 0 deletions internal/journal/contexts/worldmap.lua
Original file line number Diff line number Diff line change
@@ -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)
table.remove(worldmap_config.data, key)
worldmap_config:write()
end
end

function WorldmapJournalContext:welcomeCopy()
return JOURNAL_WELCOME_COPY
end

function WorldmapJournalContext:tocWelcomeCopy()
return TOC_WELCOME_COPY
end
27 changes: 21 additions & 6 deletions internal/journal/journal_context.lua
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
--@ 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)
if journal_context_mode == JOURNAL_CONTEXT_MODE.FORTRESS then
return FortressJournalContext{save_prefix}
return FortressJournalContext{save_prefix=save_prefix}
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
Expand All @@ -34,9 +41,17 @@ function journal_context_factory(journal_context_mode, save_prefix)
local adventurer_id = interactions.party_core_members[0]

return AdventurerJournalContext{
save_prefix,
save_prefix=save_prefix,
adventurer_id=adventurer_id
}
elseif journal_context_mode == JOURNAL_CONTEXT_MODE.WORLDMAP then
local world_id = df.global.world.cur_savegame.world_header.id1

return WorldmapJournalContext{save_prefix=save_prefix, world_id=world_id}
elseif journal_context_mode == JOURNAL_CONTEXT_MODE.LEGENDS then
local world_id = df.global.world.cur_savegame.world_header.id1

return WorldmapJournalContext{save_prefix=save_prefix, world_id=world_id}
elseif journal_context_mode == JOURNAL_CONTEXT_MODE.DUMMY then
return DummyJournalContext{}
else
Expand Down
62 changes: 62 additions & 0 deletions test/gui/journal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,46 @@ 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.generate_table_of_contents()
local journal, text_area = arrange_empty_journal({w=100, h=10})

Expand Down Expand Up @@ -636,3 +676,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