Skip to content
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

Adv-Rumors additional conversation options #1182

Merged
merged 12 commits into from
Jul 2, 2024
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Template for new versions:
- `gui/notify`: new notification type: injured citizens; click to zoom to injured units; also displays a warning if your hospital is not functional (or if you have no hospital)
- `prioritize`: new info panel on under-construction buildings showing if the construction job has been taken and by whom. click to zoom to builder; toggle high priority status for job if it's not yet taken and you need it to be built ASAP
- `gui/pathable`: new "Depot" mode that shows whether wagons can path to your trade depot
- `advtools`: convo - add a new conversation option to "ask whereabouts of" for all your relationships (before, you could only ask whereabouts of people involved in rumors only)
myk002 marked this conversation as resolved.
Show resolved Hide resolved
- `gui/design`: all-new visually-driven UI for much improved usability

## Fixes
Expand Down
5 changes: 4 additions & 1 deletion docs/advtools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,7 @@ framework. They can be repositioned via `gui/overlay` or toggled via

When enabled, this overlay will automatically add additional searchable
keywords to conversation topics. In particular, topics that relate to slain
enemies will gain the ``slay`` and ``kill`` keywords.
enemies will gain the ``slay`` and ``kill`` keywords. It will also add additional
conversation options for asking whereabouts of your relationships - in vanilla,
you can only ask whereabouts of historical figures involved in rumors you personally
witnessed or heard about.
117 changes: 113 additions & 4 deletions internal/advtools/convo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ local adventure = df.global.game.main_interface.adventure
-- Gets the keywords already present on the dialog choice
local function getKeywords(choice)
local keywords = {}
for _, keyword in ipairs(choice.key_word) do
for _, keyword in ipairs(choice.keywords) do
table.insert(keywords, keyword.value:lower())
end
return keywords
Expand All @@ -22,8 +22,8 @@ end
-- Adds a keyword to the dialog choice
local function addKeyword(choice, keyword)
local keyword_ptr = df.new('string')
keyword_ptr.value = keyword
choice.key_word:insert('#', keyword_ptr)
keyword_ptr.value = dfhack.toSearchNormalized(keyword)
choice.keywords:insert('#', keyword_ptr)
end

-- Adds multiple keywords to the dialog choice
Expand All @@ -45,7 +45,7 @@ local function generateKeywordsForChoice(choice)
end

-- generate keywords from useful words in the text
for _, data in ipairs(choice.print_string.text) do
for _, data in ipairs(choice.title.text) do
for word in dfhack.toSearchNormalized(data.value):gmatch('%w+') do
-- collect additional keywords based on the special words
if word == 'slew' or word == 'slain' then
Expand All @@ -61,8 +61,111 @@ local function generateKeywordsForChoice(choice)
addKeywords(choice, new_keywords)
end

-- Helper function to create new dialog choices, returns the created choice
local function new_choice(choice_type, title, keywords)
local choice = df.adventure_conversation_choice_infost:new()
choice.cc = df.talk_choice:new()
choice.cc.type = choice_type
local text = df.new("string")
text.value = title
choice.title.text:insert("#", text)

if keywords ~= nil then
addKeywords(choice, keywords)
end
return choice
end

local function addHistFigWhereaboutsChoice(profile)
for i, c in pairs(adventure.conversation.conv_choice_info) do
Crystalwarrior marked this conversation as resolved.
Show resolved Hide resolved
if c.cc.type == df.talk_choice_type.AskWhereabouts and
c.cc.invocation_target_hfid ~= -1 and
Copy link
Member

Choose a reason for hiding this comment

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

if profile.histfig_id is never equal to -1, then you can remove the check for -1

c.cc.invocation_target_hfid == profile.histfig_id then
-- Don't add repeat entries
return
end
end

local histfig = df.historical_figure.find(profile.histfig_id)
local name = ""
local creature = df.creature_raw.find(histfig.race)
if creature then
local caste = creature.caste[histfig.caste]
name = caste.caste_name[0]
end
local title = "Ask for the whereabouts of the " .. name .. " " .. dfhack.TranslateName(histfig.name, true)
Copy link
Member

Choose a reason for hiding this comment

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

do we only need the english name here? are they never referenced by their native name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In vanilla convo system yeah they're only referenced by their English name

Copy link
Member

Choose a reason for hiding this comment

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

is that what you want, though? @Ozzatron what do you think?

if profile._type == df.relationship_profile_hf_historicalst then
title = title .. " (Heard of)"
end
local choice = new_choice(df.talk_choice_type.AskWhereabouts, title, dfhack.TranslateName(histfig.name):split())
Copy link
Member

Choose a reason for hiding this comment

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

why are we adding keywords for the untranslated name if the translated name appears in the text itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Vanilla keyword behavior

