I was inspired to create the Detect Runes spell when I saw someone ask if something like that exists. It's a surprisingly tricky task. The current implementation relies on powerofthree's Papyrus Extender, but it should be possible to implement this in vanilla as well.
The game already has a "Detect" effect, used for the Detect Life and Detect Dead spells. (The only difference between those spells is the list of conditions on their Magic Effects, which limit what they apply to.) The Detect effect highlights all nearby characters that match the effect conditions. In theory it should be possible to add further conditions to limit these (e.g. Detect Actors Carrying At Least 500 Gold; Detect Thalmor; Detect Marriageable NPC), but detecting non-character objects in a similar manner is a "some assembly required" task.
What is a placed rune?
Rune spells work by casting a projectile, and it turns out that that projectile is the placed rune. Specifically, it's a "lobber"-type projectile (i.e. a grenade or a landmine) that's been set to detonate on proximity rather than impact, and it's been given the L_SPELLTRIGGER collision layer. Once a lobber is spawned into the game world, it's a typical ObjectReference. In fact, you can even pre-place lobbers like runes in the Creation Kit, as the vanilla game does in some cells, and in xEdit you'll see those listed as PGRE, short for "Placed Grenade."
If we can find some way to search for ObjectReferences in a given area, then the trick will be to figure out if a given placed projectile is a rune in specific, or some other kind of projectile, such as an arrow mid-flight. (An alternative approach would be to just check for specific base forms i.e. the vanilla runes, but then we miss out on modded runes.)
Current implementation
PO3's FindAllReferencesOfFormType function can be used to find all refs near a given basis (e.g. our spellcaster) whose base form is of a given form type (e.g. projectiles). However, there are no vanilla functions that can give us detailed information about a Projectile form, and those forms don't have keywords or any other sort of scriptable metadata.
PO3 does give us a way to check if a projectile is a lobber, which will rule out things like arrows that are mid-flight. That's the most information we can get about the projectile itself; there's no way to check from Papyrus whether it detonates on a timer, on proximity, or on impact. We can make an educated guess, though, by ruling out any projectile that's currently moving: po3's extender gives us a way to test condition lists from Papyrus, and the GetVelocity condition function can be used to test a ref's velocity along any single 3D axis. We can take advantage of this by creating a dummy spell whose conditions test whether a target object's velocity on all three axes is zero, and then using PO3's API to run that spell's conditions with any given projectile as that target.
So in sum, our shortcut for identifying potential runes is:
- It's an object in the game world
- It's a projectile
- It's a lobber (grenade or landmine)
- It's not moving
Once we can search for and find runes near the caster, we can apply detection VFX to them. My approach was to modify the MoteEffect01.nif model from the Dawnguard DLC to remove its collision, offset it upward so it hovers above the target, and change the vertex colors using Blender and PyNifly. PyNifly doesn't understand most of the structural elements in MoteEffect01.nif, like "billboard nodes" to make the effect face the camera, so I had to Frankenstein the exported geometry back into the original NIF by hand using NifSkope. Once I had a model ready, I used it on an activator, and gave that activator a script to make it delete itself after a delay (with the desired time communicated to it by the magic effect script that spawns it).
Sadly, it's not possible to use the exact same VFX as the Detect Life and Detect Dead spells because those rely on an EffectShader with particle effects, and EffectShader particle effects only work on character models. (The particles are emitted from bones in the character's animation skeleton.) This also means it's harder to get the NIF to show up through walls (and the initial release of this mod doesn't attempt this). Theoretically, it should be possible to exactly recreate an EffectShader's particle effects in a NIF file that we could use. However, as of this writing, particle emitters aren't terribly well-understood -- even NifSkope, the most feature-complete community-made NIF renderer, can't display them -- and no one knows exactly how EffectShaders' particle settings map to particle-related NIF data. It's something I'd like to look into in the future, but it's low-prio compared to other projects I'm working on right now.
Of course, detecting particles this way requires an additional dependency: PO3's DLL. What if we wanted to do it with just the vanilla toolbox?
Vanilla implementation
If we forego SKSE DLLs, then we're more limited, but there's still an approach that I think would be good enough. A Papyrus script can find n nearby references that match certain criteria, but it needs help from a quest.
Here's the basic design: we start by creating a repeatable unmarked quest, and giving it n many Reference Aliases. Each alias is marked as Optional, and set to fill from refs in the loaded area, preferring those that are closest. (All of these things, so far, are just checkboxes.) We then give the alias conditions that narrow down what refs it'll pick:
- GetIsObjectType will match projectiles.
- GetVelocity will match refs that aren't moving.
Once our quest and aliases are set up, the basic idea is this: we use a Papyrus script on our Magic Effect that starts the quest. We put a script on the quest which scans over each of its aliases once the quest starts up, and applies our detection VFX to those projectiles. The script then shuts the quest back down, prepping it for the next spellcast. This way, the game's quest system does the work of finding refs near the player for us.
That could potentially be enough to detect runes and other landmine-like projectiles. The main concern I have (since I haven't developed or tested anything based on this approach) is whether a fired arrow still counts as a projectile (rather than an ammo) once it deflects off of a hard surface and tumbles onto the ground. If it counts as a projectile, then once it comes to rest, we'd mistake it for a rune (since we wouldn't be able to tell that it isn't a lobber).
0 comments