Jump to content

[LE] Is it generally considered safe to have SKSE scripts that also run on non-SKSE systems?


Recommended Posts

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

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

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

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

  • Recently Browsing   0 members

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