Skip to content

Rework of party member quest progression PR #769#848

Open
rfortier wants to merge 5 commits intotiltedphoques:devfrom
rfortier:fix/Rework-PR-769-party-member-quest-progression
Open

Rework of party member quest progression PR #769#848
rfortier wants to merge 5 commits intotiltedphoques:devfrom
rfortier:fix/Rework-PR-769-party-member-quest-progression

Conversation

@rfortier
Copy link
Contributor

@rfortier rfortier commented Jan 7, 2026

The community loves Members being able to advance quests (instead of only Party Leader), but it introduced a bunch of problems with duplicate quest triggers. This rework deals with it (hopefully).

A large credit goes to co-author @miredirex for reverse engineering a couple more methods to make this work, especially having more awareness of when a Player is in a scene (BGSScene) or not. That's one key to making cutscenes sync, or at least sync much, much better. That, and deduplicating the storm of duplicate attempts to advance the quest stage that was introduced with Member-can-progress-quests PR #769. That introduced several bugs that are fixed here.

A lot of the effort in this PR was figuring out the real-world situations in which quests can and cannot rewind, and making sure the fix supported that. You can find the information in ck.uesp.net starting with SetStage(), but it isn't all consolidated in one place. Find comments in the code, it's too long and complicated to re-explain here (likely introducing errors)

The most fundamental problem found in the old code is that ScopedQuestOverride() doesn't work, because in many cases quest progress is executed on a different thread; the ScopedQuestOverride() is destructed before the quest OnEvent...() routines are invoked. So all the Members send their updates to the server even when told not to. The server just ignored them all before, but now with Member quest progression enabled, it ends up causing replicated quest updates. Some quests ignore it, many break.

The engine is in control of the content of events passed to the OnEvent routines. I could not find a reliable way to pass any context to the OnEvent routines through that barrier (it runs async, so matching up was nigh-on impossible). So ended up implementing a dedup history server-side, where there is enough context to figure this out.

The worst-case flow (when a Member triggers progress), is:

  1. Member sends update.
  2. Update is forwarded to Leader, only.
  3. Leader OnEvent hooks send the Leader update to the server.
  4. Leader SendsToParty
  5. Party members reflect their updates to the Server, but are dropped by the dedup history because the Leader has already sent it.
  6. Entries in the dedup history time out in seconds (needs some tuning), so quests can rewind and stages can be triggered multiple times to evaluate.

There's also logic to keep track of the SceneMaster, the player that initiated the scene. This has proven to be less useful than hope, it was to be used to sync everyone's Scene state to the SceneMaster. But the scene start/stop routines have so many side-effects, the logic made things worse more often than better. So now, state is tracked in case we find more use for it, but largely doesn't do anything.

The one exception: players can get stuck in dialogues that have been answered by the SceneMaster. Most times, they are fixed by a quest stage transition, but I have seen scenes that don't end with a quest stage change. So Scene End triggers a forced quest stage change. It's ignored by the client if they've already done it, so this one helps more than it hurts.

Partial support for Remote actors to run AI packages when in-Scene That comment in the commit is obsolete, the support was yanked as too incomplete.

@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch 2 times, most recently from 9cf7866 to bbde0e3 Compare January 8, 2026 00:41
@rfortier rfortier changed the title Rework of party member quest progresion PR #769 Rework of party member quest progression PR #769 Jan 8, 2026
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch 3 times, most recently from e4475b6 to 25fa8c9 Compare January 16, 2026 02:28
@rfortier
Copy link
Contributor Author

Substantial logic change. Remote updates received while playing a scene are ignored. Unless they include a SceneEnd flag. Those updates are sent when a scene ends and forces everyone to accept the update to catch up.

Many variations of scene support have been tried, this seems to be the best compromise.
When in a Scene, ignore updates from other players. Each player runs the scene and generates their own progression.
This mostly works for scenes that are "movies," but if the scene requires player interaction, one player makes the choice (usually, the one who started the scene, but if the party stands together they might all get the dialog prompts, for example). The scene progresses for that player, everyone else is "stuck" there.

Unless multiple players click the option to continue. That's often going to do something unpredictable, don't do that.

To unstick players, at Scene End, send a "poke" quest stage update that is not deduped, to catch everyone up.

A future improvement may send such a poke every time player interaction causes a quest stage change, but have to figure out how to detect that.

@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch 3 times, most recently from 5e2f603 to 3323bb6 Compare January 22, 2026 21:15
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch 3 times, most recently from 48729e5 to 4804437 Compare February 7, 2026 15:50
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch 2 times, most recently from aef55b7 to b64e36f Compare February 17, 2026 22:29
…work in a party.

If the cutscene design is that all party members (or all inrange) play the scene, the Leader's dialog actions would step on and cancel the copies running in the party members. This would mess up the scene phase timing, causing the members to race ahead and abort actions of the Leader, potentially hanging the scene. Found with the Finn/Carbos scene in Enderal.

