Jump to content

Reference Aliases not filling


Recommended Posts

The aliases and quest always exist, from the time the game first loads.  The quest starts in a "stopped" state (unless you flag it SEQ), in stage zero, and the aliases unfilled.

When the quest is started (you did not mention how you start your quest, nor whether sqv reports the quest as "running: True"), it fills the aliases as per your settings and then goes into "running" state.  If you use an autofill option, you need to stop and restart the quest to try to fill the aliases with new values.

When the quest stops, the aliases are cleared, but the ReferenceAlias and associated scripts remain!  Only the value reported by getReference is affected.

The ReferenceAlias script itself does not appear to be notified when the quest starts or stops, nor when the alias contents change.  The quest script gets just the OnInit() when the quest starts (but not stops!) or when the stage number changes.

You may want to experiment manually starting and stopping your quest via console (startquest myquest / stopquest myquest).

Also.

Testing leveled actors is tricky.  Their actor base is created dynamically by cloning and 'leveling' the clone.  Finding conditions that identify them can be difficult.  In my own modding, I try to rely on the ActorType* keywords as much as I can.  Try simple will-obviously-fill-something, conditions and build from there step by step to sort through actors.

 

Link to comment
Share on other sites

On 3/11/2024 at 8:05 PM, xkkmEl said:

The aliases and quest always exist, from the time the game first loads.  The quest starts in a "stopped" state (unless you flag it SEQ), in stage zero, and the aliases unfilled.

When the quest is started (you did not mention how you start your quest, nor whether sqv reports the quest as "running: True"), it fills the aliases as per your settings and then goes into "running" state.  If you use an autofill option, you need to stop and restart the quest to try to fill the aliases with new values.

When the quest stops, the aliases are cleared, but the ReferenceAlias and associated scripts remain!  Only the value reported by getReference is affected.

The ReferenceAlias script itself does not appear to be notified when the quest starts or stops, nor when the alias contents change.  The quest script gets just the OnInit() when the quest starts (but not stops!) or when the stage number changes.

You may want to experiment manually starting and stopping your quest via console (startquest myquest / stopquest myquest).

Also.

Testing leveled actors is tricky.  Their actor base is created dynamically by cloning and 'leveling' the clone.  Finding conditions that identify them can be difficult.  In my own modding, I try to rely on the ActorType* keywords as much as I can.  Try simple will-obviously-fill-something, conditions and build from there step by step to sort through actors.

 

whoops--sorry--responded to wrong thread 😄

Edited by csbx
Link to comment
Share on other sites

On 3/11/2024 at 8:05 PM, xkkmEl said:

The aliases and quest always exist, from the time the game first loads.  The quest starts in a "stopped" state (unless you flag it SEQ), in stage zero, and the aliases unfilled.

When the quest is started (you did not mention how you start your quest, nor whether sqv reports the quest as "running: True"), it fills the aliases as per your settings and then goes into "running" state.  If you use an autofill option, you need to stop and restart the quest to try to fill the aliases with new values.

When the quest stops, the aliases are cleared, but the ReferenceAlias and associated scripts remain!  Only the value reported by getReference is affected.

The ReferenceAlias script itself does not appear to be notified when the quest starts or stops, nor when the alias contents change.  The quest script gets just the OnInit() when the quest starts (but not stops!) or when the stage number changes.

You may want to experiment manually starting and stopping your quest via console (startquest myquest / stopquest myquest).

Also.

Testing leveled actors is tricky.  Their actor base is created dynamically by cloning and 'leveling' the clone.  Finding conditions that identify them can be difficult.  In my own modding, I try to rely on the ActorType* keywords as much as I can.  Try simple will-obviously-fill-something, conditions and build from there step by step to sort through actors.

 

Thanks for the response and sorry for the delayed reply. I've been away from my PC. The quest is set to start with the game and sqv reports the quest "Enabled? Yes", State: Running". There are no quest stages. I only use it for the reference aliases which I didn't think would exist until/unless something spawned that matched the conditions of the alias.

I think I'm going to have to find another way to do this or just scrap distributing my mod and attach my Creature Control script directly to Lvl*Predator base objects. That works fine with the script catching OnLoad events, and I don't really want my script attached to Lvl*Predator. I did that just because it's quicker and easier.

