Talk:Topic
Jump to navigation
Jump to search
Reverse engineering[edit source]
I'm looking into precisely how infos are randomized. Findings so far:
- If a Topic Info's "Hours Until Reset" cooldown is active, then the selection algorithm acts as though that Topic Info doesn't even exist. This means that flags like "Random End" won't influence randomization.
- Ditto for if the info is a deleted record, or if it's flagged as "Say Once" but doesn't belong to the Player Dialogue tab.
Using Lua as pseudocode:
function topic:pick_info(
result, -- C++ bool&; Lua can't do that so let's imagine it's a table like { _value = ... }
subject, -- ref
target, -- ref
arg4, -- bool
arg5, -- optional topic
arg6 -- optional unknown struct (intrusive linked list; possibly related to ExtraSayToTopicInfo)
)
result._value = false
if self:is_deleted() then
return nil
end
local infos_done_before_repeating = {}
local final_candidate_topic_infos = {}
local random_ended = false
local last_seen_candidate = nil
if self.owning_quest
and (self.owning_quest.quest_flags & 1) -- started
and !(self.owning_quest.quest_flags & (1 << 7)) -- Stage Wait
and !self.owning_quest.promote_task
then
if self.owning_quest.dialogue_conditions:IsTrue(subject, target, self.owning_quest) then
for _, info in ipairs(self.infos) do
local esp13 = { _value = false } -- in C++, another raw bool that gets passed by reference
if info and (not info:is_in_cooldown()) then
if info:check_conditions(esp13, subject, target) then
if info.is_random_end then
random_ended = true
end
if arg4 or (arg5 and TOPIC_SUBTYPE_DEFINITIONS[arg5.subtype].unk04 == 0) then
while arg6 then
if not arg6.next and not arg6.data then
break
end
if arg6.data.unk0C == info then
goto after_gather_for_randomization
end
if arg6.data.unk14 ~= self.owning_quest then
goto continue
end
if arg6.data.unk10 == self then
goto after_gather_for_randomization
end
::continue::
arg6 = arg6.next
end
end
if not info.is_random then
if #final_candidate_topic_infos == 0 then
return info
end
break
end
if this.do_all_before_repeating and info.info_flag_15 then
infos_done_before_repeating[#infos_done_before_repeating + 1] = info
else
final_candidate_topic_infos[#final_candidate_topic_infos + 1] = info
end
end
end
::after_gather_for_randomization::
if (not arg4) and esp13._value then
--
-- Boolean `esp13` is false if the info is deleted, a misplaced "Say Once" info, etc..
-- I'm not sure what happens to this boolean otherwise, i.e. if the info is able to
-- try its conditions but then the conditions don't match.
--
result._value = true
last_seen_candidate = info
end
if random_ended then
break
end
end
end
end
if self.do_all_before_repeating and #final_candidate_topic_infos == 0 then
for _, info in ipairs(infos_done_before_repeating) do
info.info_flag_15 = false -- sentinel bit 0x8000 in the info flags mask
end
final_candidate_topic_infos = infos_done_before_repeating
end
if #final_candidate_topic_infos > 0 then
if #final_candidate_topic_infos == 1 then
return final_candidate_topic_infos[0]
end
-- Pick a random candidate:
return final_candidate_topic_infos[math.random(#final_candidate_topic_infos)]
end
if arg4 then
return nil
end
return last_seen_candidate
end
DavidJCobb (talk) 01:29, 8 April 2026 (EDT)
- In particular, static analysis suggests that if a Topic Info's conditions don't test as true, then the presence of the "Random End" flag or the absence of the "Random" flag on that info don't affect anything. For example, given the following infos:
Caption text Conditions met Flags Text Yes Random This is Line A Yes Random This is Line B No Random, Random End This is Line C Yes Random This is Line D No This is Line E Yes Random, Random End This is Line F
- Manual examination of the compiled code suggests that it would be possible for an NPC to say any of A, B, D, and F; the intervening Random-End and non-random lines fail to break the topic into separately randomized groups because those lines' conditions aren't met and their flags therefore do not influence the random selection. This is very counterintuitive, particularly given how infos are documented to work on the main page, but it aligns with the behavior I've been seeing while working on a mod with lots of infos and very complex info conditions. DavidJCobb (talk) 01:52, 8 April 2026 (EDT)
- Just to really drive the point home, here's a screenshot of that table with some scribbling on it to more clearly denote the randomized line groups you would expect versus those that the game actually uses [per my reverse engineering and what behaviors I'm seeing in-game] given the condition results per line:

- As for how this behavior arose? You'd only run into problems with this if you have a topic consisting solely of groups of randomized lines, wherein some groups have overlapping conditions, not all lines within a group have the exact same conditions, and multiple groups have their Random End lines fail to match conditions. The case that led me to run into this was a topic that checks an NPC's location and reports it to the player, with variable wording based on the NPC's gender. The topic contained one group of randomized lines per location, but lines within a group were interleaved by gender (i.e. male/female/male/female), such that the "Random End" flag was placed on female lines, and therefore asking about a male NPC could cause you to hear lines from multiple groups (including the "I don't know where he is" group). It's quite conceivable that the behavior I'm documenting here -- the flags being ignored -- is literally just a bug that Bethesda never ran into due to how specific things have to get for this to actually cause problems.
- DavidJCobb (talk) 02:27, 8 April 2026 (EDT)