Locational Damage Fixes Part II - States

I'm new to modding. But it just so happens that a simple tweak to Locational Damage makes it chug along better in big and chaotic battles.

Cipscis has an informative blog on Skyrim modding (one of the few in existence). In one particular tutorial he dives into states. Papyrus offers a simple and elegant way to switch between two distinct sets of functions in a script through the use of states. In other languages this isn't really a big deal; it might be syntactic sugar but it won't buy you much. But because Papyrus is entirely event-driven, you can do cool and useful things.

States may be used to self-regulate a script by forcing it to complete the code it is already running before triggering a function again:

It's possible that a function may be called again before it has finished running from the last time it was called. This is particularly likely for functions that take some time to complete, but are called often. By using states, it is possible to prevent a function from running more than once simultaneously

In context of Locational Damage, the onHit event is computationally intensive. There's no guarantee it'll finish quickly (in this scripting world, a single iteration could take 250 ms or longer).

The given example is below:

ScriptName LongFunctionScript extends ObjectReference

Function LongFunction()
	GoToState("Busy") 
	; Do something that takes a long time
	GoToState("Waiting") ; The function has finished, so it can be called again
EndFunction

State Busy
	Function LongFunction()
		; Do nothing
	EndFunction
EndState

Note that in the busy state the function in question does nothing. When the operation completes, it switches back to the ready state. This is what the fix in the stability patch looks like, analogous to Cipscis' example:

function OnHit(objectreference AttackerObj, form akSource, Projectile akProjectile, Bool abPowerAttack, Bool abSneakAttack, Bool abBashAttack, Bool abHitBlocked)
    ; Go to the busy state
    ; Find the body zone that was hit and apply effects. This can take a while!
    ; Go back to the ready state

state busy
	function OnHit(objectreference AttackerObj, form akSource, Projectile akProjectile, Bool abPowerAttack, Bool abSneakAttack, Bool abBashAttack, Bool abHitBlocked)
		return ; Do nothing
	endFunction
endState

In mod lists that don't have a lot of scripts, this fix for LD doesn't make much sense. It lowers the bar for seeing critical hits and effects not register. It can and will cause the mod to drop events that it cannot keep up with, as opposed to the previous behavior of triggering at all times. This means that even if Papyrus is just barely overloaded, it can still drop a triggered event such as a headshot.

But the problem is without any self-regulation the game can be forced into a state where it is processing tens or hundreds of lagging events, due to how often the onHit event is capable of firing with Locational Damage. This is an acute risk on slow machines or in heavily modded games. Papyrus, in one sense, is very reliable and faithful. If onHit triggers 100 times in a short period, it will dutifully run the respective code 100 times. Even if the battle is long over and your target is dead. This has manifested in many modders' games as script lag; effects will register when they are no longer wanted and other scripts will not run until the game is reloaded or until several minutes have passed.

There are more sophisticated methods for regulating a script's performance that use an average run-time measure in seconds of events and functions as a trigger for disabling features. It is possible that this technique could be used to, say, disable NPC->Player locational damage when script lag is detected. Perhaps in the future I will try to implement it in LD. However, this technique using states is still necessary. Since the onHit event is computationally intensive and can run many times in sequence, it makes sense to implement a busy state as described by Cipscis to minimize the chance for runaway script lag.

At this point I'm no longer releasing the incremental downloads; go here on Nexus to find the beta patch if you haven't already. As a preview to the last and most important enhancement of the trio included in the stability patch, I moved a significant portion of the logic over to an SKSE plugin to improve the performance of the calculations involved in Locational Damage. It's a relatively straightforward fix that just requires careful inputs and outputs to avoid the dangers of SKSE plugins, but results in significant and noticeable gains to performance. I also took it in a slightly different direction to boost performance even more. Check back in a week for the next post.