It's better if I attach my script to EncWolf instead of to the LeveledCharacter object because I have settings per creature type (stored in globals) and don't want a big if/else statement checking for the creature's type (race or whatever) when it loads.

The setting is stored in a different global per race (one global for wolves, one for bears, etc.), which I have an MCM to modify. It determines whether wolves are cowardly, should disintegrate and leave an ashpile to loot, just disintegrate with no ashpile, or keep their default behavior. I need a setting (a global variable) like that for each type of creature, so if I attach the script directly to EncWolf I can just have a GlobalVariable property in my script that's set to different global variables for different types of creatures. It makes it easy to create an MCM to change the script's behavior for different types of creatures, and then I don't need a big if/else statement checking the creature's type to find the proper GlobalVariable setting.

My project is a lot more involved than this, so I think I explained too much. All I really need to know is how to best attach a script to non-unique actors like EncWolf when those objects load? I can open EncWolf from the Object Window of the CK and attach my script there, setting the GlobalVariable property to my setting for wolves. But that's not an advisable way to create a mod since base objects are modified directly. How would I accomplish the same thing without editing base objects directly?

I saw a video on doing it with barrels. Don't edit the barrel directly by putting something into it but use a reference alias to add whatever, so that the mod is compatible with other mods. I need to do that with wolves. 🙂 I didn't want to use the script example on creationkit.com that uses a MagicEffect to dynamically attach a script to whatever actors. It seems like it'd be really slow and possibly problematic when lots of actors spawn like around the civil war. But is there any other way to get scripts attached to non-unique actors at run-time without directly editing base objects?

It's not just getting a script attached but also having an event trigger when the script is attached. Without that (an OnLoad event, for example) I can't do anything when wolves spawn, so it'd be useless.

Link to comment
Share on other sites

2 hours ago, Sirgallyhave said:

My project is a lot more involved than this, so I think I explained too much. All I really need to know is how to best attach a script to non-unique actors like EncWolf when those objects load? I can open EncWolf from the Object Window of the CK and attach my script there, setting the GlobalVariable property to my setting for wolves. But that's not an advisable way to create a mod since base objects are modified directly. How would I accomplish the same thing without editing base objects directly?

So far as I know, those are your only options: add it to the ActorBase in the CK, add it to the ObjectRef in the CK, put each instance in an alias, or place a magic effect on each instance.  I hear there are SKSE plugins that offer the ability to attach scripts directly but I have not tried them.

The alias route works for a small, fixed, number of instances at a time, and requires a lot of dynamic maintenance to move instances in and out of the aliases.

The magicEffect is also workable, and there are indeed issues with the scripts firing all at the same time clogging the CPU and causing stack dumps.  But it's still an easy and efficient option if you avoid targeting actorTypeNPC and if you don't rely too heavily on OnEffectFinish.

 

Link to comment
Share on other sites

5 hours ago, xkkmEl said:

So far as I know, those are your only options: add it to the ActorBase in the CK, add it to the ObjectRef in the CK, put each instance in an alias, or place a magic effect on each instance.  I hear there are SKSE plugins that offer the ability to attach scripts directly but I have not tried them.

The alias route works for a small, fixed, number of instances at a time, and requires a lot of dynamic maintenance to move instances in and out of the aliases.

The magicEffect is also workable, and there are indeed issues with the scripts firing all at the same time clogging the CPU and causing stack dumps.  But it's still an easy and efficient option if you avoid targeting actorTypeNPC and if you don't rely too heavily on OnEffectFinish.

 

Cool, thanks for the summary. I switched to a MagicEffect and it seems to be working fine but I haven't done a lot of testing yet. Thanks for taking the time to give me a hand with this. I appreciate it.

Link to comment
Share on other sites

With a magiceffect, I recommend you place akCaster and/or akTarget in local script variables to prevent them from disappearing from memory faster than you can process the events (notably the OnEffectFinish event, especially while going through load doors or fast travelling). 

