Jump to content

How do I give my mod soft-dependencies?


ScrollSerrayt

Recommended Posts

Posted

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. 

Posted

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

 

Posted
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. 

Posted
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. 

 

Posted
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.

Posted
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. 

Posted
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

 

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

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