Jump to content

Mod Creation Best Practices Questions


Recommended Posts

Posted

I have been learning how to build xEdit-and-Papyrus mod patches (GoG version means no CK) for months, but Love Sickness inspired me to build a mod from scratch because its ESP and scripts were so clean and understandable that it made a bunch of things click into place (so now I can do something new and substantially different, versus the small change I was originally thinking of). I am now using that and SexLab Survival as my main learn-by-example mods.

 

I am not publishing this first creation, but I want to use it to learn how to "mod right" for the future. I know how to program, I know how to use Google, and I've found some community "best practice" advice. But I have some questions.

 

1a) Why/when should I use StorageUtil (or whatever) to store frequently-used mod variables, instead of a declared Property? It makes cross-mod access easier and the variables can persist across script reset (like on an update), but are there other reasons? Example: Fill Her Up Baka Edition uses StorageUtil to store inflation figures, but it also provides API functions to cleanly retrieve those values. Other than programmer preference, I don't see why he (and some other mod authors) do it that way. What other reason is there?

 

1b) I will need a lot of cross-script variable reference unless I jam all my code into one file (a bad idea for several reasons). Is there a reason why I should NOT put all my cross-script variables into a single script for easier bookkeeping? Then I always know where they are and store/load becomes easier. I know that cross-script variable reference is slower than same-script reference, but as long as I'm not doing something stupid like using that for iterator variables, I don't think it should matter a lot? Variables that are only used locally would stay local.

 

2) I can put multiple scripts into a single quest and all of them run independently for threading and mod events. They do share a single update timer. If I need no more than one update timer, are there any other problems with running a lot of scripts from a single quest? Or should I break out everything into its own quest? Example: SexLab Survival uses tens of quest for tiny pieces of functionality. I guess the maintenance is probably easier with split quests, which is a good argument to do it that way.

 

3) I read wankingSkeever's Reddit post on updating mods in-place. It sounds like the rules for updating a script-heavy mode are pretty simple. As long as the script isn't running, it can be updated. So write scripts that finish fast with no busy-waiting, use FallrimTools to check the save game and make sure that a script isn't running ("finish fast" does not mean that it impossible to save during script execution), and then replace the script. New properties are initialized automatically and internal variables can be set on an update function if necessary. Writing scripts to sanity-check internal variables before use is even better (but not always possible, like when using a new variable as a declared internal constant). If updating quest properties, reset the quest to repopulate them. Anything else to watch out for? Without CK, I'm not going to be manipulating objects in the game world, so that part isn't a problem.

 

Any responses from experienced modders are appreciated.

Posted (edited)

 

2 hours ago, scoul73 said:

... Love Sickness ...

I use that mod too!  :) 

 

2 hours ago, scoul73 said:

1a) Why/when should I use StorageUtil (or whatever) to store frequently-used mod variables, instead of a declared Property? It makes cross-mod access easier and the variables can persist across script reset (like on an update), but are there other reasons?

You'll probably get somewhat different answers, but mainly for cross-mod access.  StorageUtil also lets you readily associate data with various object references (such as NPCs).  Maybe you want to remember when the PC last interacted with an actor.  On the other hand, dialog conditions can't see StorageUtil data, so sometimes using a faction rank is better.  Also, properties, factions, or global variables are easy to inspect with the console.  You'll be doing a lot of troubleshooting, and it's useful to be able to ask a user of your mod to use the console to check a quest property.

 

2 hours ago, scoul73 said:

1b) I will need a lot of cross-script variable reference unless I jam all my code into one file (a bad idea for several reasons). Is there a reason why I should NOT put all my cross-script variables into a single script for easier bookkeeping?

It's usually good to group common data values in a separate script for better organization.  If a large script has a lot of properties, it can reach the point where the output from the SQV console command is larger than the scrollable area, so smaller blocks of values are easier to inspect in-game.  However, you don't see it done all that often because so many mods start small, and the value of such organization isn't obvious.  By the time the puppy has grown to a large dog, the benefit of refactoring is clear, but the author probably won't want to take the time (and risk introducing errors into time-tested code).

 