Even so, you will have cases where the magiceffect and spell underlying your ActiveMagicEffect script are destroyed by the game engine before you can process the events.  Be ready for all kinds of failures (getting events with None inputs, failure to receive events altogether, etc.). 

These quirks are uncommon, but not rare; you will likely need to deal with them, that is you will need to structure your code so that things don't break if you are not able to get at or process some of these events.

 

Link to comment
Share on other sites

On 3/17/2024 at 3:59 PM, xkkmEl said:

With a magiceffect, I recommend you place akCaster and/or akTarget in local script variables to prevent them from disappearing from memory faster than you can process the events (notably the OnEffectFinish event, especially while going through load doors or fast travelling). 

Even so, you will have cases where the magiceffect and spell underlying your ActiveMagicEffect script are destroyed by the game engine before you can process the events.  Be ready for all kinds of failures (getting events with None inputs, failure to receive events altogether, etc.). 

These quirks are uncommon, but not rare; you will likely need to deal with them, that is you will need to structure your code so that things don't break if you are not able to get at or process some of these events.

 

Thanks for the tips. I'm storing akCaster in a local variable right off the bat in OnEffectStart. I'm not really using OnEffectFinish other than for debugging just curious to see when it's triggered. All I have to do is get the Actor from akCaster into a local and then I do whatever to wolves, bears and such as per the setting and that's that.

The issue I'm having now is that I can't get an ActiveMagicEffect into a property. I need access to the script attached to my ActiveMagicEffect in my MCM script because the script attached to the magic effect has the array of ints in it for the settings (whether a creature should be made cowardly, disintegrated,  disintegrated and leave an ashpile to loot, or keep its default behavior).

I can't figure out how to get access to the int[] that I store those settings in. It's simple if I put the array in the MCM script. Then, I can access that array in the magic effect itself by referencing my MCM quest to get the attached script (and the int[] property).  But that causes bottlenecks. The game waits whenever it has to access data or a function in another script.

It's why I put the settings (the int[]) in the ActiveMagicEffect script itself, so the settings are right there and I don't have to access another script which can cause huge delays.

My Magic Effect that attaches the control script has a condition check to ensure the object is in CreatureFaction, so hopefully when NPCs spawn for civil war stuff, my mod won't crash the game. I only need to operate on actors that are in CreatureFaction so I hope the game will notice that condition in the magic effect and not attach the script to anything that isn't in that faction.

But this is just getting mind-boggling (as usual around Skyrim). Reference aliases don't trigger any events when they get filled, so they're useless to me. So I either modify base objects like EncWolf directly attaching my script to them, or I use the magic 'cloak' example from Dynamically Attaching Scripts.

If I do that, I have to use a script that extends ActiveMagicEffect... and if I do that, I can't get access to the int[] storing the setting for each creature type in my MCM script making the settings impossible to change.

Typical Skyrim modding. 🙂  It seems to be how everything goes.

Edit: I'm going to have to change how I'm doing this. I get that ActiveMagicEffect doesn't exist until the spell is cast and it operates on a specific actor. And the wolves and whatnot my script operates on often don't exist when the game loads. They spawn as the player character moves around, so there's no script to reference from the perspective of the CK.

If I use GlobalVariable types to store my settings, I can put the globals into a list and then I can access the settings (get at the different globals) from the magic effect script and my MCM script. It's just much slower to use a list of globals than to use an int[].

I have to be able to index the setting per creature type (bear, wolf, etc.) according to what gets matched in a Race[] I'm checking akCaster.GetLeveledActorBase().GetRace() against.

That's why I'm using Race array. It's fast but when I get a match, I only have an array index to work with and that's why I moved my settings from globals to an int[]. But that doesn't seem do-able, so I'll have to go back to using globals and put the global (the MCM setting) for each race my system handles in a FormList that matches the Race array I'm checking.

I've done that before, so I know it's possible. But it's slow to access FormLists as opposed to just indexing an int[] and I'm worried my mod is going to bog down the game.

 

 

 

Link to comment
Share on other sites

6 hours ago, Sirgallyhave said:

[...]

