Number 5: An honest mistake
The CWScript script attached to quest CW has a property Alias_FieldCOImperialHjaalmarchHQ that is set to alias FieldCOImperialHaafingarHQ.
It does not even come up in vanilla civil war questline. But in the cut content city defense battle in Morthal on the imperial side, this causes the Jarl alias to be unfilled, and the quest objective will have "[...]" instead of Idgrod Ravencrone's name, and presumably the jarl surrender will be bugged.
And it does not even come up if the cut content siege quest is started with the appropriate HQ field commander. One really has to call the function (CW as CWScript).GetReferenceHQFieldCOForHold() which reads that property. Sure, that function is used by the CWSiegeQuickStartScript for starting defense battles, but then again, the quick start only starts minor hold battles in attack mode, the defense is commented out as "not implemented fully".
So, not a bug in the vanilla final cut.
Number 4: A no-op operator
The function SetNewOwnerOfFort(...) in CWFortSiegeScript uses '==' which is comparison in instructions that were clearly meant as assignment ('=')
if IsPlayerAttacking() && CWs.PlayerAllegiance == CWs.iImperials
AttackingFaction == CWs.iImperials
DefendingFaction == CWs.iSons
Else
AttackingFaction == CWs.iSons
DefendingFaction == CWs.iImperials
EndIf
By the way, the condition here is outright buggy - if the player is aligned with stormcloaks, and player is attacking, it still would assign imperials to the AttackingFaction. But this bug is put to shadow by using '=='. As a result, instead of using faction number 1 (imperials) or 2 (stormcloaks), this will set 0 as the owner of the "fort".
That function is called indeed at the shutdown stage of the vanilla fort battle quests. I'm not sure why it does not cause any issue in vanilla civil war - probably nobody cares what the CWOwner value is for a fort location (the hold CWOwner is changed in another function), and maybe this 0 owner value is fixed by some other code
In Open Civil War, the same code is also executed for minor hold battles, and regular fort battles no longer change the ownership of the hold, so to avoid bugs, this function is changed to a truly no-op function.
Number 3: Beating jarl to submission during a fort battle far far away
In major sieges such as battle of Whiterun, Markarth and Riften, there is an elaborate jarl confrontation/exchange sequence. In cut content city battles for other, so-called minor, holds, there is no jarl exchange, but still you can win the the battle by beating up the jarl. The quest for the fort battle must have evolved from the cut content minor hold city battles, because it still contains every code related to winning the hold when you beat the jarl.
You just have to have a fort battle quest running, and go visit the jarl in the hold capital city, and attack the jarl. That's how this is described as an exploit. Sometimes it is remarked that you have to complete the first mission in the hold, if that is a regular civil war mission rather than a fort battle.
But the jarl surrendering scene won't play at all, because it belongs to the city battle quest. So while you will win the hold nonetheless, the fort battle quest won't be stopped (in the city battle it is the scene end that prompts the stopping of the battle quest). So you end up with an unfinished fort battle quest running, and you will get stuck in the next hold when the next fort battle won't come up.
Now, if you would refrain from accepting the fort battle quest, and reach the jarl before the fort battle quest is automatically stopped - perhaps it would be clean. See also bug #2.
In Open Civil War this exploit is suppressed, because it could lead to a bogus city battle results, without the player even realizing what's going on.
Number 2: Walking past enemy military camps can mess up things
The civil war missions and fort battles are quests started through Story Event Manager, and I guess it could take a noticeable time on low-end machines. So when the player character travels to a military camp, the appropriate quest gets silently started. By the time the player finds the quest giver in the tent (in the final cut, only Rikke and Galmar), the quest is running, and it has dialogue options to accept it. Only then it gets added to the quest journal.
By the way, if the player ignores it, and travels enough away from the camp, the quest gets stopped (so that's how you can have two hold campaigns open at the same time and be offered an appropriate fort battle in each of them).
The problem is that the script CWMissionGeneratorTriggerScript, which prepares all of this stuff when we approach the military camp, does it also for enemy military camps! We can't do their missions, perhaps they are conditioned on belonging to the appropriate faction elsewhere. But an attempt to start those missions on enemy side is made, and as part of that attempt, the alias FieldCO in the CW quest is set to the enemy faction lieutenant (Galmar or Rikke). I guess it's a left-over from the early design where any field commander could be the quest-giver.
This happens in the OnCellAttach event, so it happens as soon as the player enters the 5x5 cell grid square surrounding the military camp (it's 5x5 for the default value of uGridsToLoad, but players often increase it for better distance draw visuals). So it wouldn't be an issue in vanilla civil war if only the opposing military camps would be far enough so that their 5x5 cells surrounding squares would not overlap. Unfortunately, for example for Hjaalmarch stormcloak camp and Pale imperial camp they do overlap indeed, and if you approach the Pale imperial camp from south on the road, you will first trigger it for the imperial camp, and only after that for the stormcloak camp, and then you will reach the imperial camp with FieldCO alias value set to Galmar. Meaning, no "Reporting in" dialogue when talking to Rikke. Bummer!
https://srmap.uesp.net/?locx=2535&locy=91474&zoom=14&showcells=true
(Note that on that map the Hjaalmarch imperial camp is even closer to the Hjaalmarch stormcloak camp. Usually they are mutually exclusive locations, though, and approaching a disabled camp does not change of FieldCO alias value. So it only can be harmful if there was some delay in changing hold ownership and the new camp was enabled but the old haven't been disabled - very unlikely).
Unfortunately, the alias FieldCO is used for more than quest-giver dialogue. If it was only for that, it won't be bug #2 on this list, you would simply travel away from the camp and approach it from a different direction or fast-travel to it (and let the game engine decide which camp triggers first if more than one is triggered). But if you have bad FieldCO alias when completing a mission or a fort battle, the game won't remove your faction lieutenant from the faction CWFieldCOMissionActiveFaction, which causes the questline to get stuck later on, with lack of the quest-accepting dialogue (at least for the final hold campaign, in the fort battle quest). And the quest objective "Regain / Liberate (HOLD_NAME)" which normally targets your faction lieutenant won't have a target marker.
Open Civil War includes a fix that prevent enemy faction military camps from triggering this and messing up stuff.
Honorable Mention:
Before getting to the bug#1, let's talk briefly about the jarl confrontation and exchange scenes in major siege battles. In Skyrim, if an enemy will appear during a scene, there are 3 ways to handle that: stop the scene and enter the combat; pause scene and resume it when the combat is over; or go on with the scene, don't ignore the combat. The vanilla major siege jarl scenes don't always use the third option, and the reason why Galmar and Ralof won't go back to fight Whiterun guards is (most likely) because the game adds Galmar, Ralof, Player to the imperial faction (temporarily, until player leaves the Dragonsreach).
In Open Civil War, to ensure that Markarth and Riften jarl scenes play uninterrupted, the scene form is flagged to behave that way. No shady faction manipualtions...
So, not really a bug, but if some other mod will revert the changes in that scene, then the jarl scenes in Markarth may get choppy or stuck (and it may depend on how far from the palace exit the NPC end up after the fight...)
Number 1: A livelock-starvation by arrow volleys
One feature of the civil war battles, seemingly disabled in the final cut are pre-planned volleys of arrows. A bunch of arrow shooting positions markers are linked with the master arrow volley activator, which has a few scripts attached.
The script CWArrowVolleyParentScript has a function StartFiring() that enters a loop, which boils down to the following:
while firing == True && (0 <= timesToFire || 0 <= 0)
if CWBattlePhase.GetValue() == ThePhaseToFireIn
Utility.Wait(5)
FireSelfAndLinkedRefs() ; do the actual volley of arrows stuffs
endIf
endWhile
Note: In programming languages with actor-based concurrency (which Papyrus kind of is), making an infinite loop is a no-no. An obvious deadlock, as no other thread can get the execution lock which is taken by the loop. On E pages http://erights.org/elib/concurrency/event-loop.html this is called a whitelock livelock, so though livelock in general can mean also something else, let's stick with that name.
So when the battle phase advances past the scheduled arrow volley phases, the loop does active waiting without ever calling the Wait(5) function that would suspend the thread for good 5 seconds. Still, it calls GlobalVariable function GetValue(), so when it's time to set firing to False (which is done there with event OnUnload() calling function StopFiring()), then it is really up to Papyrus scheduler to be fair and not starve out the new thread trying to access this. Apparently the scheduler is not fair, and other thread calls can be stuck for dozens of seconds while this loop remains the active thread.
Even worse, also the ObjectReference function Disable() gets stuck as well (which makes me wonder if the implicit threading lock in Papyrus is not per instance of a script as documented in the creationkit.com threading notes wiki, but rather the lock is part of the object the script is attached to). And the quest for major siege battles attempts to Disable() those arrow volley activator objects in the script fragment of its shutdown stage 255.
It does gets stuck before the battle music is turned off, the battle weather is cleared, the siege camp is disabled, and last but not least, to the victor goes the spoils: ownership of the hold, government and garrisons. And since the stage is marked as "shutdown stage", the quest gets automatically stopped, and peeking its state in console or by Papyrus script functions shows this quest as not running. The Story Event Manager, however, won't start up again that quest. And this quest is reused for all major hold siege battles: Whiterun, Solitude, Windhelm in vanilla, and Markarth and Riften in the cut content. For the Story Event Manager this quest is not yet ended while the stage fragment script is not yet finished.
The music and weather might perhaps go away on their own, but the siege camp stays there, and there is no change in the hold status, until the shutdown stage gets unstuck. In vanilla Skyrim this only affects Battle of Whiterun on the Stormcloak side, since you need to have Whiterun on your side to be send forth to holds conquest, and on the imperial side you already have that before the battle. Battle of Windhelm and Solitude are at the end of the questline, so it is likely not as game-breaking, and between Battle of Whiterun and Battle of Windhelm there's sure plenty of time for the thing to get unstuck, neh? Plenty of advice on this issue on the side of Stormcloak were: "don't fast travel to Windhelm. You have to go there on foot!", and it often worked. But for how long?
We may be certain that the Papyrus interpreter don't check, when the GlobablVariable.GetValue() is called, if some other thread is waiting suspended and should be put in front of the queue, so to speak. I can understand that - GetValue() should be as fast as possible, for it does not do anything complex - just fetch the value of a global variable. On the other hand, it is possible for Papyrus engine to let other threads stop firing and/or disable the arrow volley object. In fact, usually this is done at once, with no noticeable delay whatsoever. Suppose then that Papyrus checks threads and reschedules them every frame. And only if it happens so that the active loop will be inside GetValue() at the onset of the frame, the another thread, competing for the access, might get executed.
Now if it happens, with all the other scripts that Papyrus runs, and with the CPU time Papyrus spends every frame, if it happens that Papyrus executes a whole number of this loop turn-arounds, it will end up at the same instruction as it started. Thus, miss the time when the loop is inside GetValue(). If Papyrus executed a fractional number of loop turn-arounds, it still does not mean that it will hit GetValue() - it may easily hit a pattern set of instructions that avoids GetValue(). If you go on and play the game, maybe something will change, and this pattern will change and eventually the siege shutdown will get unstuck. Ditto if you reload the game and try the siege again. If you just stay and wait, it ain't going away on its own...
With Open Civil War, this glitch was particularly funny when after a battle of Markarth or Riften you would attempt a capital relief battle. It wouldn't even start, but the escort soldiers from Kynesgrove or Dragonstone wouldn't know that, and run to the city, with caution, tension, weapons unsheathed, only to give up the pretense at reaching some point and casually walk back...
3 comments
Maybe making them do damage to the attackers will be funny,haha.