If the cutscene design is only the person(s) who trigger the scene play the scene, and the scene is triggered by a non-Leader, no one except the scene-initiator got any dialog. This is an uncommon case; usually the start of a scene triggers a quest stage change that brings everyone into the scene, or the scene is just random encounter / idle chatter that is just fine to sync to just the triggering player.

Because scenes that don't trigger quest stage changes but also would be desirable to sync are rare, and because there is no good way to distinguish them from scenes where syncing the audio will cause dialog cancels and timing problems, this case is left as unsolved, have to live with it.

There is some logic to trigger scenes in the rest of the party when one starts the scene. This solves some cases. In others, there are scene start conditions that just stop the scene again. Best known example is the Companions Brotherhood quest stage C02. Unless the entire party stands on the tiny hotspot where the initiator triggers the scene, it just stops again.

It's not perfect, but the selected (surviving experiment) solution is:
1) Try to trigger the scene for  the entire party, which often works.
2) Each party member plays the scene to themselves, generally with decent audio sync. The party Leader is still controlling the NPCs and motion / animation sync handles most cases.
3) If the scene triggers TaskDialogues (those NPC dialogues where you answer with a menu pick), that dialog is individual to the SceneMaster (they who triggered), so that is synced to party (PlayersInRange).
4) Only flaw known is AI packages.

Subtitle sync follows the same rules as Dialogue sync.

AI package handling is left for a future iteration, since it also is tricky. Current state: AI packages only run on Leader, so if Member triggers a scene, the AI packages doesn't run. If it is anything of consequence (more than "look at player", say, having to do something and then interact with player), the scene will get stuck and Leader will have to take over. If Leader is close, NPC will just switch to Leader. If farther away or not in scene, Leader will have to get closer, possibly having to unlock the Leader with the EPC (EnablePlayerControls) console command.

For the next round, the two big options I see for fixing AI is for
1. SceneMaster to take ownership of NPCs in-scene. They can be released at end of scene, or just wait for a cell change.
2. Find a way to reactivate AI on "remote" NPCs for the duration of the scene, This sounds more fragile.
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch from b64e36f to 4caa3c5 Compare February 20, 2026 20:23
…nes.

Getting the failed attempt out of the code, but keeping the commit so you can it was attempted, but there's not enough info.
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch from 4caa3c5 to 877d649 Compare February 22, 2026 22:31
…only Party Leader), but it introduced a bunch of problems with duplicate quest triggers. This rework deals with it (hopefully).

The most fundamental problem is that ScopedQuestOverride() doesn't work, because quest progress is executed on a different thread; the ScopedQuestOverride() is destructed before the quest OnEvent...() routines are invoked. So all the Members send their updates to the server even when told not to. The server just ignored them all before, but now with Member quest progression enabled, it ends up causing replicated quest updates. Some quests ignore it, many break.

The engine is in control of the content of events passed to the OnEvent routines. I could not find a reliable way to pass any context to the OnEvent routines through that barrier (it runs async, so matching up was nigh-on impossible). So ended up implementing a dedup history server-side, where there is enough context to figure this out.

The worst-case flow (when a Member triggers progress), is:
1) Member sends update.
2) Update is forwarded to Leader, only.
3) Leader OnEvent hooks send the Leader update to the server.
4) Leader SendsToParty
5) Party members reflect the updates to the Server, but are dropped by the dedup history because the Leader has already sent it.
6) Entries in the dedup history time out in seconds (needs some tuning), so quests can rewind and stages can be triggered multiple times to evaluate.

Partial support for Remote actors to run AI packages when in-Scene
Has some value in a commit to say "already tried that."
@rfortier rfortier force-pushed the fix/Rework-PR-769-party-member-quest-progression branch from 877d649 to 3d2eb3c Compare February 22, 2026 22:48
@rfortier
Copy link
Contributor Author

Did a submission cleanup pass. Deleted all the dead (#if 0) code that didn't work or didn't help. Kept as a separate commit so you can see that code and the notes on why it didn't work if anyone works on this in the future.

Turned down all the logging to a more modest amount. There are a couple of [critical] logs that aren't exactly errors, they are still experimental code paths that haven't been fully tested because they have not yet been seen in the wild. I might know where one is hiding, though.

It's ready for review. I've gotten the critique already that it is "too big," but most of that is logging. It's a repeating pattern of setting up a bunch of state / status variables so they can be used in log messages, and then a lot of logs, what it took to figure out what was happening or broken, and then proving the fix in a rapid-fire distributed state machine.

Could delete the logging now that it works, but I don't recommend it. I think there are still more edge cases we will find, and we'll need the same logs to fix the AI Package support, which will fix several more cases where the party Leader must be the one to advance the quest.

The heart of the change is in the two QuestService.cpp files. Most of the rest is RE:: support and an easy read.

@rfortier rfortier marked this pull request as ready for review February 23, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant