opusGlass Posted July 6, 2017 Share Posted July 6, 2017 Using the technique discussed here As Teh Masterer says, it will throw an error to the logs. But besides that, is it considered safe to use this as a check for SKSE, then run different code for SKSE systems and non-SKSE systems? I'm designing a script that needs to function on non-SKSE systems (namely SSE) but I would like to add extra functionality for SKSE systems. Link to comment Share on other sites More sharing options...
cdcooley Posted July 6, 2017 Share Posted July 6, 2017 Yes. There's no way to avoid at least one error in the Papyrus logs. There's no way to call a function that doesn't exist without generating an error and there's no other way to determine if the SKSE functions are working than trying one of them. The best practice is to use a single call to SKSE.GetVersionRelease() and set some variable. Then you can check the value of that variable and avoid calling the SKSE specific code if you know SKSE isn't installed. Otherwise every SKSE function is going to either return 0 or None and also generate an error in the log file when SKSE isn't installed. Link to comment Share on other sites More sharing options...
opusGlass Posted July 7, 2017 Author Share Posted July 7, 2017 Thank you cdcooley! That's what I thought but I figured I should double check. Cheers Link to comment Share on other sites More sharing options...
opusGlass Posted July 8, 2017 Author Share Posted July 8, 2017 The best practice is to use a single call to SKSE.GetVersionRelease() and set some variable. Wait, new question. Is it possible to safely make an optional MCM menu? I'm unable to find a similar version check function for SkyUI. But even if I do find it, MCM menus require a script that extends SKI_ConfigBase. I suppose I could stick the MCM script into a separate quest that does not start enabled, and it only gets activated if SkyUI is installed. Then no SkyUI-dependent code would get run. But I'm not sure if that's sufficient since a script of unknown type would still be attached to a record. EDIT: I will make a new post Link to comment Share on other sites More sharing options...
cdcooley Posted July 9, 2017 Share Posted July 9, 2017 Since players without SkyUI won't have the SKI_ConfigBase script your script which extends that one will simply fail to load. There will be two or three lines of errors in the Papyrus log as the game loads but nothing else bad will happen. Optional MCM support is as easy as making sure you don't put anything critical to the functioning of your mod in the MCM script. Technically the MCM script doesn't need to be on a separate quest since you can attach more than one script to any particular quest. But since any scripts extending one of the SKI_ scripts will fail to load you have to make sure that anything critical is handled in some other script. In particular that means you can't store any configuration information in local variables or properties of the MCM script itself. You can use global variables, properties on other scripts, and anything else you can detect directly from the game (enable state of objects, etc.) to store information. As long as the MCM is just accessing and changing those values you'll be fine. Here's an example MCM using that style and a section of another script showing how I use custom properties of other scripts. With the InigoMCM you can set some options related to Inigo's horse but you can also set those same options with the console using commands like "setpqv InigoStatus steed allow" because of the custom steed property on his status quest script. ScriptName InigoMCMScriptDemo extends SKI_ConfigBase {Example MCM menu based on InigoMCM mod code. Values set by the MCM are either ActorValues of Inigo himself or properties in some script, could have been GlobalVariables or even local variables.} ; Properties to access various external objects and scripts. WorldSpace Property Tamriel Auto Actor Property PlayerRef Auto Actor Property InigoRef Auto ; this is used enough to make it worth setting as its own property InigoStatusQuestScript Property InigoStatus Auto {The status script holds lots of useful properties and functions that can be tweaked from the MCM.} InigoExtrasScript Property InigoExtras Auto {A script holding extra features like whistle hotkey and Mr. Dragonfly options that are not in the main Inigo mod.} ; These dynamic values must be checked each time the MCM is opened amd unfortunately ; the values are needed in multiple functions so they have to be declared here Actor SteedRef ; may be null Form CurrentWorld ; either a world or cell so never null bool IsSteedAllowedHere ; based on current contents of the formlist and this location ; I do not need an OnConfigInit() function, but that works just like the basic version of the MCM tutorial. Function OnPageReset(String a_page) ; Gather some information before showing the page because somethings get disabled if they are not appropriate ; Get current steed from its alias, if he has one. SteedRef = InigoStatus.SteedAlias.GetReference() as Actor CurrentWorld = PlayerRef.GetWorldSpace() if !CurrentWorld CurrentWorld = PlayerRef.GetParentCell() endif ; There is a formlist with all of the places Inigo is allowed to ride, see if we are in it. IsSteedAllowedHere = CurrentWorld && InigoStatus.InigoSteedAllowed && InigoStatus.InigoSteedAllowed.HasForm(CurrentWorld) ; Now show the menu (a_page variable is not used because all of the options fit on one page) SetCursorFillMode(TOP_TO_BOTTOM) ; This one is a key remap that allows the key to be completely unmapped. AddKeyMapOptionST("Whistle", "Whistle Hotkey", InigoExtras.WhistleKeyCode, OPTION_FLAG_WITH_UNMAP) ; This one is a multiple option toggle implemented with a TextOption field. There is another control specifically for that but I did not use it. AddTextOptionST("ShowMrD", "Show Mr Dragonfly", ShowMrDTxt(InigoExtras.MrDVisible.ShowMrD)) SetCursorPosition(1) AddToggleOptionST("SteedAllowed", "Steed Allowed Here", IsSteedAllowedHere) if SteedRef AddSliderOptionST("SteedSpeedMult", "Trusty Steed's Speed", SteedRef.GetActorValue("SpeedMult"), "{0}%") else ; I think it looks nicer to show a disabled text box if he does not have a steed AddTextOptionST("SteedSpeedMult", "Trusty Steed's Speed", "", OPTION_FLAG_DISABLED) endif SetCursorPosition(6) AddHeaderOption("Advanced Options") ; I wanted it to be clear that some of these are percentage values so they have custom formatting. ; These in this group all modify actor values on Inigo himself. AddSliderOptionST("SpeedMult", "Inigo's Speed", InigoRef.GetActorValue("SpeedMult"), "{0}%") AddSliderOptionST("CarryWeight", "Inigo's Carry Capacity", InigoRef.GetActorValue("CarryWeight"), "{0}") AddSliderOptionST("AttackDamageMult", "Inigo's Attack Damage", InigoRef.GetActorValue("AttackDamageMult")*100.0, "{0}%") ; real value is a simple multipler AddSliderOptionST("DamageResist", "Inigo's Damage Resistance", InigoRef.GetActorValue("DamageResist"), "{0}") SetCursorPosition(7) AddHeaderOption("Emergency Use Only") ; These TextOptions are being used as buttons AddTextOptionST("SummonInigoNow", "Summon Inigo Now", "") AddTextOptionST("SummonMrD", "Summon Mr Dragonfly", "") AddTextOptionST("RecoverBow", "Recover Inigo's Bow", "") AddTextOptionST("RecoverBooks", "Recover Inigo's Books", "") EndFunction string Function ShowMrDTxt(int val) {Simple utility function to translate the ShowMrD variable values into matching text for the button.} if val == 1 return "Large Jar" elseif val == 2 return "Small Jar" elseif val == 3 return "Front Jar" else return "No" endif EndFunction ;Here are the various states needed to implement everything above (in reverse order) State RecoverBooks Event OnHighlightST() SetInfoText("Recover Inigo's missing books.\n(They'll be added to his inventory not yours.)") EndEvent ; Implements a button to perform an action Event OnSelectST() SetOptionFlagsST(OPTION_FLAG_DISABLED) if InigoRef.GetItemCount(InigoExtras.InigoJournal) < 1 && PlayerRef.GetItemCount(InigoExtras.InigoJournal) < 1 InigoRef.AddItem(InigoExtras.InigoJournal, 1) endif if InigoRef.GetItemCount(InigoExtras.InigoBrave) < 1 && PlayerRef.GetItemCount(InigoExtras.InigoBrave) < 1 InigoRef.AddItem(InigoExtras.InigoBrave, 1) endif if PlayerRef.HasSpell(InigoExtras.WhistlePower) if InigoRef.GetItemCount(InigoExtras.WBook) < 1 && PlayerRef.GetItemCount(InigoExtras.WBook) < 1 InigoRef.AddItem(InigoExtras.WBook, 1) endif endif if (InigoExtras.InigoBadVibrationsQuest.IsStageDone(180) if InigoRef.GetItemCount(InigoExtras.DVJournal) < 1 && PlayerRef.GetItemCount(InigoExtras.DVJournal) < 1 InigoRef.AddItem(InigoExtras.DVJournal, 1) endif if InigoRef.GetItemCount(InigoExtras.Champion1) < 1 && PlayerRef.GetItemCount(InigoExtras.Champion1) < 1 InigoRef.AddItem(InigoExtras.Champion1, 1) endif if InigoRef.GetItemCount(InigoExtras.Champion2) < 1 && PlayerRef.GetItemCount(InigoExtras.Champion1) < 1 InigoRef.AddItem(InigoExtras.Champion2, 1) endif endif EndEvent EndState State RecoverBow Event OnHighlightST() SetInfoText("Recover Inigo's bow if you've lost it.\n(It will be added to his inventory not yours.)") EndEvent Event OnSelectST() SetOptionFlagsST(OPTION_FLAG_DISABLED) if InigoRef.GetItemCount(InigoExtras.Bow) < 1 && PlayerRef.GetItemCount(InigoExtras.Bow) < 1 InigoRef.AddItem(InigoExtras.Bow, 1) InigoRef.EquipItem(InigoExtras.Bow) ; Equip it so the player knows this function worked. endif EndEvent EndState State SummonMrD Event OnHighlightST() SetInfoText("Summon Mr. Dragonfly to the player.") EndEvent Event OnSelectST() SetOptionFlagsST(OPTION_FLAG_DISABLED) InigoExtras.MrD.MoveTo(PlayerRef, 80.0*Math.sin(PlayerRef.GetAngleZ()), 80.0*Math.cos(PlayerRef.GetAngleZ()), 10.0, 0) EndEvent EndState State SummonInigoNow Event OnHighlightST() SetInfoText("Bring Inigo to the player. This could break his quests!") EndEvent Event OnSelectST() SetOptionFlagsST(OPTION_FLAG_DISABLED) ; position him directly in front of the player and turn him to face the player. ; enabling and disabling can fix many game glitches float az = PlayerRef.GetAngleZ() InigoRef.MoveTo(PlayerRef, 80.0*Math.sin(az), 80.0*Math.cos(az), 10.0, 0.0) InigoRef.Disable() InigoRef.SetAngle(0.0, 0.0, az + 180.0) InigoRef.Enable() EndEvent EndState State AttackDamageMult Event OnHighlightST() SetInfoText("Inigo's Attack Damage compared to other NPCs. [default = 100%]") EndEvent Event OnSliderAcceptST(float value) InigoRef.ForceActorValue("AttackDamageMult", value/100.0) ; real value is a multipler, so convert percent back to decimal SetSliderOptionValueST(value) EndEvent Event OnSliderOpenST() SetSliderDialogStartValue(InigoRef.GetActorValue("AttackDamageMult")*100.0) ; real value isn't a percent, so make it one SetSliderDialogDefaultValue(100.0) SetSliderDialogRange(10.0, 1000.0) SetSliderDialogInterval(10.0) EndEvent EndState State DamageResist Event OnHighlightST() SetInfoText("Inigo's Damage Resistance (including benefits from any armor he may be wearing). [default = 0]") EndEvent Event OnSliderAcceptST(float value) InigoRef.ForceActorValue("DamageResist", value) SetSliderOptionValueST(value) EndEvent Event OnSliderOpenST() SetSliderDialogStartValue(InigoRef.GetActorValue("DamageResist")) SetSliderDialogDefaultValue(0.0) SetSliderDialogRange(0.0, 1000.0) SetSliderDialogInterval(1.0) EndEvent EndState State CarryWeight Event OnHighlightST() SetInfoText("Inigo's current carry capcity (possibly modified by spells or enchantments). [default = 300]") EndEvent Event OnSliderAcceptST(float value) InigoRef.ForceActorValue("CarryWeight", value) SetSliderOptionValueST(value) EndEvent Event OnSliderOpenST() SetSliderDialogStartValue(InigoRef.GetActorValue("CarryWeight")) SetSliderDialogDefaultValue(300.0) SetSliderDialogRange(100.0, 900.0) SetSliderDialogInterval(5.0) EndEvent EndState State SpeedMult Event OnHighlightST() SetInfoText("Inigo's current speed compared to the game default speed for NPCs. [default = 115%]") EndEvent Event OnSliderAcceptST(float value) InigoRef.ForceActorValue("SpeedMult", value) SetSliderOptionValueST(value) EndEvent Event OnSliderOpenST() SetSliderDialogStartValue(InigoRef.GetActorValue("SpeedMult")) SetSliderDialogDefaultValue(115.0) SetSliderDialogRange(50.0, 200.0) SetSliderDialogInterval(5.0) EndEvent EndState State SteedSpeedMult Event OnHighlightST() SetInfoText("Inigo's steed's current speed compared to the game default speed for horses. [default = 100%]") EndEvent Event OnSliderAcceptST(float value) SteedRef.ForceActorValue("SpeedMult", value) SetSliderOptionValueST(value) EndEvent Event OnSliderOpenST() SetSliderDialogStartValue(SteedRef.GetActorValue("SpeedMult")) SetSliderDialogDefaultValue(100.0) SetSliderDialogRange(50.0, 200.0) SetSliderDialogInterval(5.0) EndEvent EndState State SteedAllowed Event OnHighlightST() SetInfoText("Should Inigo be allowed to ride his trusty steed in this location?\nThis also summons or dismisses his current steed if possible.") EndEvent Event OnDefaultST() IsSteedAllowedHere = true OnSelectST() EndEvent Event OnSelectST() SetOptionFlagsST(OPTION_FLAG_DISABLED) IsSteedAllowedHere = !IsSteedAllowedHere if CurrentWorld != Tamriel ; Riding is always allowed in Tamriel if IsSteedAllowedHere InigoStatus.Steed = "Allow" else InigoStatus.Steed = "Forbid" endif endif if SteedRef && !SteedRef.IsBeingRidden() if IsSteedAllowedHere || (CurrentWorld == Tamriel && PlayerRef.GetDistance(SteedRef) > 900) InigoStatus.Steed = "Summon" else InigoStatus.Steed = "Stable" InigoStatus.Steed = "Follow" endif endif EndEvent EndState State ShowMrD Event OnHighlightST() SetInfoText("Should Inigo wear Mr Dragonfly in a jar on his belt and if so how?\nOptions: No (the default), in a full-sized jar, in a small jar, and in a small jar in front") EndEvent Event OnSelectST() int pr = InigoExtras.MrDVisible.ShowMrD + 1 ; cycle to the next option InigoExtras.MrDVisible.ShowMrD = pr ;the custom property ShowMrD handles everything important SetTextOptionValueST(ShowMrDTxt(pr)) EndEvent Event OnDefaultST() InigoExtras.MrDVisible.ShowMrD = 0 SetTextOptionValueST(ShowMrDTxt(0)) EndEvent EndState State Whistle Event OnHighlightST() SetInfoText("After learning to Whistle to Inigo you can use this key instead of the power.") EndEvent Event OnDefaultST() OnKeyMapChangeST(45,"","") ; X is the default EndEvent Event OnKeyMapChangeST(int keyCode, string conflictControl, string conflictName) if keyCode == 1 || (keyCode > 255 && !Game.UsingGamepad()) ShowMessage("$SKI_MSG1", false, "$OK", "$Cancel") return endif InigoExtras.UnregisterForKey(InigoExtras.WhistleKeyCode) InigoExtras.WhistleKeyCode = keyCode if keyCode > 0 InigoExtras.RegisterForKey(keyCode) endif SetKeyMapOptionValueST(InigoExtras.WhistleKeyCode, false, "") EndEvent EndState Excerpt from InigoStatusQuestScript.psc (attached to the InigoStatus quest). ; These two actions can't be triggered any other way ; setpqv inigostatus steed allow - Allow Inigo to ride in this worldspace ; setpqv inigostatus steed forbid - Forbid Inigo from riding here ; These are just for testing ; setpqv inigostatus steed status - Report about Inigo and his horse ; setpqv inigostatus steed stable - Send Inigo's horse home (to the cabin) ; setpqv inigostatus steed summon - Bring Inigo's horse to him now ; setpqv inigostatus steed ride - Inigo's should ride his horse ; setpqv inigostatus steed follow - Inigo's horse should follow him ; setpqv inigostatus steed wait - Inigo's horse should stay behind ; setpqv inigostatus steed help - Show the list of horse commands string Property Steed Hidden {Trigger a few extra horse features.} Function set(string cmd) Form world = InigoRef.GetWorldSpace() if !world world = InigoRef.GetParentCell() endif if world && world != PlayerRef.GetWorldSpace() && world != PlayerRef.GetParentCell() world = None endif if !steedRef && cmd != "Status" Debug.Notification("Inigo does not have a horse.") elseif cmd == "Status" if world && InigoSteedAllowed.HasForm(world) Debug.Notification("Inigo can ride here.") elseif InigoRef.GetDistance(PlayerRef) < 9000 ; only report if Inigo is nearby Debug.Notification("Inigo can not ride here.") endif int mode = InigoRef.GetFactionRank(InigoRideWithoutPlayerFaction) if !steedRef Debug.Notification("Inigo does not have a steed.") elseif mode < 0 Debug.Notification("Inigo's steed does not follow.") elseif mode == 0 Debug.Notification("Inigo's steed travels with him.") else Debug.Notification("Inigo rides whenever possible.") endif OnAnimationEvent(None, None) elseif cmd == "Allow" if world && !InigoSteedAllowed.HasForm(world) InigoSteedAllowed.AddForm(world) steedRef.MoveTo(InigoRef, 50 * Math.sin(InigoRef.GetAngleZ() + 90), 50 * Math.cos(InigoRef.GetAngleZ() + 90)) OnAnimationEvent(None, "tailHorseMount") else Steed = "Status" endif elseif cmd == "Forbid" if world && InigoSteedAllowed.HasForm(world) InigoSteedAllowed.RemoveAddedForm(world) OnAnimationEvent(None, None) else Steed = "Status" endif elseif cmd == "Stable" if InigoRef.IsOnMount() InigoRef.PlayIdleWithTarget(Dismount, steedRef) Utility.Wait(2.0) endif InigoRef.SetFactionRank(InigoRideWithoutPlayerFaction, -1) steedRef.MoveTo(LangleyHouseMapMarker) ; Langley's House Marker steedRef.EvaluatePackage() elseif cmd == "Summon" if !InigoRef.IsOnMount() && world && InigoSteedAllowed.HasForm(world) steedRef.MoveTo(InigoRef, 50 * Math.sin(InigoRef.GetAngleZ() + 90), 50 * Math.cos(InigoRef.GetAngleZ() + 90)) InigoRef.SetFactionRank(InigoRideWithoutPlayerFaction, 0) steedRef.EvaluatePackage() else Steed = "Status" endif elseif cmd == "Wait" InigoRef.SetFactionRank(InigoRideWithoutPlayerFaction, -1) steedFollowMode = -1 steedRef.EvaluatePackage() OnAnimationEvent(None, None) elseif cmd == "Follow" InigoRef.SetFactionRank(InigoRideWithoutPlayerFaction, 0) steedFollowMode = 0 steedRef.EvaluatePackage() OnAnimationEvent(None, None) elseif cmd == "Ride" && !InigoRef.IsOnMount() InigoRef.SetFactionRank(InigoRideWithoutPlayerFaction, 1) OnAnimationEvent(None, "tailHorseMount") else Debug.Notification("Ride, Follow, Wait, Summon, Stable, Allow, Forbid, Status") OnAnimationEvent(None, None) endif EndFunction EndProperty Custom property code can get messy but the great thing about them is that setting the property causes the function to run. That means you can run the function with a console command. Link to comment Share on other sites More sharing options...
opusGlass Posted July 9, 2017 Author Share Posted July 9, 2017 Thank you cdcooley! You're the hero skyrim modding needs. :) Link to comment Share on other sites More sharing options...
Recommended Posts