2 hours ago, scoul73 said:

2) I can put multiple scripts into a single quest and all of them run independently for threading and mod events. They do share a single update timer. If I need no more than one update timer, are there any other problems with running a lot of scripts from a single quest?

Again, you'll probably see different responses.  Grouping scripts into quests is good for organization, but not so good if SQL output becomes too long to see it all.

 

2 hours ago, scoul73 said:

3) ... updating mods in-place.... Anything else to watch out for?

You can still update quests that might be running as long as you warn the users of your mod.  If I need to do that, I'll state in bold red text in the What's Changed section not to update the mod while a certain quest is running.  Even if I don't need to do that, I also place the update notes in a post in the support topic, so players will have two ways to see the information.  This also helps if the site's notification doesn't happen to go out when publishing a new version.

Edited by Hex Bolt
Because I always notice a missing word right after pressing Save
Posted
27 minutes ago, Hex Bolt said:

You'll probably get somewhat different answers, but mainly for cross-mod access.  StorageUtil also lets you readily associate data with various object references (such as NPCs).  Maybe you want to remember when the PC last interacted with an actor.  On the other hand, dialog conditions can't see StorageUtil data, so sometimes using a faction rank is better.  Also, properties, factions, or global variables are easy to inspect with the console.  You'll be doing a lot of troubleshooting, and it's useful to be able to ask a user of your mod to use the console to check a quest property.

 

In addition to what Hex Bolt has said, I would add that the StorageUtil data can be associated explicitly with singular forms such as actors, so similar data can be separated and used between many instances. Properties can also really pollute save files if used poorly, as they tend to stick around in a save and not automatically reintialize/erase themselves between updates/removal when a esp/esm/esl or script changes. StorageUtil data self-cleans, properties do not. 

 

In general, if you want to provide data to other mods that might frequently change or need form association, I'd suggest StorageUtil over Properties. Access to storageutil data can be done better by other mods without adding dependencies and causing papyrus errors.

 

They ultimately both have their own uses and valid reasons for being used. It just depends on your purpose. Comparing them honestly doesn't make much sense in my head since they have such different uses.

 

Posted

Thanks, both of you. I have a couple followups.

 

20 hours ago, Hex Bolt said:

On the other hand, dialog conditions can't see StorageUtil data, so sometimes using a faction rank is better.  Also, properties, factions, or global variables are easy to inspect with the console.  You'll be doing a lot of troubleshooting, and it's useful to be able to ask a user of your mod to use the console to check a quest property.

 

Right, good points. I'm mostly doing number manipulation with always-on quest scripts for this mod, but using them to save values across non-persistent stuff makes sense. And the debugging makes sense too. I'm a debug-by-printf sort of person, meaning to the console in this context, and forgot about that sqv command you mentioned later, so thanks for the reminder. That will speed stuff up.

 

20 hours ago, Hex Bolt said:

You can still update quests that might be running as long as you warn the users of your mod.

 

Right, I was thinking about the quests used as holding containers for functionality (like your own Love Sickness). That Reddit post says that running quests don't update for new properties, but that can be fixed by resetting the quest, just make sure to save off and restore the values of any properties that need to be preserved. Quests, like "in-game quests a player sees" might get more complicated for resetting state, but that just means more state preserving and restoring code. A place to use StorageUtil!

 

19 hours ago, Ashal said:

Properties can also really pollute save files if used poorly, as they tend to stick around in a save and not automatically reintialize/erase themselves between updates/removal when a esp/esm/esl or script changes.

 

This is interesting. I wondered if orphaned properties were saved or purged, and it sounds like the first one. Is it really a big problem in practice? How often do mod authors redo all their properties on an update, or how often do people purge mods from a save game? With the size of Skyrim save files, it would take an awful lot of leftover ints and references and strings to cause real bloat.

 

