Search the Community
Showing results for tags 'papyrus'.
-
If I have a magic effect with a script that calls RegisterForSingleUpdate(), but that magic effect is then dispelled before OnUpdate() is called, does the script still have something registered and not cleared down? I ask because I have a "failsafe" where the magic effect is dispelled but I believe this may be redundant as I am only calling updates in a single chain like so: Event OnUpdate() if(condition) runSomething() RegisterForSingleUpdate() else UnRegisterForUpdate() endif endEvent
-
I'm trying to make a simple lesser spell that makes the player masturbate when triggered above an arousal threshold. I bypassed the arousal check for now because for some reason, the script couldn't get the player's arousal. There was also a debug statement on my code that returned the arousal value, that's where I found out it was returning 0. Is there something I'm not understanding about SLA? Also note I'm trying out the new SexLab Aroused NG.
-
dint understand how it works, here are images of what im trying to do: Add custom letter to player inventory, and remove item from player too. https://imgur.com/a/KnJdvK4 If you could use my custom item as an example id appreciate it as I'm having problems understanding mod speak atm. Thank you.
- 1 reply
-
- creationkit
- mod
-
(and 2 more)
Tagged with:
-
Hi All, I'm working on a small mod (my first) to add Giantess/shrinking content to SE. The gameplay loop is various events shrink the player and paying money to the follower restores height. What's working is a quest form for giantess-themed Misc topic info content and the a dialogue quest to regain height. The shrink/growth mechanism was originally based on an old script I found on the GiantessCity forum (my scripting skills are rudimentary). I first tried this in a new "Shrinking Disease" assigned to different Races but on play-testing it caused huge script lag and eventually crashed the game. This is the script: Scriptname mgtsShrink extends ActiveMagicEffect Import Utility float Property TargetScale Auto {The scale towards which the target should grow (e.g. 1.5 is 50% base size)} float Property GrowthPerEffect Auto {The amount of size change per effect, negative value causes shrinking (e.g. if .2, a target with scale .5 grows to .6 (.2 * .5 = .1 increase)} Event OnEffectStart(Actor akTarget, Actor akCaster) float currentScale = akTarget.GetScale() float nextScale = currentScale + currentScale * GrowthPerEffect float loopScale = currentScale float changePerLoop = 0.003 int numberOfLoops = 0 ; figure out if scale is already being effected Wait(0.2) float changedScale = akTarget.GetScale() if currentScale == changedScale if (GrowthPerEffect > 0) if nextScale > TargetScale nextScale = TargetScale EndIf numberOfLoops = Math.Floor(Math.abs(currentScale - nextScale)/changePerLoop) Debug.Notification("Entering Grow...currentScale=" + currentScale + ", nextScale=" + nextScale) While numberOfLoops > 0 loopScale+=changePerLoop akTarget.SetScale(loopScale) numberOfLoops-=1 EndWhile Else if nextScale < TargetScale nextScale = TargetScale EndIf numberOfLoops = Math.Floor(Math.abs(currentScale - nextScale)/changePerLoop) ;Debug.Notification("Entering Shrink...currentScale=" + currentScale + ", nextScale=" + nextScale) While numberOfLoops > 0 loopScale-=changePerLoop akTarget.SetScale(loopScale) numberOfLoops-=1 EndWhile EndIf if currentScale == akTarget.GetScale() Debug.Notification("This spell isn't powerful enough to change the target's size") Else ;Debug.Notification("Exiting Grow/Shrink...currentScale=" + akTarget.GetScale()) EndIf Else Debug.Notification("The script is already running!") EndIf EndEvent I then made a "Shrinking Spell" with a shrinking MagicEffect based on this script and called it in Papyrus End Fragments in Dialogue Topics ("shrinkspell.Cast(game.getplayer())"). This seems to avoid the script lag but the game is crashing regularly, probably because of the known issues with SetScale() (see the creation kit wiki). I believe NetImmerse can avoid this problem (see this reddit post). Can anyone recommend how to replace the SetScale() command in the script with NetImmerse instead, if this is indeed the problem? Cheers!
- 4 replies
-
- special editon
- mod
-
(and 1 more)
Tagged with:
-
PapyrusUtil LE/SE/AE View File Which file to download: For Skyrim LE (Oldrim): PapyrusUtil LE v33.zip For Skyrim SE (Pre-AE, 1.5.x runtime): PapyrusUtil SE v39.zip For Skyrim SE/AE (1.6.x+ runtime): PapyrusUtil AE v43.zip 1. Description SKSE plugin that allows you to save any amount of int, float, form and string values on any form or globally from papyrus scripts. Also supports lists of those data types. These values can be accessed from any mod allowing easy dynamic compatibility. Also adds the following papyrus commands: Toggle fly cam and set fly cam speed - TFC. Set menus on / off - TM. Adds an additional package stack override on actors. See ActorUtil.psc Replace any animations on selected objects. See ObjectUtil.psc Print messages to console. Get, set, save, or and load data to a custom external JSON file. See JsonUtil.psc PapyrusUtil.psc - version check & variable initialized arrays. StorageUtil.psc - store variables and lists of data on a form that can be pulled back out using the form and variable name as keys. See psc file for documentation. JsonUtil.psc - Similar to StorageUtil.psc but saves data to custom external .json files instead of forms, letting them be customizable out of game and stored independent of a users save file. ActorUtil.psc - actor package override. ObjectUtil.psc - animation replacement. MiscUtil.psc - some misc commands. 2. Examples Example 1: Setting and getting simple values StorageUtil.SetIntValue(none, "myGlobalVariable", 5) ; // enter none as first argument to set global variableStorageUtil.SetIntValue(Game.GetPlayer(), "myVariable", 25) ; // set "myVariable" to 25 on playerStorageUtil.SetFloatValue(akTarget, "myVariable", 75.3) ; // set "myVariable" to 75.3 on akTargetStorageUtil.SetStringValue(none, "myGlobalVariable", "hello") ; // enter none as first argument to set global variableint ivalue1 = StorageUtil.GetIntValue(none, "myGlobalVariable") ; // get the previously saved global variableint ivalue2 = StorageUtil.GetIntValue(Game.GetPlayer(), "myVariable") ; // get value of myVariable from player; // myGlobalVariable can exist both as int and string at the same time.; // Different type values are separate from each other.float fvalue = StorageUtil.GetFloatValue(akTarget, "myVariable") ; // get float value from akTargetstring svalue1 = StorageUtil.GetStringValue(none, "myGlobalVariable") ; // get "hello"string svalue2 = StorageUtil.GetStringValue(none, "myMissingVariable", "goodbye") ; // get "goodbye"; // an optional 3rd variable can be passed in the Get function to be returned if the given key "myMissingVariable" doesn't exists. Example 2: Saving object references Actor akCasterActor akTargetStorageUtil.SetFormValue(akTarget, "Friend", akCaster)Actor friend = StorageUtil.GetFormValue(akTarget, "Friend") as Actor Example 3: Value lists StorageUtil.IntListAdd(none, "myGlobalList", 5)StorageUtil.IntListAdd(none, "myGlobalList", 27)StorageUtil.IntListAdd(none, "myGlobalList", 183)StorageUtil.IntListAdd(none, "myGlobalList", 3)StorageUtil.IntListAdd(none, "myGlobalList", -12398); // iterate list from last added to first addedint valueCount = StorageUtil.IntListCount(none, "myGlobalList")while(valueCount > 0) valueCount -= 1 Debug.Notification("List[" + valueCount + "] = " + StorageUtil.IntListGet(none, "myGlobalList", valueCount))endwhile; // iterate list from first added to last addedvalueCount = StorageUtil.IntListCount(none, "myGlobalList")int i = 0while(i < valueCount) Debug.Notification("List[" + i + "] = " + StorageUtil.IntListGet(none, "myGlobalList", o)) i += 1endwhile; // Get the 2nd, 3rd, and 4th elements of the list into an arrayint[] myList = new int[3]StorageUtil.IntListSlice(none, "myGlobalList", myList, 1) ; // starts pulling elements from the list starting from from the 1 index; // skipping the 0 index value, "5" will fill the papyrus array until it runs out of either list or papyrus array elementsDebug.Notification("2nd: " + myList[0]) ; // prints "2nd: 27"Debug.Notification("3rd: " + myList[1]) ; // prints "3rd: 183"Debug.Notification("4th: " + myList[2]) ; // prints "4th: 3"; // remove 27 from the listStorageUtil.IntListRemove(none, "myGlobalList", 27); // remove last element of listStorageUtil.IntListRemoveAt(none, "myGlobalList", StorageUtil.IntListCount(none, "myGlobalList") - 1); // set first element to -7StorageUtil.IntListSet(none, "myGlobalList", 0, -7); // find first occurance of element in listint index = StorageUtil.IntListFind(none, "myGlobalList", 183)if(index < 0) Debug.Notification("Not found!")else Debug.Notification("Element 183 is at index " + index)endif; // clear listStorageUtil.IntListClear(none, "myGlobalList"); // create a new list from a papyrus arrayfloat[] newList = new float[3]newList[0] = 4.04newList[1] = 39.2newList[2] = -42.25StorageUtil.FloatListCopy(PlayerRef, "myCopiedList", newList)Debug.Notification("Copied value 0 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 0)) ; // 4.04Debug.Notification("Copied value 1 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 1)) ; // 39.2Debug.Notification("Copied value 2 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 2)) ; // -42.25 Example 4: Saving values that are shared among all savegames in an externally saved file. JsonUtil.SetIntValue("MyModConfig.json", "AnswerToLifeUniverseEverything", 42); // (optional) Save any changes made to your file and creates it if it does not yet exists.; // This is done automatically without needing to be done manually whenever a player saves their game.; // Files are saved and loaded from Skyrim/data/SKSE/Plugins/StorageUtilDataJsonUtil.Save("MyModConfig.json") ; // ... Start a new game ...int mySetting = JsonUtil.GetIntValue("MyModConfig.json", "AnswerToLifeUniverseEverything") ; // mySetting == 3; // Alternative version using the globally shared external file; // All mods using these commands share this file, saved/loaded from Skyrim/data/SKSE/Plugins/StorageUtil.jsonStorageUtil.SetIntValue("AnswerToLifeUniverseEverything", 42); // ... Start a new game ...int mySetting = StorageUtil.GetIntValue("AnswerToLifeUniverseEverything") ; mySetting == 3 3. Requirements SKSE latest version: http://skse.silverlock.org/ Address Library for SKSE Plugins: https://www.nexusmods.com/skyrimspecialedition/mods/32444 4. Installing Use mod manager or extract files manually. 5. Uninstalling Remove the files you added in Installing step. 6. Updating Just overwrite all files. 7. Compatibility & issues Should be compatible with everything. 8. Credits Ashal - continued maintenance & refactoring of original plugin's source code h38fh2mf - original version, idea, address library conversion SKSE team - for making this plugin possible milzschnitte - for suggestions eventHandler, Expired, aers, arha, ianpatt - SKSE64 conversion & update assistance Submitter Ashal Submitted 12/07/2013 Category Modders Resources Requires SKSE, Address Library Special Edition Compatible Yes
-
Is there a way to insert the speaker's name into the "Topic Text" for player dialogue? For example, currently the player says something like "Hello, do you want to trade?" but I'm hoping there is a way to use text replacement or aliases to have it say "Hello <Speaker Name>, do you want to trade? This is for a player dialogue generic quest that could be used with various different followers or NPCs and not specifically tied to a "quest" quest or scene. Could this be solved with a papyrus code fragment attached to the quest under the "quest stages" tab? (though I haven't tried to make a quest with any stages which seem required for papyrus fragments) Thanks!
-
Section 2 - Papyrus logs From the Creation Kit: Papyrus is an entirely new scripting system created specifically for the Creation Kit. Like any scripting environment, Papyrus can be a daunting system, especially for those who are unfamiliar with other scripting or programming languages. This page is intended to serve as your portal to all things Papyrus, and there are several ways to use it, depending on expertise level. This means that a Papyrus log is a list (log) of steps taken by Bethesda's scripting system (Papyrus) while your Skyrim game is running. As such it can be useful in the detection of all types of errors, including crashes (CTDs). Sounds good so far but many of my readers will have played Skyrim for years now and never run across a Papyrus log on their computer. There is a good reason for that; Skyrim's default setting for papyrus logging is "off". So, the first step in doing anything with it is turning it "on". 1. Turning on papyrus logging There are two ways to do this and I have done them both at different times. The easy method is to turn it on in Bethini (mentioned in my last blog post) and found here: https://www.nexusmods.com/skyrim/mods/69787 To turn on papyrus logging with Bethini simply start the tool. Once there, press the "General" tab listed across the top. Tick "on" the buttons that say "logging" and "debug info". Save your changes and exit. The next time you play Skyrim you will generate a papyrus log. The other way to turn on papyrus logging is the harder, technosavvy way. First, you have to find your Skyrim.ini file. Mine can be found here: C:\Program Files (x86)\Steam\steamapps\common\Skyrim. However, in the Technical Threads the path given is My Documents > My Games > Skyrim > skyrim.ini. From my reading it appears that you can find Skyrim.ini in one or the other place and sometimes both. I would suggest that you use your computer's Search engine to find it and, when you do, make yourself a note (because when we're done you're going to want to go back there). Having found Skyrim.ini and opened it in your favorite editor (I've always just used Notepad), you should be able to scroll down until you reach a section that looks very much like this: [Papyrus] fUpdateBudgetMS = 1.6 fExtraTaskletBudgetMS = 1.6 fPostLoadUpdateTimeMS = 2000.0 iMinMemoryPageSize = 128 iMaxMemoryPageSize = 512 iMaxAllocatedMemoryBytes = 76800 bEnableLogging = 0 bEnableTrace = 0 bLoadDebugInformation = 0 bEnableProfiling = 0 This example was also taken from the Technical threads. Mine was in alphabetical order when I found it. Do not worry about the order and do not worry if all the entries listed here are not in your Skyrim.ini. This one has been tweaked, a lot. To enable logging change the three settings (bEnableLogging, bEnableTrace and BLoadDebugInformation) from 0 to 1 (this is what Bethini did for you). Make certain that you save your changes and exit your editor. 2. Finding you papyrus logs Before you go looking for your papyrus logs you must first play some Skyrim. To this point you have had nothing for the script editor to log since you just turned it on. So, go back to Skyrim and, for a change, try to make the game break in the manner that is bothering you. Whether this is misbehaving NPCs, strange happenings in Whiterun or CTDs, you want it to occur. As soon as it does happen close your game and exit. Now, you can use your computer's Search engine to find your papyrus logs also. The default for finding the logs though is this: C:\Users\Owner\Documents\My Games\Skyrim\Logs\Script. This folder will, eventually have four logs in it. Papyrus.0.log will always be the most recent with 1,2 and 3 following. Here is an example of a papyrus log: Papyrus.0.log 3. Using the papyrus log To the best of my knowledge there is no definitive guide to using papyrus logs. When I first started looking at them I thought that they were called papyrus logs because they might as well have been written in hieroglyphics. It's not quite that bad. Aside: It is not my intention, therefore, to make anyone an expert in papyrus log debugging. If you look at one that has been run during a Skyrim "problem" you are likely to find things listed as issues of various sorts. You'll see things like "VM freezing." Sounds ominous right? Actually, it's normal and expected. However, with some time and inclination you will discover that you can make more and better diagnoses for yourself. At the very least, you now know how to turn it on and make it available to others if you need to. A few things I've learned that I would like to leave you with: 1) Whatever your problem is, it is not likely the last thing listed in the log. Logically, you would expect it to be so since you shut the game off after "discovering" the problem but the editor is too fast for you. You are likely going to have to look back several lines before you find your issue. 2) If you have a lot of warnings and issues at the very beginning of your log you likely have a mod or mods that do not want to play nice. It may be that your next step is to isolate and remove those mods and see if your problem doesn't resolve, and 3) The obvious answer is not always the right one. I worked hard to debug a modded Skyrim game and the log kept throwing up SlaveTats as the seeming culprit. Eliminating it didn't solve my problem it simply moved on to blaming a different mod. I did eventually work out the issue but my lesson was learned. Here is an (old but reliable) guide to some of the things that you can find in your Skyrim log: https://afkmods.iguanadons.net/index.php?/topic/4129-skyrim-interpreting-papyrus-log-errors/ Last note on papyrus logs. As I mentioned, the default for papyrus logging is "off". There is actually a very good reason for that. Doing the logging causes a significant performance hit. As a result, when you are satisfied that your problem is resolved, be certain to return logging to "off" (either with Bethini or manually). Otherwise, you may find that your game is significantly slowed down. Hopefully, performance improvement hints next.
-
Version 4.4
410,974 downloads
Which file to download: For Skyrim LE (Oldrim): PapyrusUtil LE v33.zip For Skyrim SE (Pre-AE, 1.5.x runtime): PapyrusUtil SE v39.zip For Skyrim SE/AE (1.6.x+ runtime): PapyrusUtil AE v43.zip 1. Description SKSE plugin that allows you to save any amount of int, float, form and string values on any form or globally from papyrus scripts. Also supports lists of those data types. These values can be accessed from any mod allowing easy dynamic compatibility. Also adds the following papyrus commands: Toggle fly cam and set fly cam speed - TFC. Set menus on / off - TM. Adds an additional package stack override on actors. See ActorUtil.psc Replace any animations on selected objects. See ObjectUtil.psc Print messages to console. Get, set, save, or and load data to a custom external JSON file. See JsonUtil.psc PapyrusUtil.psc - version check & variable initialized arrays. StorageUtil.psc - store variables and lists of data on a form that can be pulled back out using the form and variable name as keys. See psc file for documentation. JsonUtil.psc - Similar to StorageUtil.psc but saves data to custom external .json files instead of forms, letting them be customizable out of game and stored independent of a users save file. ActorUtil.psc - actor package override. ObjectUtil.psc - animation replacement. MiscUtil.psc - some misc commands. 2. Examples Example 1: Setting and getting simple values StorageUtil.SetIntValue(none, "myGlobalVariable", 5) ; // enter none as first argument to set global variableStorageUtil.SetIntValue(Game.GetPlayer(), "myVariable", 25) ; // set "myVariable" to 25 on playerStorageUtil.SetFloatValue(akTarget, "myVariable", 75.3) ; // set "myVariable" to 75.3 on akTargetStorageUtil.SetStringValue(none, "myGlobalVariable", "hello") ; // enter none as first argument to set global variableint ivalue1 = StorageUtil.GetIntValue(none, "myGlobalVariable") ; // get the previously saved global variableint ivalue2 = StorageUtil.GetIntValue(Game.GetPlayer(), "myVariable") ; // get value of myVariable from player; // myGlobalVariable can exist both as int and string at the same time.; // Different type values are separate from each other.float fvalue = StorageUtil.GetFloatValue(akTarget, "myVariable") ; // get float value from akTargetstring svalue1 = StorageUtil.GetStringValue(none, "myGlobalVariable") ; // get "hello"string svalue2 = StorageUtil.GetStringValue(none, "myMissingVariable", "goodbye") ; // get "goodbye"; // an optional 3rd variable can be passed in the Get function to be returned if the given key "myMissingVariable" doesn't exists. Example 2: Saving object references Actor akCasterActor akTargetStorageUtil.SetFormValue(akTarget, "Friend", akCaster)Actor friend = StorageUtil.GetFormValue(akTarget, "Friend") as Actor Example 3: Value lists StorageUtil.IntListAdd(none, "myGlobalList", 5)StorageUtil.IntListAdd(none, "myGlobalList", 27)StorageUtil.IntListAdd(none, "myGlobalList", 183)StorageUtil.IntListAdd(none, "myGlobalList", 3)StorageUtil.IntListAdd(none, "myGlobalList", -12398); // iterate list from last added to first addedint valueCount = StorageUtil.IntListCount(none, "myGlobalList")while(valueCount > 0) valueCount -= 1 Debug.Notification("List[" + valueCount + "] = " + StorageUtil.IntListGet(none, "myGlobalList", valueCount))endwhile; // iterate list from first added to last addedvalueCount = StorageUtil.IntListCount(none, "myGlobalList")int i = 0while(i < valueCount) Debug.Notification("List[" + i + "] = " + StorageUtil.IntListGet(none, "myGlobalList", o)) i += 1endwhile; // Get the 2nd, 3rd, and 4th elements of the list into an arrayint[] myList = new int[3]StorageUtil.IntListSlice(none, "myGlobalList", myList, 1) ; // starts pulling elements from the list starting from from the 1 index; // skipping the 0 index value, "5" will fill the papyrus array until it runs out of either list or papyrus array elementsDebug.Notification("2nd: " + myList[0]) ; // prints "2nd: 27"Debug.Notification("3rd: " + myList[1]) ; // prints "3rd: 183"Debug.Notification("4th: " + myList[2]) ; // prints "4th: 3"; // remove 27 from the listStorageUtil.IntListRemove(none, "myGlobalList", 27); // remove last element of listStorageUtil.IntListRemoveAt(none, "myGlobalList", StorageUtil.IntListCount(none, "myGlobalList") - 1); // set first element to -7StorageUtil.IntListSet(none, "myGlobalList", 0, -7); // find first occurance of element in listint index = StorageUtil.IntListFind(none, "myGlobalList", 183)if(index < 0) Debug.Notification("Not found!")else Debug.Notification("Element 183 is at index " + index)endif; // clear listStorageUtil.IntListClear(none, "myGlobalList"); // create a new list from a papyrus arrayfloat[] newList = new float[3]newList[0] = 4.04newList[1] = 39.2newList[2] = -42.25StorageUtil.FloatListCopy(PlayerRef, "myCopiedList", newList)Debug.Notification("Copied value 0 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 0)) ; // 4.04Debug.Notification("Copied value 1 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 1)) ; // 39.2Debug.Notification("Copied value 2 = " +StorageUtil.FloatListGet(PlayerRef, "myCopiedList", 2)) ; // -42.25 Example 4: Saving values that are shared among all savegames in an externally saved file. JsonUtil.SetIntValue("MyModConfig.json", "AnswerToLifeUniverseEverything", 42); // (optional) Save any changes made to your file and creates it if it does not yet exists.; // This is done automatically without needing to be done manually whenever a player saves their game.; // Files are saved and loaded from Skyrim/data/SKSE/Plugins/StorageUtilDataJsonUtil.Save("MyModConfig.json") ; // ... Start a new game ...int mySetting = JsonUtil.GetIntValue("MyModConfig.json", "AnswerToLifeUniverseEverything") ; // mySetting == 3; // Alternative version using the globally shared external file; // All mods using these commands share this file, saved/loaded from Skyrim/data/SKSE/Plugins/StorageUtil.jsonStorageUtil.SetIntValue("AnswerToLifeUniverseEverything", 42); // ... Start a new game ...int mySetting = StorageUtil.GetIntValue("AnswerToLifeUniverseEverything") ; mySetting == 3 3. Requirements SKSE latest version: http://skse.silverlock.org/ Address Library for SKSE Plugins: https://www.nexusmods.com/skyrimspecialedition/mods/32444 4. Installing Use mod manager or extract files manually. 5. Uninstalling Remove the files you added in Installing step. 6. Updating Just overwrite all files. 7. Compatibility & issues Should be compatible with everything. 8. Credits Ashal - continued maintenance & refactoring of original plugin's source code h38fh2mf - original version, idea, address library conversion SKSE team - for making this plugin possible milzschnitte - for suggestions eventHandler, Expired, aers, arha, ianpatt - SKSE64 conversion & update assistance- 27 reviews
-
71
-
Version 0.3.0
83 downloads
Nosis' Locks: Arbitrary Read/Write Locks "Protects your code against CTDs better than Trojan protects against STDs" v0.3.0 Description NosLocks gives you an generic read/write locking mechanism to use as you see fit. It abstracts out the GotoState() paradigm of Papyrus effectively allowing a single script to have multiple states at the same time (via semaphore barriers). This library provides classic read/write locking capabilities: many readers and zero writers, or one writer and zero readers. WARNING Semaphore barriers are dangerous if you aren't careful with them. Only you can prevent dead-locks by avoiding modding while intoxicated! BETA Notice (No Longer a Warning) v0.3.0 is a mid-beta release. It's barely been tested under real world conditions but has been heavily self-tested at this point. The API will likely change a bit as well. I'm putting it out there mostly so that other modders can try it out and give feedback/make requests before I move it to a more late beta/stable state and lock the API. It's core has been proven enough at this point that I encourage you to start using it for any projects that you don't plan on releasing in the next few weeks (I expect to declare it late beta/stable by then). My plan is simple. Once it's stable the API gets locked and, other than an unexpected bug fix or two, freeze development of it entirely. So now's the time to make requests, suggestions, etc. Why Would I Need This? If you're a modder then it will help you elegantly protect your code from race conditions, particularly with complex multi-state code that "yields the floor" at inopportune moments. If you're not a modder it might be because another mod is using this (doubtful right now). You can stop reading here unless your curiosity is just boiling over. Dependencies v0.3.0: Maybe SKSE? If you want great debug information then it's a must have. Otherwise I believe nothing should be broken if you don't have it. If someone could test WITHOUT SKSE (try running the debug dump and see what's in your logs) and get back to me, I'd greatly appriciate it. v0.2.x: None. Zip. Zilch. Not even Skyrim.esm. Nothing. Upgrading From 0.2.x If you had any allocated locks in your save file then a clean save is required due to a bug that's now been fixed. Upgrading From 0.1 Clean save. Start over. Your code will be broken anyhow due to API changes. What's New (from 0.2.x to 0.3.0) Bug fixes (two major), support for ActiveMagicEffect as owner, and a bunch of debugging methods/capabilities. In detail, the following was done... What's New (from 0.1 to 0.2.x) A lot. Heavy API refactoring. No bug fixes because I didn't find any . Two major API usage changes: Locks are now "owned" so that they may be garbage collected when deadbeef. You no longer have to manually get the manager. Everything you need is scoped globally. In detail the following has changed... Reference Guide NosLocks: NosLock: Console Commands: Quick-Start Example The below demonstrates NosLocks in a nutshell... NosLock MyLockEvent OnInit() MyLock = NosLocks.AllocLockForForm(Self)EndEventInt Function GetSomething(Actor whatever, Int lockID=0) MyLock.ReadLock(lockID) Int res = gottenFromSomeProtectedData MyLock.Unlock() Return resEndFunctionFunction SetSomething(Actor whatever, Int lockID=0) MyLock.ReadLock(lockID) If (!GetSomething(whatever, lockID)) lockID = MyLock.UpgradeLock() If (!GetSomething(whatever, lockID)) ; check again manipulateSomeProtectedData EndIf EndIf MyLock.Unlock()EndFunction . In-Depth Example Owned vs. Unowned Locks In 0.1 all locks were unowned. Starting with 0.2 it's now strongly preferred that you allocate owned locks. If the script using the lock is a Form use AllocLockForForm(). If it's an Alias use AllocLockForAlias(). If it's an ActiveMagicEffect use AllocLockForAME(). Explict lock freeing is still preferred of course, but now the locks can be garbage collected when someone uninstalls your mod. A Note About Lock Upgrades If you are unfamiliar with read/write locks (but understand the concept of mutexing), keep something in mind: you can not make assumptions about the state of your data after the upgrade. The upgrade is not an atomic operation. During the upgrade another writer might have come along and changed your protected data. Basically... Write Lock IDs Two things... First, 0 is the only invalid write lock ID, so don't expect 0. Second, they should be passed around in a fully reentrant manner. Don't globalize them (it's tempting... I know) or you might find yourself scratching your head about why you're dead-locking when it looks like you shouldn't be. Lock Leaking Release those locks back to the manager if they have a limited life time! Be kind. There's 128 of them but that runs out quickly if they aren't released but new ones keep getting acquired. Examples: If you're using a lock in a magic effect, NosLocks.FreeLock(MyLock) on the effect finish event! If you have a lock for a quest that only is used when the quest is running, release it when the quest stops! If you have "permanent" locks and start an "uninstall" procedure, give them back or they will walk in limbo for eternity! If you don't remember to release then owned locks will be garbage collected periodically (not optimum of course), but unowned locks will be lost forever! Leaking unowned locks may eventually result in the player's save game being 100% deadbeef. Pre-emptive FAQ Please read before asking questions/asserting positions as they might already be addressed. Please try it out (not on a stable branch of your project) and let me know what you think. Particularly if there's issues or feature/API request changes. ~ nosis -
PART I: Concepts. _________________ Much has been said about "script heavy" mods and how they can break your game. But over the years, the notion that scripts can break your game has become less and less of an understood phenomenon, and more of a creepy boogeyman to scare children away from using scripted mods. I'm here to shed some light on this matter, not because I'm an expert nor because "I know it all", but instead, because I've investigated this phenomenon, I've witnessed how it works with my own eyes, and I've learnt to take measures on how to solve it most of the times. This article may be of beginner level to some, but I intend it both, to help beginner modders learn something, and to attempt to make normal users understand a bit more about why their game could be "broken" and why it's not because of "script heavy mods' fault". First of all I have to say one very important thing: THERE ARE NO SCRIPT-HEAVY MODS. - "But Myst, everyone knows if you install mods with scripts, you can break your game" Lies. Scripts won't break your game. Bad coding, however, can, and it poses a real danger, but that's not something that happens because of "scripts being scripts", it happens because a modder makes a mistake and publishes a mod with bad coding. Second, we should clarify what does people understand by "breaking one's game". Because this notion may seem generic and common, but it may be different for everyone. Some people says it relates to having frequent CTDs, while others, such as myself think it relates to when the game's scripts seemingly stop working and you can't do anything because scripts dont do what they're supposed to do. I have experienced this kind of "broken game" myself as well, and I also fought to understand the reasons for it, and ultimately, fix them. Script lag, is one thing, but CTDs and broken quests are a completely different one, and it's usually related to stuff such as a bad load order, mod incompatibilities, corrupt assets (IE missing textures or broken nifs) or bad graphic settings but neither of these has anything to do with scripting. Alternatively, some scripts can cause CTDs, but that's a rare thing and it's usually related to other types of bad coding which I wont refer to on this article. Third, there is another misconception on what a "script heavy mod" even is. Because it can be A mod with many scripts. A mod with LONG scripts. A mod with complex scripts that interact with many things A mod with scripts that never stop running in the background And here's the shocker, because NONE of the above constitutes ANY kind of danger to the game. A friend developed an entire mod to test the effects of scripts on the game and try to pinpoint an exact cause of script issues, it was designed to overload the game in every possible way as described by the "script heavy" myth, and his results concluded that the game can candle thousands and thousands of instances of scripts simultaneously in the background, and keep running without even coming close such a thing a s "limit". The mod was designed to push Skyrim's script engine's limits beyond what any combination of mods can ever achieve. PART II: How It Happens ________________________ What was found after such testing, however, is that there is a situation that DOES exist, and can be mistaken for a "broken game" because in practice, it is. I will not describe every possible case of "bad code", because I'm certain there's more than one screw up that can cause game issues, but I will talk about one of the most frequent ones which is by far, one, if not the most common reason of script issues. And now, I'm referring to something I call "script saturation" although I'm not sure if that's the correct term. This phenomenon, occurs when the game is receiving lots and lots of simultaneous script calls per second. As a result, a chain of events begins to occur. First,you'll notice the game can start lagging in terms of scripted events. If something was supposed to happen when you get hit, you'll notice it happens later, if you use a scripted spell, the effect will also come with a delay. This lag can be something tolerable, or it can be massive and you end up seeing the effects of a script, as far as 5 minutes later, or more. Second, if you have enabled the log in your Skyrim.ini, you'll notice the existence of the infamous stack dumps. Stack dumps, arent a problem per-se, but they are warning you of the current script saturation you're experiencing. Script saturation, in the internal mechanics of the game, means you're getting too many script calls and the game isn't capable of answering them all in time, so it stores pendant calls for the next frame in hope it can finish in time, so as described by the wiki (somewhere I can't recall), the game delays the execution of scripts. The visual result was evident since you started seeing script lag occur and stuff happening seconds, to minutes after they were supposed to. PART III: Why it Happens - Section A): Fundaments of Script Lag ________________________ - "I knew it!, I knew script heavy mods existed, you're just trying to confuse me!" Technically, a form of "script heavy" mod can exist, but I just call that BAD CODE. See, the thing is, it doesnt happen because "a mod has many scripts" or because "the scripts run in the background" or "are too long". It doesnt happen either because "you have too many mods" or "you combined more than 2 or 3 script-heavy mods in one install". It happens EXCLUSIVELY for the action of a single, lone script, which can come from anywhere, at any time. Any script can have bad coding, and size doesnt matter. If you want an example, here is one: The pevious script, has only 2 events, and does absolutely nothing in practice. But what it does do, is cause an event to get locked on processing as soon as it arrives. Now, during a battle, a concentration spell can spam these events dozens of times each frame, and the longer such concentration spell is held on the bearer of this script, the more script calls that begin to accumulate on them, until at a given moment, the barrier will be breached and your papyrus log will generate a stack dump. When that happens, if you were expecting something to happen with the script, say a shader apply, you'd notice shaders begin being applied later. And this is how a single script with 2 spammy events and no precaution, can generate script spam and slow down your script engine. Notice it's not long, it's not complex, it doesnt even do anything it just spams events. The probem is that the more events that get spammed, the more likely the game gets saturated, and other scripted mods will suffer the script lag as well. PART III: Why it Happens - Section B): "A Broken Game" ______________________ The game, can normally recover from these kinds of situations, after all, no mod is going to have a concentration spell casted on an actor during the entire game... right?... Wrong. Some mods, use mechanics that do apply constant effects on the player, ALL the time. And if you have a mod that's set to respond to these events, if it's not set correctly, there's a chance it will get saturated. The first part was correct, the game DOES try to recover from stressful scripting situations, and it does this by simply finishing answering all the script calls untill the demand is back to what it can answer on a single frame, AKA a relaxed environment. It's simple, the stress rises up, the game deals with it as best as it can, then the stress ends and the engine can relax again. But what if it never relaxes? This is when the real danger of event spammers comes into play. It happens when you have a stressful situation that generates enough script calls to saturate the engine, and you dont ever get to the moment when it finishes answering them all. I dont know the exact number, but as a mental exercise, lets say the game can answer 100 script calls per second, from a single concentration magic effect. Now, picture that you have a combination of mods, that allows an event spammer as described above, coupled with a stressful situation, say a dragon, 2 companions and 6 mages. Suddenly, you're no longer receiving 100 script calls or less per second but 400 instead. The game will queue 300 to be answered on the next time, but when that time comes, we will get another 400 By the third time it checks, you will be getting a total sum of 900 delayed calls because it can only answer 100 each time. And the lower your framerate, the less actual execution time the script engine has to catch up with the increasing demand. Now imagine the "battle" never ends, and you get this eternal event spam. You're never ever going to see scripts working again because the lag is infinite. This is close to what happens when you have an event spammer mod combined with a mod that creates a concentration, or cloak effect, being permanently applied to the player, since more than a few mods apply unseen effects on the player. This is the reason why scripts stop working and people say their game is "broken". PART IV. How to Fix It ________________________ The solution, for mod makers, is simple, for mod users, not so much as they have yet to learn to identify a troublesome mod and either fix it or remove it. But for modders, it begins by knowing the first rule, which is: BEWARE of event spammers. Try not to use those kinds of events at all, avoid effects that cause repeated calls such as cloaks or concentration type effects if possible, but if you absolutely MUST use those kinds of events/effects, you should always use an EVENT FILTER, like this: The above example is a State filter, and it will ensure only one event is processed at a time, and when it's done, the gate will be opened for another event. Alternatively, a Bool filter also works The second example is not as effective as the first one since it still has code inside, but it will reduce script spam. Note that none of these methods is flawless. Even when using them, mods are still likely to get a stack dump or two every once in a while, but by reducing their capacity to spam, you will ensure that the game's load can be overcome more than 90% of the times. So you see, scripts are not evil, but we have to be careful about how we design our mods because it's very easy to screw up and end up regretting it later. Most people never bothers to understand the mechanic of this phenomenon, and when their game malfunctions, they look for the causes of it on myths such as "scripts being evil" when it's not about having many scripts, or complicated scripting. A single mod is more than capable of "breaking" one's game under certain circumstances.
-
This is the most actual stability guide that will help you to fix known crashes, technical problems, bugs and improve overall gaming experience as much as the old 32-bit game engine allows. Philosophy of Crashes Complete text TL;DR Part I - Memory Patches Skyrim's memory model was designed to work with fixed sized memory blocks. There're a lot of memory blocks in the game process, but we're insterested in 2 main blocks. These blocks hold dynamic in-game data such as NPCs, quests, items and models of everything you see in the game together with script data and something else. It's obvious, the more you mod your game the more data will be stored inside these blocks. But, their sizes are fixed and if stored data overflow crash happens. We have 2 ways to fix the problem: - SSME: Largely increase their size to fit appetite of modern skyrim. - OSAllocators: Remove these blocks and store its data in shared area using OS allocations. Both ways work, both ways have its pros and cons, and both ways will be shown here. You can choose yourself what path to go: SSME (Recommended): + Found by me as the most stable way + Uses default alloc method that engine supposed to work with - Block size increased but still limited (enough for vast majority of players) - Size of Block 2 cannot be modified (but it's very hard to overflow the block even with a hardly modded game) OS Allocators: + Blocks completely unlimited, all data stored in shared process area + Supposed to be faster - Less stable by my tests and much more sensitive to resource data - May require additional calibrations Opinion: I always prefer SSME way. for me it behaves more stable and less buggy, however, if your game is very very extremely modded limitations of SSME way may affect. However, I met only one person yet who was able to mod the game to such condition. If you still don't know what to choose go SSME way, it will fit 99% of players. SSME way: OS Allocators way: Part II - Animation Fixes Installing too many animations may bring unwanted consequences, in the worst case you'll not be able to load or start a new game. Even if you don't have additional animations it's still recommended to go through the part. FootIK Solution Animation Limit Solution Part III - Optional This part is not strictly required, however can improve overall game quality and/or stability under specific circumstances. HDT Physics Extensions Crash Fix Papyrus performance High FPS Patches Windows 7 users warning Critter Spawn Fix Dangerous Mods PlayerSuccubusQuest (https://www.loverslab.com/files/file/667-psq-playersuccubusquest-for-sexlab/) - cum inflation feature of this mod produces infinite threads what eventually breaks papyrus script engine together with your game save. Disable this feature if you want to play the mod safety. Simply Knock & StorageUtil (https://www.nexusmods.com/skyrim/mods/73236) - this mod uses StorageUtil on gamesave loading to get configuration with relative access "../SimplyKnockData/Common.json". The path is converting by StorageUtil with codecvt function which in that case may work incorrect on Windows OS and access null ptr what crashes the game. It is not really a Simply Knock or PapyrusUtil problem, however are triggering by them. Crash is platform dependent and not guaranteed to pop up for everybody. Dragonborn In Distress (https://www.loverslab.com/files/file/8215-dragonborn-in-distress/) - may cause CTD while save attempt in active quest stage for unknown reasons (I didn't investigate it). Since ver 2.03+ seems to be fixed. Recommendations - Don't alt-tab from the game. It's simply not supported by Bethesda even with vanilla game. But if you really need to alt-tab at least make a savegame, because returning back you may meet a CTD. - Do not disable your mods from load order during game time. It may break your scripts, quest and gamesave completely even if clean it up. - Always make backups. For everything. For example some mods replace vanilla scripts and if you turn off a mod like this it may break the script completely. So it is very important to do a proper clean up and restoration after deletion. Backups saved my Skyrim many times. - Keep your main save away from new mods and even mod updates. You cannot know will you like this mod or not, will it bring any problems and so on. If you want to try a new mod i recommend to start a new game and try it out. And after some time if everything looks good include the mod to your main save. - If you pack your mods into BSA be careful with seq files. BSA archive should have proper flags if contains a seq file. Otherwise it will be unable read and break the mod. Furthermore, new game will be required even if you fixed it. - If you want to try a new mod, at first better to put it at the end of the load order. If you do it this way the new mod will override everything it needs and it will be less chances to break something for the new mod because of incompatibility with others. However, with this action you still can break old mods in case of incompatibility. Useful Game paths Links Wide collection of guides and useful links to solve many game related problems by @worik https://www.loverslab.com/blogs/entry/6768-patchwork-how-do-i-fix-this-and-that-a-link-collection/ Well done blog about how to properly setup and mod the game by @donttouchmethere https://www.loverslab.com/blogs/entry/7521-conglomerate-01-basic-setup-for-modifing-skyrim-le-well-my-skyrim-got-messy-now-i-have-to-clean-it-up-so-i-thought-to-myself-why-not-start-a-blog-about-that/
- 36 comments
-
49
-
Things in the Dark could have been one of the greatest LL quest mods ever. Unfortunately, it has a lot of bugs. The "modified" version fixes some of them, making it sort of playable. Almost every quest in TitD is a broken progression blocker. Why does TitD have so many bugs? Why does Delzaron consistently make mods that have so many faulty quests? To be fair to Delzaron, he's not the only modder with this problem, he's just the most prolific. I could name a few other authors, with buggy mods, who script their quests in exactly the same way. When trying to patch up my TitD so I could play through it some more, I started to see various common recurring problems, with how the quests were set up. I think TitD serves as a "how not to" guide on writing quests, and there are some cautionary lessons to be learned from it. Most broken quests involved a simple mistake or typo, or some non-updated value, but those mistakes all have a common root cause: unnecessary complexity. The quests are not cookies, they are snowflakes, and this massively expands the effort required to write and test them, to the point where it is impossible. Because it was effectively impossible, Delzaron failed. How can we ensure that quest development remains in the realm of possible, or better still easy? Rather than hard, or impossible? As mentioned above, the complexity and hardness comes from a few oft-repeated mistakes. As these common mistakes are so well established, and known to be flaws in so many mods, you'd think that we'd stop seeing them. Skyrim quests aren't written in a world of automated testing and extensive QA, and they can not easily be continuously updated without upsetting players with running games. Mods get periodic releases, and those releases should be as robust as possible to avoid player angst; quest logic should be as defensive as possible. If it allows the player to cheat, it's better than a quest that easily becomes blocked. When you do multiple bad things, you compound the player's pain. When you do multiple good things, you reduce the risk of your mod breaking, even if you make a mistake somewhere else. The following practices combine together to help stop the root cause of numerous quest bugs that have a common form. 1) Quest stages should be self contained What I mean by this, is if you do "setstage" on a quest, the quest should be set completely in the correct state. It shouldn't be possible to run setstage and have the quest go into an inconsistent or broken state. For example, some player dialog becomes available at stage 50, if you have collected 10 mushrooms. Choosing that dialog runs a fragment that sets another NPC's faction, sets that NPC's location, and puts a key in their inventory. Then it sets quest stage to 60. What's wrong with this? The problem is that if you want to test this quest, you cannot simply set the stage to 60, because the NPC won't be set up properly. If this quest breaks, you cannot simply set the stage to 60, because, again, the NPC won't be set up properly. How it should have worked is quite simple: the dialog fragment should have done ONE thing, set the quest stage to 60. Then, the trigger script for stage 60 should have set up the NPC's faction, location and inventory. In this case, you can test stage 60 of the quest without any additional preparation, the stage is "self contained". Following this rule avoids so many problems. The bad pattern occurs when the logic to set up the quest stage is hidden away somewhere in a different stage. This is not good modularity or encapsulation. Quest stages are a developer tool to help organize work, but many modders use them with no clear objective in mind, just because they think they should 2) Quest stages should serve a clear purpose If you have quest stages that exist without objectives, alarm bells should probably go off. Why does that stage exist? What state is it representing? In most cases, a quest stage should have a matching quest objective that the player can see, and the stage advances when that objective is met. Such quests are easy to understand as a player, or developer, and are easy to test. The worst example of this is ping-pong quests that bounce quest stage updates between multiple quests, where none of the stages were strictly necessary, and served only to update a stage on a foreign quest that itself serves no purpose at all but to update back to the previous quest. Multiple quests become entangled for no reason. However, there are legitimate reasons to have a stage with no objectives, particularly if the goal is to sequence events in a simple way. A classic example of event sequencing is the multi-step dialog. The modder uses quest stages (without objectives) to make the player have a conversation with an NPC in a certain order. Each time the player addresses the correct topic, and gets down to the right branch, the quest steps forward, unlocking the next topic and disabling the old one. This is a reasonable way to turn the dialogs on and off, because it's easy to set the quest stage to test the dialogs, and the fragments that advance the dialog stages are simple owning-quest setstage calls. Additionally, testing a quest stage in a condition is quite efficient. This pattern is broken when the modder does strange, random things: The progression of stages is erratically numbered, or jumps back and forth between dialog on different quests in a way that makes following it in the CK extremely tiresome. The stages actually go down as well as up. Very confusing. The stages intermix variant ways to control the quest, sometimes stage number, sometimes a variable. 3) Dialog conditions that involve == on anything but a quest stage are fragile The rider to this is: counter values can advance unpredictably. The corollary of this is that dialog conditions that use == on a quest stage are a good thing, but on counters that go up, probably bad, and on values that take on non-integral values, disastrous. How many mods have you played where you collected one flower too many and the quest become impossible to finish? Jobs of Skyrim, I'm looking at you! Things in the Dark has the same problem. Press the button that advances your slave ranking, and you'll skip past ranking 8, never hit the magic slave-ranking value in the dialog conditions and never be able to transition from Daithe to Hashep. Worse, you can't just setstage to start the Hashep quest-line because this quest violates rule (1) above. The solution to most of these counter bugs is to test >= instead of ==, then if something unexpected happens, your quest can still progress. And by always using a dialog condition with a stage check that asserts the correct stage, the game won't end up polluted with dialogs that should have gone away because they'd done their job. Counter values are particularly unreliable in Skyrim, due to threading, and slow Papyrus, and the player's ability to mess things around, you are asking for trouble when you expect counters to take on exact values. You never know whether you will see every counter value that is set, sometimes you might miss several. If you test for 20 petals, the odds of the player being able to get 22 before your check runs are decent. Just because you never see that case in testing doesn't mean it can't happen. But if the player has to go through a certain dialog to advance the quest, and that dialog only shows up for the exact stage that needs it, that's a good thing, stick to that pattern. 4) Use a coherent numbering scheme and keep stages ordered When quest stages are numbered 0, 10, 20, 50, 53, 54, 55, 67, 90, 100, and three of those stages are not even used, it's harder to work on that quest and keep it aligned with other quests and dialog conditions than if it used sensible numbering. It's even worse if a quest's stage numbers differ wildly from its objective numbers. In the cleanest case, objectives and stages have the same numbering, so you don't have to flip back and forth between stages and objectives to figure out what is being done. There is no reason not to start with quest stages rising in 100s. This allows you to use a pattern of steps of 5 for cases where you are doing simple dialog sequencing. Then, when you see a value other than a round hundred, you immediately know it's dialog sequencing. Of course, sometimes you need to add a stage to an established quest, but this really should be a last resort. Using steps of 100 makes this a lot less likely to run into a blockage though. In such cases, you should still strive to always keep objective numbers lined up with quest stages - there's no reason not to do that and not doing it can be enormously confusing. 5) NPCs can move - cope with it Things in the Dark, and Deadly Pleasures are frequently upset by an NPC being in the wrong place. NPCs walk off and start doing odd things, but the quest demands them to be in a certain room or near a certain marker. In some cases, TitD ports Frabbi out of the city, totally blocking your progression - it's not really what I'm talking about here though - the most obvious issue is NPCs that you have a talk objective with, who will not talk. That said, all kinds of misplaced NPC issue should be considered. Assumptions that NPCs will be where you expect are highly likely to be wrong - unless you have immediate (short term) measures to get them in place. If the PC needs to talk to a certain NPC, don't constrain that conversation to only taking place in a specific location. Constrain it by quest stage. That is enough. It will be easier to test, and work better for the player. Unless you have a really good reason, and proper mechanics around it, stopping the player from having a conversation just because they're in the wrong room is infuriating, and leads to player confusion again and again. They have no idea why they can't progress the quest, and why the dialog is missing. They have a quest objective to talk to NPC X, but NPC X has nothing to say! Avoid that pattern. It's a cause of woe. When dealing with mods like AI Overhaul, NPCs will walk off to all kinds of odd places. If an NPC must stay put, you need a special "priority 100" quest and AI package to lock them in place. Even that may not be enough. You may need a way for the PC to summon the NPC "in game". For example, if your quest requires Frabbi to be in the ruined dwemer pumping station, then port her there at the stage start. But if the player enters the station, check to see if Frabbi is there. If she is not there, port Frabbi outside the (only) exit of the pumping station, so when the frustrated player leaves, he bumps right into Frabbi and can have the conversation. Another thing that follows from this is quest markers - if the goal is an NPC, make sure the NPC is the target, not the location that they may not be at. In DP you can arrive at the quest location, but nine times out of ten, the NPC isn't there. As you don't know who you have to speak to, there's no easy workaround. 6) Scenes are your friend - don't make them an enemy Slaverun has a mechanism for using scenes that allowed Kenjoka to write massive amounts of story content without much in the way of bugs. In comparison, TitD has a small amount of story, and it fails to advance it correctly at almost every single step. Why did Kenjoka have no problems with this issue in Slaverun Reloaded, but Delzaron hits an iceberg on almost every single quest in Things in the Dark? It's not because one of them is simply a genius who keeps it all straight in his head, it's because Slaverun has a high-level system for triggering scenes, and that system is used over and over again to run the story content. This allows the story content to be tested simply by triggering those scenes at the high level. In contrast, every story segment in TitD and DP is a hand-crafted snowflake, built out of quest stages, gated dialog, forcegreets, and AI behaviors. Anything that uses Skyrim AI is inherently fragile. It's mostly fine when you have it working right, but only mostly, and it's hard to get right in the first place. AI sometimes fails for no obvious reason, and the CK mechanism for editing it is extremely fragile. Edit working AI, change nothing, and it suddenly stops and will never run again. At all. As for sharing AI ... it's hazardous. Scenes require no AI behaviors. All you have to do is ensure the participants are present, then start the scene. If the scene has the PC in it, you probably have to move the camera to a dummy and lock their controls while it's running. If you have a scene management system you only need to write that code once. Where scenes go wrong (and alas, Devious Followers is the archetype for this) is when they bind scripting into the scene stages, ad-hoc, possibly even advancing various quests as they go. This is terribly hard to maintain, because you have to know exactly which scene steps have script buried inside them. There are so many places you can hide a script away in a scene. For example, on the dialog lines instead of on the stage that fires them. There is too much freedom, and if you abuse it, you'll never untangle the mess that results. If you're going to run a script on a scene, stick to one script, run at the finish. At most two (one at the start, and one at the finish). You should need very special reasons to run scripting mid scene, and if you do, it shouldn't advance quests or make changes to internal state that advance quests, it should be purely to help the NPCs "act" during the scene. 7) SexLab can fail Don't assume that a SexLab scene will start and run to completion just because you try to start it. If your quest progression is controlled by an animation end handler for SexLab, one missing animation blocks your quest. This is a very simple mistake, and it's simple to fix. Never advance quests (directly) based on a SexLab animation end. If you want to run a pre-defined SexLab scene - create a high-level manager to ensure that shared logic for SexLab scenes is written once and tested many times, not different and unique for each use case. Design the high-level manager so it ensures all participants are present before trying to start the scene, starts the expected scene, and returns, or raises a mod event on animation end. If you know the animation failed to start, handle that immediately in the manager (spit out a Debug.Notify and act as if it ran). In the event of no animation end, use a timeout to catch it. No matter what goes wrong, when the manager determines the scene ended cleanly, or didn't start, or crashed, the caller can safely advance the quest. If there's a debug notification that the player can see in the event the scene doesn't run properly, all the better. If the manager crashes and takes down the control flow, you're still hosed. You can defend against this, but it adds complexity. A high-level scene manager that ensures participant presence solves a lot of problems. I'd rather have an NPC appear from nowhere for a scene, than the entire quest sequence block forever because they weren't in the right place at the magic moment. In most cases, a well written manager will not crash, even with missing animations - that should be a tested case. Deadly Pleasures requires the player to have a special "Patreon contributors only" animation pack. It says it works with the free version, but that's not true at all. Because it doesn't handle missing animations well, and because using the free animation pack leads to many missing animations, this results in broken quest progression on numerous occasions. I would suggest that this is not a good development pattern, unless you want players to start avoiding your mods due to a consistent expectation of nothing but broken quests. 8 ) Branching within Quests Skyrim quests, as designed by Bethesda, are not really intended to support branching within a quest. If you look at vanilla quests, they rarely introduce optional content. If they do it's usually a stage you can progress by either method (a), or method (b), or a stage with an optional step (c). Most quests progress with simple AND conditions. If you meet all the requirements, you go on to the next major stage. The flow of vanilla quests is typically sequential, (often) with no stages skipped, and stages only advancing. Some quests let you skip a stage here or there, or provide a basic OR in how you can progress. When they want to start a genuine branch, they usually launch a new quest to do it. The quest system is designed to support this model, and becomes awkward if used for other models. Quest stages can only get set to higher values. Stages have a concept of completion. Code is run on stage start, and on completion do nothing other than a SetStage(). Usually, new stages clean up the objectives of old ones. It's quite simplistic. While we have a function that is run on stage entry, there is no equivalent for stage exit (which is a serious flaw, and is probably why so many sloppily made quests don't clean-up objectives properly). There's an interesting bug/feature here ... if you SetStage to a LOWER stage number, the set will not change the stage, but the stage entry script will still execute! This can lead to modder confusion. You can definitely create more sophisticated logic, but Skyrim does nothing special to help you. Quests are designed to work the simple way I suggest above, and that's it. That is how the journal objective system is designed to work: do steps, in order, finish quest, success, get reward. There aren't many branches in Skyrim vanilla. There are some exclusive choices, but they're usually done by starting two quests and failing one of them when you finish the other. Civil War does this for Storm Cloaks vs Imperials vs Broker Peace. Using that model, it can launch a new quest any time, and have that quest shut-down competing branches. If you want to make vanilla-style quests, you should design like this. It's not a huge benefit, but it's a benefit. The alternative is that you have to write a quest system of your own in script. Misapprehension of this pattern leads to Delzaron coming unstuck sometimes, but most of his quests are linear and fit this model perfectly. Fish seems to "get" it, but still gets sloppy with objective clean-up. Sex Slaves permits branching, so it launches "competing" quests, but you can't complete them all successfully. While you can build multiple paths into a single quest, it's not really the intended pattern and it makes objective clean-up harder; there are ways to be rigorous about it, but Beth just don't bother; it's probably easier and cleaner to make a new quest. e.g. 10 Learn you must find the maguffin 20 Magguffin found - sets objectives "give maguffin to a Ulfric or Balgruuf" 30 Maguffin given to Balgruuf - start new quest Balgruuf's Maguffin 40 Maguffin given to Ulfric - start new quest Ulfric's Maguffin 50 Clean up objectives ... Completion vs 10 Learn you must find the maguffin 20 Magguffin found - sets objectives "give maguffin to a Ulfric or Balgruuf" 30 Maguffin given to Balgruuf - go to stage 100 40 Maguffin given to Ulfric - go to stage 200 100 Clean up common objectives from 10-40 110 Do stuff with Balguuf's maguffin... Go to stage 300 ... (various stages) 150 Complete Balgruff's maguffin quest-line 200 Clean up common objectives from 10-40 210 Do stuff with Ulfric's maguffin... ... (various stages) 260 Complete Ulfric's maguffin... Go to stage 300 300 Completion And if you want to unify clean-up ... 10 Learn you must find the maguffin 20 Magguffin found - sets objectives "give maguffin to a Ulfric or Balgruuf" 30 Maguffin given to Balgruuf - set Balgruuf flag - go to stage 50 40 Maguffin given to Ulfric - clear Balgruuf flag - go to stage 50 50 Clean up objectives from before (unified clean-up), branch on Balgruuf flag, go to stage if true 100 or 200 if false 100 Do stuff with Balguuf's maguffin... ... (various stages) 150 Complete Balgruff's maguffin quest-line... Clean up Balgruuf objectives ... Go to stage 300 200 Do stuff with Ulfric's maguffin... ... (various stages) 260 Complete Ulfric's maguffin... Clean up Ulfric objectives ... Go to stage 300 300 Clean up common objectives ... Completion See how much simpler it is if you don't do branches within the quest? Often, we'll see quest authors mush their stages together, performing multiple activities per stage to get around this. This muddy thinking often leads to them getting muddled about what a stage should do, or having poor stage planning and design. It also leads to "objective clean up in completion code" which then creates broken objectives when players SetStage from the console. Last Words A smart person learns from their mistakes. A really smart person learns from the mistakes of others. I know this seems like a hatchet job on poor Delzaron. I used his mods as an example, but he's not alone in doing this. I love what he's trying to do. I admire his persistence. His mods are consistently interesting, and raise all kinds of provoking possibilities. However, his scripting is a tail of tears, and I think it's worth examining why, so that others don't go down that same road and fall straight into the same potholes. There are other good/bad habits I didn't mention. Maybe you've come across some quest patterns you'd like to never see again?
- 9 comments
-
19
-
A lot of mods that have soft-dependencies have all kinds of nasty cross-mod issues that cause a CTD at game start, or simply break other mods that are involved in the mess. Often, the problems only manifest when three or more mods are involved. The way to avoid these problems completely is quite simple. The problem is created by a handful of bad patterns, and there is nothing mysterious or magical about it. There is a solid reason for why it happens, and why it can sometimes be quite complex to reproduce, or for a player to fix when setting up a new LO. In most cases, the cause of problems is a mod that has code to call some foreign mod in a quest script that auto-starts, and it doesn't check whether the ESP is present, it simply calls Game.GetFormFromFile and if it gets a None, it assumes the mod is missing. USLEEP does this FFS! It is the root cause of all that stupid spam it spews on every single game start. This pattern is toxic. Do not ever do this: Form foo = Game.GetFormFromFile(0x00123456, "Foreign Mod.esp") If foo != None TypeFromSomeOtherMod realFoo = foo As TypeFromSomeOtherMod ... do something with realFoo EndIf So what should you do? Follow the five rules to avoid ever referencing types that don't necessarily exist, and your mod should be much more stable with respect to soft-dependencies, even if you rely on a mod that is a confluence of trouble, such as SLIF. The Rules 1) Put all the mod interactions for a single foreign mod into a single global function. Do not (ever) mix mod interactions between different mods in the same file! 2) Do not interact with a type from a foreign mod in any function other than a global function. 3) Do not access a global function that performs an interaction with a foreign mod without first confirming that its ESP is present. 4) Do not access foreign mods during first start-up of the game. 5) Avoid access to foreign mods during OnLoad and equivalent events. You can see this pattern followed properly in Deviously Cursed Loot. SLD doesn't follow these patterns properly; there are several errors that need to be fixed. It seems to get away with it anyway, but it's better to be safe than sorry with Skyrim. But why? Quite simply, if you reference a type from a script file that doesn't exist, Skyrim doesn't know how to handle it. The type is garbage. It will either CTD, or - if you are lucky - the type will be incompatible with all objects and never take a value apart from None. To be sure we don't reference types that don't exist, we must make sure those types are not present anywhere in a script we might execute when that type's defining script doesn't exist. And if that defining type is bound to an ESP, it will also need that ESP, so we should check that exists first. The type only properly exists when its script files are present, and the best rule we have for determining the presence of the script files, is the presence of the ESP. This is important for other reasons too, because we need the ESP to populate pre-defined properties on foreign types. Clearly both ESP and script need to exist, but checking both creates an absurdly tight binding, and is busywork that requires special Papyrus extensions anyway. It's good enough to check for the ESP. It's not good enough to skip that check, because an object script without its ESP may be parsed but will still break your game because its an orphan: a class that has no possible instances. Only global scripts can operate properly without their parent ESP or ESM, because a global script cannot be instanced. By putting the interactions in a global function in a script that contains only global functions, we can be sure that our mod will never try to reference those types unless we explicitly call the global. The same is not the case for a script attached to a quest. If you attach a script to a quest, its papyrus instance will be created when the quest is started. If the quest is auto-start, that will be on first load of the game. So, if you reference a foreign type - even if due to control flow, you do not execute that reference - it is still processed by Skyrim when the potential calling script is loaded. Skyrim must know the type's signature, and to do that it instantiates the type, and to do that, requires an ESP. Using global scripts stops this, because they are not pre-instantiated, and they are only instantiated when executed, and executed when explicitly called. But we must be sure not to call those globals at sensitive times, of if the scripts (and types) they rely on do not exist, or cannot be instantiated. Thus, we check the ESP of the foreign mod exists before trying to call the globals, and we only call the globals from "safe" functions that will not run on first game startup. I'm simplifying a bit. It's really only an issue with scripts that are bound to objects, but those are the most common soft-dependency. Any other kind of script would be a global, and would have no persistent internal state and no properties. For such scripts the mere existence of the script object is enough. If it's missing, your game will not explode. Though the code that tries to call it won't work and will crash, merely having a reference to it in your own quest won't cause an issue at all, beyond the crashing of your script that is. Why must we avoid first game startup? This is important because the foreign types we want to reference may not even be loaded, even if the ESP exists. Skyrim loads the types when the ESP loads, and normally that happens in strict load order. But if we reference a type in our script, it will cause the script that defines that type to be loaded before the ESP that instances it. This is not well supported in Skyrim. Either way, we don't know the load order, and whether the other mods have loaded yet. This is why changing load order can sometimes fix issues. If your mod is not following the pattern cleanly, but loads really late, it might get away with it anyway. OnLoad is an edge case. OnLoad doesn't run on first load (strange, I know, but I believe this is on purpose so OnLoad can rely on initialization). So, mods that reference foreign mods in their OnLoad should be safe? They are. Mostly. In some cases, a player adds a mod during play. That mod may then get picked up by a "naughty" soft-depending mod that checks for soft-deps OnLoad, but the mod just added by the player might not have properly loaded. This is why checking in OnLoad is hazardous. If you don't add mods mid-play, it will be fine. And if you do, you may get away with it if the mod gets properly loaded before the OnLoad for the depending mod runs. SLD seems to get away with it in practice, but you shouldn't push your luck; I see the SLD behavior as a bug that needs to be fixed. Practical Examples So, where should we check for foreign mods? I believe the correct answer is in an OnUpdate handler. When OnLoad runs, it should set an int "checkSoftDeps" variable to 1. It should also set the presence of ALL soft-dep mods to false - keep a boolean flag for each one. When OnUpdate runs, it has a branch that runs if checkSoftDeps > 0 Inside that branch it increments checkSoftDeps If checkSoftDeps > 2, then run the handling code to check for soft deps, then reset checkSoftDeps to 0. This makes it impossible to run the checks prematurely, but only requires one variable. Inside the soft-dep check handler, we need to see if the ESPs are present. If the ESP index is not 255, then it is, and we can set the presence flag to true. e.g. In OnLoad: checkSoftDeps = 1 slifPresent = False aproposPresent = False In OnUpdate: If checkSoftDeps > 0 checkSoftDeps += 1 If checkSoftDeps > 2 slifPresent = 255 != Game.GetModByName("Sexlab Inflation Framework.esp") aproposPresent = 255 != Game.GetModByName("Apropos2.esp") checkSoftDeps = 0 EndIf EndIf In code that uses apropos: If aproposPresent && 255 != Game.GetModByName("Apropos2.esp") Actor aproposActor = MyModAproposShim.GetAproposActor() ... EndIf In the Global function file MyModAproposShim: Actor Function GetAproposActor() Global Apropos2Actors actorLibrary = Game.GetFormFromFile(0x0002902C, "Apropos2.esp") .... Return foundActor EndFunction Note how at the point of use, the mod ESP is checked again, even though we have the boolean? That's because the boolean may not get reset fast enough via OnLoad, but it saves us checking GetModByName over and over if the mod is never present. Note how only the global function, safe in its own file contains an actual TYPE from Apropos2: Apropos2Actors. When I wrote SLD, I was under the misapprehension that simply not using a type was enough. That is wrong. If the type is not loaded, and you load a script that references it, you can break the game. Not necessarily though, and that is where the complex results that Monoman wrote about originate. See my comments in the spoiler on first and second consumers. This topic has actually been written about by engine experts, but the posts are hard to find. Essentially, if you reference the type, and the mod isn't loaded, you will cause the type to be loaded if it can be. If that goes ok, then your mod may not cause a problem, but you may have now caused a type to load outside of the normal order of load ordering ... consequences of this can result in the type itself breaking, but if it ends up trying to load references to ANOTHER type that is itself later in the LO, and that's where things can go bad. If you start this chain, and any type is missing, Skyrim fails to instantiate the type, and code - in some other mod - may then try to use it anyway, and CTD. Thus if you follow the pattern, you stop other mods that aren't well-behaved, from breaking the game. Some things to think about...
- 19 comments
-
16
-
- papyrus
- modding help
-
(and 1 more)
Tagged with:
-
C.A.N.S. API and How to Use It in Patches or Mods
Feliks posted a blog entry in The Jack of All Trades
Welcome ladies and gentleman once again to the CANS API blog post. With the latest build (0.18) there are some massive changes. In general implementation from the old post will still work but quite a bit of it is irrelevant and no longer necessary. So to save you some time, the game some memory, and better explain things from the last one, this has been rewritten. I'm going to break this up into sections for everyone's convenience. 1. How Does the C.A.N.S. Framework Actually Work? Now you see, I'm actually really proud of that, but it's also very complicated, so unless you're legitimately interested probably don't open this spoiler. 2. What are the Limitations of C.A.N.S.? tl;dr: Under normal circumstances you won't ever have to deal with them; and CANS does not contribute significantly to lag 3. How do I Make a Mod or Patch CANS Compatible? Now this is the bread and butter of this post, why most of you are here, and hopefully something that brings us into a new era of inflation or pregnancy based mods. Full API and explanation within the spoiler Note: It is assumed you have a basic understanding of Papyrus and the Creation Kit 4. Advanced Features/Tips & Tricks Here are a few neat features advanced modders may want to take advantage of. There you have it folks, the updated for 1.0 C.A.N.S. Framework API/Implementation -
This is a little snippet of code, that I've written in C and C++ before. This time it's for papyrus. It's a ticket-based spinlock. For those with a comp-sci background, known as a ticket lock (Communicating Sequential Processes - C.A.R.Hoare, Chapter 6.6 Scheduling). So what does it offer: it's a spinlock (i.e. a form of mutex) it's fair (the thread that waits the longest for the lock is the one that will acquire it next). It provides 3 functions: Function lock() locks the resource (blocking) Function unlock() unlocks the resource (non-blocking) bool Function trylock() If the lock is available, will acquire the lock and return true; otherwise, it will fail and return false. I.e. returns true if the lock attempt succeeds, false otherwise. Feel free to include it in your mod. ;/Copyright (c) 2014, irquihAll rights reserved.Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE./;;;;;; Ticket spinlock;; NOTE: due to how papyrus works, these functions may only call methods in our script; NOTE: due to the script being locked (as long as no external functions are called); these functions can be treated as if they were atomics.; DO NOT REFACTOR TO EXTERNAL FUNCTIONS!int property spl_wrap_around_ = 0x7fffffff autoreadonlyint spl_ticket_ = 0int spl_cur_ = 0float property spl_wait_seconds_ = 0.05 autoreadonlyint spl_n_waiting = 0Function lock() debug.trace("DDRestrainedConfigQuest: Acquiring lock...") ; Acquire ticket int ticket = spl_ticket_ ; Update ticket if ticket == spl_wrap_around_ spl_ticket_ = 0 else spl_ticket_ += 1 endif ; Wait until spl_cur_ reaches our ticket spl_n_waiting += 1 int spl_spincount = 0 while spl_cur_ != ticket if spl_spincount == 100 || (spl_spincount != 0 && spl_spincount % 1000 == 0) debug.TraceStack("spl_spincount = " + spl_spincount + ", maybe deadlock? Currently " + spl_n_waiting + " blocked threads.", 1) ; Warn about slow lock access endif spl_spincount += 1 utility.WaitMenuMode(spl_wait_seconds_) endwhile debug.trace("DDRestrainedConfigQuest: Lock acquired after " + spl_spincount + " spins.") spl_n_waiting -= 1EndFunctionFunction unlock() if spl_cur_ == spl_wrap_around_ spl_cur_ = 0 else spl_cur_ += 1 endif debug.trace("DDRestrainedConfigQuest: Releasing lock...")EndFunctionbool Function trylock() if spl_cur_ != spl_ticket_ debug.trace("DDRestrainedConfigQuest: Trylock failed to acquire lock...") return false endif if spl_ticket_ == spl_wrap_around_ spl_ticket_ = 0 else spl_ticket_ += 1 endif debug.trace("DDRestrainedConfigQuest: Trylock successfully acquired lock...") return trueEndFunction
-
It's often the case that you need to handle a "sensitive" event in some mod code. These events are things like OnHit, OnAnimationEvent, and OnItemAdded. What these events have in common is that in some cases they can be produced in large busts. An event handler that takes too long handling those events can result in an overloaded script system that overflows its stack and gets suspended, with unpleasant results for your game. Another kind of event that exhibits this problem is one generated by a cloak, and it follows that trigger box events can also cause similar overloads. In all cases, the problem is the same, efficient handling of the events. While some will say "don't use cloaks ever", that's a gross over-statement that takes a dogmatic approach to cloaks. Problems have arisen because the pattern shown in the CK cloak tutorial is quite bad, and adding scripts with cloaks is generally a bad idea, but cloaks in general are no worse than any other event that can fire rapidly in bursts - probably less bad than an OnItemAdded running on the player. The central problem with script in these handlers is the basic problem of taking too long to execute. This is caused simply by doing too much in the handler script. Scripts that go off and potentially do heavy work are almost certain to cause an issue unless additional measures are taken. A favorite pattern is the "busy state", which uses Papyrus built-in states to avoid overload. In this pattern, after a successful call to the event handler, additional calls to the handler will do nothing unless the first call has completed. The code looks something like this: This approach is fine is you just want to know the event is being hit by something. It's good for situations like an OnHit event where you want to know the player is being hit in combat, but you don't need to handle every hit, just as many as you can - and the player won't really notice those hits you didn't process. But, quite frequently, the reason you're running an event handler is because you need to get all the events. You really don't want to miss any. This is most likely with an OnItemAdd, where you're looking for a particular item. An appropriate filter is the recommended solution to OnItemAdd performance risks, and that's great if you know exactly what items you are interested in, and the list is small. If the list is huge, or you don't know the items but simply that they have a certain keyword, or came from a certain mod, then filtering is probably unsuitable. Nevertheless, where you can use a filter, that should be your first approach, as filters are very good at reducing the number of event calls, so you don't have to run a lot of code on irrelevant items at all. But let's suppose a filter is not going to cut it. Imagine you are doing what I wanted to do recently, which is to detect every DD key the player obtains. There are now many mods that create their own uniquely flavored DD keys, and so it's not enough just to check for the default DD keys any longer - if it ever was. I could do this... There are no states needed in this case. It's not ... terrible ... the work done in the handler is strictly bounded, and once the _DFKeyFound global gets set, further calls early-out and do nothing. Though, of course, a massive inventory flood coming from emptying Sanguine's chest in SD+, or the prison confiscation chest, or similar, probably wouldn't ever set that value, and the script would run a lot of times. Possibly a couple of hundred times in a single burst - but it's not such a huge script it would bring down your game - it would just take a while to clear. But ... if you then piled more load on top, it might become an issue. So, the above code would probably be ok, but it's still a little worrying. It could be better. We could do less. What is the minimum we could do? One approach could be this: FormListAdd is for all intents instantaneous compared to any Papyrus execution; the cost is entirely dominated by the Papyrus overhead. Somewhere else there is a regular service routine that pulls everything out of the lists and deals with it. How that's done properly is a topic of its own, and I don't want to get stuck in that, so let's just pretend it's easy to iterate over each list and process the items. One list is mostly going to be full of "None". This is the least we could reasonably do in a handler, and it won't miss any items. The down side of this is that we absolutely must service those lists reliably or they will grow without bound, and probably cause a CTD. That is also probably an unacceptable risk. What if the Papyrus servicing the lists fails? Here's the compromise I came up with for DF: In this case, if the list is overflowing, we back off and stop adding - items will be lost - but this is better than creating a massively bloated list that is going to lag in processing anyway. In fact, with a limit of 1000, if we are skipping filling the list, it's almost certainly because the servicing routine has stopped running and is dead. Also, I took a little Papyrus time to determine whether to handle as item or base. This is a less clear-cut whether it's the right approach, but it saves time later in processing the list later, that is for sure. The alternative is to use the approach above and fill two lists (or the same list) with both inputs and discard all the None items later. Another approach is to discard non-uniques, which will filter out all the None items at the point of add - but those calls to StorageUtil are going to start using a bit more CPU if you do that. What's best? You'd need to profile in detail to know. I couldn't be bothered going that far this time. So, there is no straight answer to any problem, and no perfect solution. But sometimes the "busy" state pattern is not going to do what you want, and you need something else. Busy state invariably discards a lot of calls when you get a burst. It maintains performance, but the cost is lots of lost events. States can result in some hierarchic resolution of the state declarations, so they are more costly than they look, but I suspect it's still negligible. The problem is really losing all those calls that you made the handler to catch in the first place. Without detailed profile results it's hard to say exactly what would be the absolute best approach here; but profiling this sort of scenario in a meaningful way is not trivial. I can easily imagine some garbage profile results, but a useful measure might be how often we get a suspended stack.
- 6 comments
-
5
-
Hey, I'm working on a mod with some similar functionality to Combat Strip Lite and I'm trying to unequip an armor piece and prevent it from being re-equipped. Based on Creation Kit it should be as simple as PlayerRef.UnequipItem(*Item*, True) but this hasn't been working for me. After unequipping the item, it can be re-equipped from the pipboy with no issues. Here's the script I have: Function StripPlayer() VM_Debug("Stripping Player", Module) Int[] ArmoredSlots = GetFilledSlots(VM_Armor) Int[] ClothedSlots = GetFilledSlots(VM_Clothing) If ArmoredSlots.Length > 0 Int Index = Utility.RandomInt(0,ArmoredSlots.Length - 1) Int Slot = ArmoredSlots[Index] Form Item = GetArmor(Slot) If Item != none && !DD_List.HasForm(Item) StrippedItem NewStrippedItem = New StrippedItem NewStrippedItem.ItemRef = Item NewStrippedItem.Slot = Slot StrippedItems.Insert(NewStrippedItem,0) PlayerRef.UnequipItem(Item, true, true) EndIf ElseIf ClothedSlots.Length > 0 Int Index = Utility.RandomInt(0,ClothedSlots.Length - 1) Int Slot = ClothedSlots[Index] Form Item = GetArmor(Slot) If Item != none && !DD_List.HasForm(Item) StrippedItem NewStrippedItem = New StrippedItem NewStrippedItem.ItemRef = Item NewStrippedItem.Slot = Slot StrippedItems.Insert(NewStrippedItem,0) PlayerRef.UnequipItem(Item, true, true) EndIf Else VM_Debug("Nothing To Strip",Module) EndIf StartTimer(StripRegenTimer,4) EndFunction Any ideas on what I'm doing wrong here that may be interrupting the prevention? Any help is greatly appreciated! Thanks!
-
Hi, I have problems understanding the new changes from equipDevice( ) to lockDevice( ) in Devious Devices 5.1. I am working on a little mod which should equip a certain Devious Device. I started this mod with DD 4.x and the core functionality worked fine with equipDevice( ). Now I switched from Skyrim to SSE and ported my mod. Many Devices still work and some not at all. So since I upgraded DD one major version I checked the code and found that equipDevice( ) is now deprecated and the code is replaced: Function EquipDevice(actor akActor, armor deviceInventory, armor deviceRendered, keyword zad_DeviousDevice, bool skipEvents=false, bool skipMutex=false) LockDevice(akActor, deviceInventory) EndFunction Now I understand partly whats happening here. The code is replaced and the LockDevice( ) function is called with less parameters. But how do I pass my keywords now? There are certain devices I really dont like and would like to avoid, but if I only call a certain device like "zlibs.zad_DeviousPlugVaginal" for "deviceInventory" it would return all Devices of that kind, correct? I would be really thankful if someone could point me to a good tutorial or more in depth info.
-
Hi all, once more I am in need of your collective intelligence. Haven't played in a few days and when I tried loading my most recent saves I encountered CTDs. I only added one mod before starting skyrim, so removed it tried again... CTD. Remembered the LE trick of loading older saves and loading the newest save from ingame, the older save loaded just fine but the recent ones result in a CTD again. Quick research let me believe it has to do with the 65k string limit, but it seems this is no longer a problem in SE. However when comparing one of the recent broken saves and the last working one (not more than 1 hour of playtime between those two) the line count of the file increased from 6.467 to 186.325 (which even to my amateur eyes does not look fine) and when trying to open it with ReSaver I get "Error while reading the Papyrus section". Broken save: Autosave2_1736BDFA_0_41727961_RiftenWorld_001937_20210810224159_19_1.ess Working save: Autosave1_1736BDFA_0_41727961_TwilightSepulcher02_001808_20210810205926_18_1.skse Does anybody know what happened here? Is it fixable, is there some way to avoid this in the future?
- 3 replies
-
- ctd
- 65k string limit
-
(and 6 more)
Tagged with:
-
I'm hoping to make a mod adding custom expressions and I've tested them in game by replacing a few included in racemenu. When it comes to making them appear papyrus / scripting wise I'm stuck. I know about SetExpressionOverride and MFG console's SetPhenome but the documentation on them both doesn't include anything on creating new moods / mouths. I'm looking for two solutions to help out. A. Manipulating racemenu sliders in game using papyrus. (I'd have to follow a tutorial to add these sliders which'll have my expressions) B. Adding a new mood that I can use with SetExpressionOverride or SetPhenome I'm experienced in Papyrus and the Creation Kit but this feels out of my reach. Any help is appreciated!
-
So, I´ve been trying to change an actors face color with nioverride. To do this, I´ve come up with this snippet NiOverride.AddNodeOverrideString(target, fem, partNameHEad, 9, 0, textureHeadCurrent, false) NiOverride.AddNodeOverrideint(target, fem, partNameHEad, 7, -1, 0x3c0b00, false) partNameHead is the headnode name aquired by using this function: string Function GetHeadNode(Actor aAct) ActorBase ab = aAct.GetActorBase() int i = ab.GetNumHeadParts() string headNode While i > 0 i -= 1 headNode = ab.GetNthHeadPart(i).GetName() If StringUtil.Find(headNode, "Head") >= 0 return headNode EndIf endWhile return "" EndFunction But now I have the following problem: While this piece of code works on the player and the majority of NPC´s, there are a few select npcs where it just wont work on. These for example Or this one I cant apply overrides, nor can I get their actual textures by using "NiOverride.GetNodePropertyString(act, false, partNameHead, 9, 0)" Any Idea on what I might be overlooking?
- 4 replies
-
- nioverride
- face
- (and 4 more)
-
I can't for the life of me seem to get this right. I've tried a dozen variations, over a few months of frustration, and it just is never working. I've taken an old open-permissions basic follower mod (Mango) and started adding stuff to it, including new XVASynth dialogue. I've started with a simple "find my lost gear" micro-quest (right in the same dungeon). The quest starts. The marker works. Finding the gear updates the quest (only the cuirass is required from the chest the stuff is found in). The dialogue when you return to the quest-giver is there, but then when you trigger it to give the gear, nothing happens and the quest is stuck forever. What's supposed to happen is when you tell her you got her gear for her, it takes the quest-flagged cuirass from you and equips the non-quest-flagged variant on the NPC. Sounds dirt simple. Here's the mod in its presently broken alpha state. Can anyone please help fix this? I think if I get this working and see why it works and my older attempts failed, I'll be able to proceed with the rest of the "real" quests I have in mind without any further serious problems. https://mega.nz/file...7NDp5ZtWsJneLlA NSFW: The follower NPC doesn't have an outfit on when you find her, so if your game has nude textures instead of vanilla underwear textures, she'll show up nude. For expedient testing: Clear out Embershard mine, and save. Install the alpha mod, and go back into Embershard. The follower NPC, Mango, is chained to the wall across from the locked bandit armory room in the middle of the dungeon. The dialogue is very simple and self-explanatory. The Heirloom Nordic Cuirass to find is in the chest on the table near the bandits' forge and armoring table in the cavern a bit further inside the dungeon.
-
I have tried to wrap my head around making a papyrus script to move a actor (player or NPC) behind a chair when they exit it so not as to get stuck against a table that is in front of it. I've tried GetPosition, GetAngle, SetPosition & SetAngle but can't seem to grasp how to grab the enter / exit event. I thought to track GetSitting of the ActorReference to then SetPosition when that state is registered as "standing", but nothing I have poorly conjured has compiled. Scriptname ExitChairBehind extends ObjectReference Event OnActivate(ObjectReference akActionRef) ;Get the Actor who activated Actor akActivator = akActionRef.GetActorReference() ;Get Rotation of the furniture Float fObjectAngle = GetAngle() ;Get Current XYZ pos of Furniture Float fObjectPos = GetPosition() ;Get Activated Actor Sitting State ActorID.GetSitting() endEvent Event OnActivate(Objectreference akActionRef) If GetSitting = 0 AkActor SetPosition(-1, -1, -1) ;SetPosition should be relative to the furniture position, and opposite its current Z rotation to X or Y Position. EndEvent This isn't a completed script as I've tried to scrub out stuff that was just not correct. I realize this is very complicated for someone who isn't good at coding. So any help would be appreciated, or if someone can write this, even better. I am frustrated at this point.
-
Hello, I'm having some trouble with a mod I'm creating. The issue is Quest/Array/Property related but here's some background first: I'm trying to create a system for handling approaches (similar to AAF Sexual Harassment) but I'm trying to implement a system that uses Quests to handle approach scenarios. Effectively the system would process like this: Approach is initiated - This could be a combat surrender, friendly NPC approach, etc. Approachers are identified - Actors are identified based on the approach type (Combatants/Lovers/Citizens/etc.) *Random Quest is selected - Based on the actor selection and the approach type, a quest would be randomly selected from an array. Quest is run - The quest runs with any dialog/scripts/scenes/etc that is coded into it. So my problem is with the array of quests in step 3. The array doesn't seem to be functioning as I'd expect it. In my papyrus I have 4 properties that align with the different approach scenarios that will be available: Group ApproachQuests Quest[] Property VM_CitizenApproaches auto Quest[] Property VM_GuardApproaches auto Quest[] Property VM_CombatApproaches auto Quest[] Property VM_LoverApporaches auto EndGroup These properties are filled with pre-constructed quests (I only have one right now) in Creation Kit: The papyrus script then loops through the list of Quests based on the type set when the approach was initiated (VM_ApproachType): Function StartQuest() VM_Debug("Starting Quest Scene") If VM_ApproachType == "Lover" ElseIf VM_ApproachType == "Citizen" ; Get Quest Int QuestIndex = Utility.RandomInt(0, VM_CitizenApproaches.Length-1) VM_Debug("Quest Index: " + QuestIndex) Quest ActiveQuest = VM_CitizenApproaches[QuestIndex] If !ActiveQuest.IsRunning() ActiveQuest.Start() EndIf ActiveQuest.Reset() ActiveQuest.SetStage(10) ElseIf VM_ApproachType == "Guard" ElseIf VM_ApproachType == "Combat" EndIf EndFunction When trying to debug the approach it gives this response: error: Cannot access an element of a None array stack: [VM_Main (1D004C50)].VM:VM_Approaches.StartQuest() - "<unknown file>" Line ? [VM_Main (1D004C50)].VM:VM_Approaches.StartApproach() - "<unknown file>" Line ? [VM_Main (1D004C50)].vm:vm_scenes.DebugApproachStart() - "<unknown file>" Line ? [VM_Main (1D004C50)].vm:vm_scenes.OnKeyUp() - "<unknown file>" Line ? Based on this response, it leads me to believe it's an issue with the array being filled. as the Quest Index is returning -1. I have no clue what I'm doing wrong here so any insight on this would be greatly appreciated! Also, let me know if more information would be useful and I can provide it. Thanks!
-
More of a general directions asking post. I recently learnt how to make dialogue lines, and I do know a thing or 2 about scripting (nothing pro, but I manage) So I was wondering if maybe there was a way to integrate some sexlab stuff into mods. Idea was just some immersive dialogue lines that called for scenes, and that said scenes were properly tagged. Thing is, for that, I need to know the code lines to call for a sexlab scene and how to call for appropriate tags. It's dialogue line, end dialogue fragment script, insert code, but what? Trying to look for documentation but I'm still a little lost, also opened a few scripts from mods that could help, like SLEN, but I'm still little bit lost here. Anyone kind enough to point me in the right direction?