The issue I'm having now is that I can't get an ActiveMagicEffect into a property. I need access to the script attached to my ActiveMagicEffect in my MCM script because the script attached to the magic effect has the array of ints in it for the settings (whether a creature should be made cowardly, disintegrated,  disintegrated and leave and ashpile to loot, or keep its default behavior).

I can't figure out how to get access to the int[] that I store those settings in. It's simple if I put the array in the MCM script. Then, I can access that array in the magic effect itself by referencing my MCM quest to get the attached script (and the int[] property).  But that causes bottlenecks. The game waits whenever it has to access data or a function in another script.

[...]

I know of no proper way to access ActiveMagicEffect scripts or their properties.  Using a shared data repository is the way to go.  The MCM Quest is a good standard choice but I prefer JContainers...

Papyrus locks are a mess. I call this issue "lock pollution": once you reference a world record (Form or ORef), papyrus takes a lock on it and does its best to avoid releasing it, whether you need the lock or not.  Any other script that tries to access the same records will be queued waiting for you to finish.  The longer a thread runs, the more locks it accumulates and the more threads back up behind it.

You can mitigate the issue by keeping your script events/threads short-lived, or by calling Utility.wait( 0.0001) often to release accumulated locks.

You might try to read all the int-array entries you need from the MCM quest in OnEffectStart, and move all heavy processing to other events.  The OnEffectStart calls will end up being serialized still, but that won't matter much if they are brief enough.  Once all the shared-data accesses are done, your scripts can run more freely.

Personally though, I would use JContainers to get around this, as much as I can.

 

Link to comment
Share on other sites

12 hours ago, xkkmEl said:

I know of no proper way to access ActiveMagicEffect scripts or their properties.  Using a shared data repository is the way to go.  The MCM Quest is a good standard choice but I prefer JContainers...

Papyrus locks are a mess. I call this issue "lock pollution": once you reference a world record (Form or ORef), papyrus takes a lock on it and does its best to avoid releasing it, whether you need the lock or not.  Any other script that tries to access the same records will be queued waiting for you to finish.  The longer a thread runs, the more locks it accumulates and the more threads back up behind it.

You can mitigate the issue by keeping your script events/threads short-lived, or by calling Utility.wait( 0.0001) often to release accumulated locks.

You might try to read all the int-array entries you need from the MCM quest in OnEffectStart, and move all heavy processing to other events.  The OnEffectStart calls will end up being serialized still, but that won't matter much if they are brief enough.  Once all the shared-data accesses are done, your scripts can run more freely.

Personally though, I would use JContainers to get around this, as much as I can.

 

Oh, interesting. I had no idea Wait() released the locks. That's good to know. I also didn't think of just copying the array in OnEffectStart. That's a good suggestion but in this case, I'm in and out of that event one time just to set the creature's aggression, or to disintegrate it, and I never need to access the OnEffectStart  in the script on any one creature again. So I'd probably spend more time copying the entire array than just accessing it one time (the setting could be at index [0]) to find out what to do with a given creature.

Hmm. I just realized I should probably put wolves at index [0] instead of where I have them now in alphabetical order at the end of the array. lol 

I've never used JContainers or JSON in anything. I'll take a look, thanks for the link.

Link to comment
Share on other sites

I don't know how likely it is that someone will run across this in the future and wonder how I solved the issue. But I always get frustrated when people leave threads open without explaining how they solved things, so I'll explain just to close the topic.

I ended up using GlobalVariable types (one per race/creature type, like one global to hold the setting for wolves, another for bears, etc.) that I dropped into a FormList. The list can be accessed from scripts that extend ActiveMagicEffect and scripts attached to MCM quests for player configuration of settings just by putting a property in each script:

FormList property my_globals_list auto

You can drag and drop GlobalVariables from the CK Object Window into FormLists and then the list can be accessed like an array:

GlobalVariable one_setting = my_globals_list.GetAt(offset) as GlobalVariable

That's what I did to solve this, so I can access the globals (the settings my MCM allows players to change) from the script on the magic effect and from the script on my MCM quest. In testing I've done, it's over ten times slower than using an int[] to store settings, but I think it's probably as fast or faster than using a JSON. Using JContainers would be better though since it'd allow players to configure the settings one time and then they could access the same settings from multiple save games.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...