19 hours ago, Ashal said:

Access to storageutil data can be done better by other mods without adding dependencies and causing papyrus errors.

 

Excellent point. I had not thought of that. It doesn't matter as much to me the mod author working with my own mod, but it might make it easier on another author trying to work with my mod (and that other author might someday be me, trying to make a second mod work with my first one). Just check StorageUtil values instead of adding dependencies or working with a lot of conditionals of "is this mod loaded". FillHerUp could be a good example of that, if you want to use the inflation amount if it exists and treat it as zero if it does not. Assuming the mod author is willing to declare that their StorageUtil key is permanent, so it doesn't change between versions.

 

Another question: Is there a good way to build a last-resort "reset the mod" function? I ask, because I have run into a bug where an MCM stops working even after saving and restarting the whole game. SexLab Survival has done this to me several times. When that happens, FallrimTools always shows suspended stacks. I guess that those are usually concurrency bugs where scripts deadlock against each other, or one enters a busy-wait state that never resolves, and then other scripts can't ever call into the blocked script. I can only fix this by reloading an older save from before the problem started.

 

My idea was to put in a bare-bones "stop the quest" function (my prototype was built into the "OnInit" function of a "Mymod_Reset" quest, which I could just "startquest" from the console), but I realized that stopping a quest doesn't actually stop the scripts (I could still load the MCM on a stopped MCM quest, and I tested with changeable conditional logic to verify that it t was running in real time and not just cached). So that doesn't solve it. Does anyone know an elegant way to do that? I have other ideas like trying to run code from something transitory instead of a quest, like an active magic effect, but maybe the effect stays around until the (deadlocked) code stops running? I can try some experiments, but maybe someone has an answer?

 

Yeah, I know, "write good code and don't create places where there can be a mutual deadlock or a concurrency bug", and I do my best, but it's nice to have a fallback option too, right?

 

By the way, @Hex Bolt, I said this above, but my compliments again on Love Sickness. I opened up the ESP, and then the scripts, and everything was just so understandable. It really did make a dozen things click at once in my head, and I went "Wow, I can see exactly how to make the change that I had in mind, but I also see how I could start from scratch and bring a dozen different things together in a new mod". I had used other mods to learn how to make patches, but yours was the first really good top-to-bottom example I studied.

Posted
31 minutes ago, scoul73 said:

That Reddit post says that running quests don't update for new properties, but that can be fixed by resetting the quest, just make sure to save off and restore the values of any properties that need to be preserved.

That has not been my experience.  For me, running quests have always updated with new properties (which is good, because resetting a quest for something like that would be pretty inconvenient).

 

33 minutes ago, scoul73 said:

By the way, @Hex Bolt, I said this above, but my compliments again on Love Sickness.

Thank you!  You made my day.

Posted
3 hours ago, Hex Bolt said:

That has not been my experience.  For me, running quests have always updated with new properties

 

Seconding this.  New properties are not an issue.  (Not sure if it matters, but the quest I'm using does auto-update by shutting itself down and restarting 5 sec after playerloadgame, so maybe thats why it works without a hitch?)

 

Enjoying this thread.  Good info in here.

Posted

I found another reason to use StorageUtil  instead of in-script Properties.

 

Papyrus arrays cannot natively be multi-dimensional, and their small size (128 elements max) means that standard tricks to treat single-dimensional arrays as multi-dimensional ones runs out of headroom fast. Instead of playing tricks with formlists or arrays of script objects with their own internal arrays, it is easier to dynamically generate a deterministic indexing key for StorageUtil as needed.

 

For example, instead of myarray[x][y][z] (which Papyrus doesn't allow), I just use StorageUtil with a key of "myscript.myarray.x.y.z". I am sure the performance would suck if I was trying to do heavy-duty math over it, but for statistic keeping and summation, performance should be fine.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

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