ScrollSerrayt Posted September 4, 2020 Posted September 4, 2020 Hi, I'm making my own mod, and my goal involves having compatibility features for a lot of Sexlab mods. But when I select the plugins I want to work with and make changes, all those plugins become a master mod and I don't want all those mods to be required. Can someone point me to a tutorial or give me a detailed explanation how I can make my mod have optional-dependencies for certain features of my mod? Help would be appreciated.
Guest Posted September 4, 2020 Posted September 4, 2020 You create separate plugins for records that need to reference one or more of those dependencies. For scripts, you can encapsulate them inside mutually exclusive functions: This Works: Quest MyMod Event OnInit() if Game.GetModByName("MyModPlugin.esp") != 255 MyMod = Game.GetFormFromFile(0x000D62, "MyModPlugin.esp") as Quest else MyMod = none endif EndEvent Function DoSomethingDefault() EndFunction Function DoSomethingMyMod() (MyMod as MyModScript).DoSomething() EndFunction Function DoSomething() if MyMod == none DoSomethingDefault() else DoSomethingMyMod() endif EndFunction This Doesn't Work (function DoSomething will always fail if you don't have MyMod, even if the cast is never actually executed): Quest MyMod Event OnInit() if Game.GetModByName("MyModPlugin.esp") != 255 MyMod = Game.GetFormFromFile(0x000D62, "MyModPlugin.esp") as Quest else MyMod = none endif EndEvent Function DoSomething() if MyMod == none else (MyMod as MyModScript).DoSomething() endif EndFunction
ScrollSerrayt Posted September 4, 2020 Author Posted September 4, 2020 2 hours ago, Hawk9969 said: You create separate plugins for records that need to reference one or more of those dependencies. For scripts, you can encapsulate them inside mutually exclusive functions: This Works: Quest MyMod Event OnInit() if Game.GetModByName("MyModPlugin.esp") != 255 MyMod = Game.GetFormFromFile(0x000D62, "MyModPlugin.esp") as Quest else MyMod = none endif EndEvent Function DoSomethingDefault() EndFunction Function DoSomethingMyMod() (MyMod as MyModScript).DoSomething() EndFunction Function DoSomething() if MyMod == none DoSomethingDefault() else DoSomethingMyMod() endif EndFunction This Doesn't Work (function DoSomething will always fail if you don't have MyMod, even if the cast is never actually executed): Quest MyMod Event OnInit() if Game.GetModByName("MyModPlugin.esp") != 255 MyMod = Game.GetFormFromFile(0x000D62, "MyModPlugin.esp") as Quest else MyMod = none endif EndEvent Function DoSomething() if MyMod == none else (MyMod as MyModScript).DoSomething() endif EndFunction The GetModByName function you mentioned in the scripts seem helpful for my goal. Thanks for showing examples too. But i'm still confused, because even if I could tell certain code to not run if a mod is not detected, I don't understand how I could make that code without accidentally making my mod a master plugin. Because as soon as I tried and make a quest using a global value and a id reference to another mod that I had loaded, it instantly became a master plugin. How can I reference values and properties of another mod without making it a master? Is there a tool to remove unnecessary masters? You briefly mentioned plugins, but I have a question about those. For important dependencies where I tweak quests and scripts from another mod, would the best method be to create a separate patch plugin that uses my base mod and the other mod I'm working with, and create a FOMOD that auto detects what mods the player already has installed? I've seen some mods I've installed do this, but I've never done that myself. Thanks for giving the time to help me out.
Monoman1 Posted September 4, 2020 Posted September 4, 2020 2 hours ago, Hawk9969 said: This Works: While this works it can cause some serious nastiness (Mods not starting, scripts being dumped, ctds) under some fairly unique circumstances. Specifically it is the casting to a potentially unknown/not installed type that causes the problem. In your example it's 'MyMod as MyModScript'. Not all mod users will have all your soft dependencies installed. That's the very nature of them. Have a read of here: https://www.loverslab.com/topic/106848-multiple-mods-with-soft-dependency-to-the-same-resource/ I'd only bother reading the first post and from post 11 and down. In between is me learning. Lupine (whom I learned the best method from) also has a blog post here https://www.loverslab.com/blogs/entry/10593-soft-dependencies-without-pain/ SL Survival is choc full of soft dependencies if you want examples but it's a fairly big mod to be delving into for newcomers.
Guest Posted September 4, 2020 Posted September 4, 2020 11 hours ago, ScrollSerrayt said: The GetModByName function you mentioned in the scripts seem helpful for my goal. Thanks for showing examples too. But i'm still confused, because even if I could tell certain code to not run if a mod is not detected, I don't understand how I could make that code without accidentally making my mod a master plugin. Because as soon as I tried and make a quest using a global value and a id reference to another mod that I had loaded, it instantly became a master plugin. How can I reference values and properties of another mod without making it a master? Is there a tool to remove unnecessary masters? I don't know about CK since I don't use that slow bugfest myself, but you can remove unused masters via xEdit by right clicking the plugin and selecting "Clean Masters". If you need to add references to your plugin that are meant to be soft-dependencies, you can create a new plugin with the base plugin as master and overwrite any necessary records from the base plugin. 11 hours ago, Monoman1 said: While this works it can cause some serious nastiness (Mods not starting, scripts being dumped, ctds) under some fairly unique circumstances. Specifically it is the casting to a potentially unknown/not installed type that causes the problem. In your example it's 'MyMod as MyModScript'. Not all mod users will have all your soft dependencies installed. That's the very nature of them. Have a read of here: https://www.loverslab.com/topic/106848-multiple-mods-with-soft-dependency-to-the-same-resource/ I'd only bother reading the first post and from post 11 and down. In between is me learning. Lupine (whom I learned the best method from) also has a blog post here https://www.loverslab.com/blogs/entry/10593-soft-dependencies-without-pain/ SL Survival is choc full of soft dependencies if you want examples but it's a fairly big mod to be delving into for newcomers. This is why I clearly said MUTUALLY EXCLUSIVE functions. The PapyrusVM will push them into the stack by function calls; as long as your function with a soft-dependency is never called if that soft-dependency is not loaded, you won't have any problems. And yes, events are functions under the hood (somehow equivalent to Pascal/Delphi's procedure and C/C++'s void function). Look at both functions in my example; the first will never call a soft-dependency function if you don't have that plugin loaded, while the second (the one that fails) won't call it, but it's referenced inside a function that does. Your example with states is just the same thing under the hood, you've mutually exclusive functions. The limitation with states however, is that you can only have one, if you need multiple soft-dependencies within a single script and each soft-dependency runs somewhere else, the obvious solution is to use mutually exclusive functions that only get called if the soft-dependency is present. By the way, global or not, they will still fail the same way if you are not using mutually exclusive calls. No point in using globals for this. The Papyrus compiler will NEVER inline functions, it's completely safe to encapsulate soft-dependencies within functions that are meant to be called only with that soft-dependency loaded. You are also completely wrong in saying that it will lead to CTDs. If the PapyrusVM fails to resolve a dependency, it will simply not call the function with said dependency; this is a HANDLED exception that even gets logged into the log file, CTDs are unhandled exceptions or partially handled exceptions that the program doesn't know how to/can't proceed (like the ones where you've an error box before the program's shutdown). Script dumps on this error is also false and the mod can certainly fail to start if it relied on the failed function to properly initialize. And yes, I've gone through this intensively (even under the hood with a debugger attached). My first mod in here, Manual Strip, had this issue and I'd contacted @Erstam back then because I was writing the SAM actor update code based on his but at the time we couldn't figure out why his worked fine while mine didn't. When I got back to Skyrim after getting a new GPU, I went ahead and properly looked into this and figured out the mutually exclusive functions trick by myself (and the cause of the problem), although Manual Strip doesn't use it (having a separate compiled script for SAM users) simply because I didn't bother updating it again just for that.
Monoman1 Posted September 5, 2020 Posted September 5, 2020 15 hours ago, Hawk9969 said: This is why I clearly said MUTUALLY EXCLUSIVE functions. Doesn't matter. That's my point. But don't take my word for it. Try it yourself. Here again. Two mods trying to simply cast as an external type. I'm not even calling any external functions or variables. Just casting. The test mod functions themselves are never called and additionally their quests are not even flagged as 'start game enabled' Master Mod: Scriptname _Master_TestScript extends Quest Function MasterFunction() EndFunction Test Mod A: Scriptname _TMA_TestScript extends Quest Function NeverCalledFunctionInANeverStartedQuest() (Game.GetFormFromFile(0x000D62, "MasterMod.esp") as _Master_TestScript) EndFunction Test Mod B: Scriptname _TMB_TestScript extends Quest Function NeverCalledFunctionInANeverStartedQuest() (Game.GetFormFromFile(0x000D62, "MasterMod.esp") as _Master_TestScript) EndFunction Results (search papyrus log for '_TM': Master mods script is present and installed (Soft dependency is installed). Search result: Everything is just peachy. No errors. No problems. Master mod script is not installed (soft dependency is missing). Search result: Line 50: [09/05/2020 - 10:58:55AM] Error: Unable to link types associated with function "NeverCalledFunctionInANeverStartedQuest" in state "" on object "_TMB_TestScript". Line 51: [09/05/2020 - 10:58:55AM] Error: Unable to bind script _TMB_TestScript to _TMB_TestQuest (7B000D62) because their base types do not match Line 51: [09/05/2020 - 10:58:55AM] Error: Unable to bind script _TMB_TestScript to _TMB_TestQuest (7B000D62) because their base types do not match Test Mod B has crapped out and has its script dumped. IIRC any connected scripts are also dumped in a cascading fashion leading to (usually) catastrophic failure of that mod. Test Mod A is fine. But has caused Test Mod B to crap out indirectly. This is depending on load order I believe. If Mod A was loaded after Mod B then Mod A would have crapped out instead.
Guest Posted September 5, 2020 Posted September 5, 2020 10 hours ago, Monoman1 said: ... This seems like the state evaluation code reading the content of all functions defined in the default state for whatever reason. I know there is working soft-dependency codes in the default state, even without mutually exclusive functions (Cumshot's CS2GameScript.psc, line 386), which prompts me to believe this evaluation code only reads part of the function/script's content. Have you tried avoiding the default state? No globals, no specific states for soft-dependencies, just declarations in the default state and definitions at other states. Scriptname whatever extends Quest ; Declarations Quest MyModA Quest MyModB Function DoSomethingA() EndFunction Function DoSomethingB() EndFunction Function MutualExA1() EndFunction Function MutualExA2() EndFunction Function MutualExB1() EndFunction Function MutualExB2() EndFunction Event OnBeginState() EndEvent Event OnKeyDown(int keyCode) EndEvent ; Initialize Event OnInit() GotoState("Initialized") EndEvent State Initialized ; Definitions Function DoSomethingA() if MyModA == none MutualExA1() else MutualExA2() endif EndFunction Function DoSomethingB() if MyModB == none MutualExB1() else MutualExB2() endif EndFunction Function MutualExA1() ; Do something EndFunction Function MutualExA2() MyModAScript mod = MyModA as MyModAScript ; Do something EndFunction Function MutualExB1() ; Do something EndFunction Function MutualExB2() MyModBScript mod = MyModB as MyModBScript ; Do something EndFunction Event OnBeginState() if Game.GetModByName("MyModA.esp") != 255 MyModA = Game.GetFormFromFile(0x000D62, "MyModA.esp") as Quest else MyModA = none endif if Game.GetModByName("MyModB.esp") != 255 MyModB = Game.GetFormFromFile(0x000D62, "MyModB.esp") as Quest else MyModB = none endif RegisterForKey(0x1E) ; A RegisterForKey(0x30) ; B EndEvent Event OnKeyDown(int keyCode) if keyCode == 0x1E DoSomethingA() elseif keyCode == 0x30 DoSomethingB() endif EndEvent EndState
Recommended Posts
Archived
This topic is now archived and is closed to further replies.