Copy link
Member

Choose a reason for hiding this comment

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

and the translated name is already in the keyword list?

-- insert before the last choice, which is usually "back"
adventure.conversation.conv_choice_info:insert(#adventure.conversation.conv_choice_info-1, choice)
choice.cc.invocation_target_hfid = histfig.id
end

local function addIdentityWhereaboutsChoice(identity)
for i, c in pairs(adventure.conversation.conv_choice_info) do
Crystalwarrior marked this conversation as resolved.
Show resolved Hide resolved
if c.cc.type == df.talk_choice_type.AskWhereabouts and
c.cc.invocation_target_hfid ~= -1 and
Copy link
Member

Choose a reason for hiding this comment

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

same as above -- if identity.impersonated_hf is never -1, then we don't need this check

Copy link
Contributor Author

@Crystalwarrior Crystalwarrior Jun 30, 2024

Choose a reason for hiding this comment

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

It's possible for impersonated hf identity to be -1

c.cc.invocation_target_hfid == identity.impersonated_hf then
-- Don't add repeat entries
return
end
end
local identity_name = identity.name
local name = ""
local creature = df.creature_raw.find(identity.race)
if creature then
local caste = creature.caste[identity.caste]
name = caste.caste_name[0]
else
-- no race given for the identity, assume it's the histfig
local histfig = df.historical_figure.find(identity.histfig_id)
creature = df.creature_raw.find(histfig.race)
if creature then
local caste = creature.caste[histfig.caste]
name = caste.caste_name[0]
end
end
local title = "Ask for the whereabouts of the " .. name .. " " .. dfhack.TranslateName(identity_name, true)
local choice = new_choice(df.talk_choice_type.AskWhereabouts, title)
-- insert before the last choice, which is usually "back"
adventure.conversation.conv_choice_info:insert(#adventure.conversation.conv_choice_info-1, choice, dfhack.TranslateName(identity_name):split())
choice.cc.invocation_target_hfid = identity.impersonated_hf
end
Copy link
Member

Choose a reason for hiding this comment

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

this duplicates too much of the logic in addHistFigWhereaboutsChoice; please refactor


-- Condense the rumor system choices
local function rumorUpdate()
local conversation_state = adventure.conversation.conv_act.events[0].menu
-- add new conversation options depending on state

-- If we're asking about directions, add ability to ask about all our relationships - visual, historical and identities.
-- In vanilla, we're only allowed to ask for directions to people we learned in anything that's added to df.global.adventure.rumor
if conversation_state == df.conversation_state_type.AskDirections then
local adventurer_figure = df.historical_figure.find(dfhack.world.getAdventurer().hist_figure_id)
local relationships = adventurer_figure.info.relationships

local visual = relationships.hf_visual
local historical = relationships.hf_historical
local identity = relationships.hf_identity

for _, profile in pairs(visual) do
addHistFigWhereaboutsChoice(profile)
end

-- This option will likely always fail unless the false identity is impersonating someone
-- but giving away the false identity's true historical figure feels cheap.
for _, profile in pairs(identity) do
addIdentityWhereaboutsChoice(df.identity.find(profile.identity_id))
end

-- Historical entities go last so as to not give away fake identities
for _, profile in pairs(historical) do
addHistFigWhereaboutsChoice(profile)
end
Copy link
Member

Choose a reason for hiding this comment

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

with the loop through adventure.conversation.conv_choice_info, each of these has quadradic runtime complexity. perhaps it would be a good idea to make a single loop through adventure.conversation.conv_choice_info and cache the results? (and update the cache as you add more entries)

Copy link
Member

Choose a reason for hiding this comment

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

also, ipairs is semantically appropriate for vectors, not pairs

end

-- generate extra keywords for all options
Copy link
Member

Choose a reason for hiding this comment

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

if this is being run unconditionally down here, why are you adding keywords in new_choice? can the call to add_keywords be removed from new_choice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is because new_choice might have its own "vanilla"-style added keywords which is just a few, while this section ensures more searchability

for i, choice in ipairs(adventure.conversation.conv_choice_info) do
generateKeywordsForChoice(choice)
end
Expand All @@ -78,6 +181,12 @@ AdvRumorsOverlay.ATTRS{
frame={w=0, h=0},
}

local last_first_entry = nil
function AdvRumorsOverlay:render()
-- Only update if the first entry pointer changed, this reliably indicates the list changed
if #adventure.conversation.conv_choice_info <= 0 or last_first_entry == adventure.conversation.conv_choice_info[0] then return end

-- Remember the last first entry. This entry changes even if we quit out and return on the same menu!
last_first_entry = adventure.conversation.conv_choice_info[0]
rumorUpdate()
end