Jump to content

Using hotkeys through MCM?


Recommended Posts

I've been trying to alter SitAnywhere to allow user defined hotkeys instead of relying on lesser power.

Designing a MCM menu allowing to define hotkey had been easy but so far I've been enable to assign those hotkeys to anything.

 

I've attached two scripts to a dummy quest:

 

The first one appears to be working (it could probably be improved but at least it works).

 

 

Scriptname SitAnywhere_MCM extends SKI_ConfigBase

int SitCrossLegged_OID
int Property SitCrossLegged_Key = 22 Auto Hidden


Event OnConfigInit()

	Pages =new string[1]
	Pages[0] = "Config"

EndEvent


Event OnPageReset(string page)

	If (page == "Config")

		SetCursorFillMode(TOP_TO_BOTTOM)
		AddHeaderOption("Powers")
		AddToggleOption("Remove power", False)
		AddHeaderOption("Hotkeys")
		SitCrossLegged_OID 	= AddKeyMapOption("Sit cross-legged", SitCrossLegged_Key)

	EndIf

EndEvent


event OnOptionKeyMapChange(int a_option, int KeyCode, string a_conflictControl, string a_conflictName)

	if (a_option == SitCrossLegged_OID)

		bool continue = true

		if (a_conflictControl != "")
			string msg

			if (a_conflictName != "")
				msg = "This key is already mapped to:\n'" + a_conflictControl + "'\n(" + a_conflictName + ")\n\nAre you sure you want to continue?"
			else
				msg = "This key is already mapped to:\n'" + a_conflictControl + "'\n\nAre you sure you want to continue?"
			endIf

			continue = ShowMessage(msg, true, "$Yes", "$No")
		endIf

		if (continue)
			SitCrossLegged_Key = KeyCode
			SetKeymapOptionValue(a_option, KeyCode)
		endIf
		
	endIf
endEvent

Event OnConfigClose()
	UnregisterForAllKeys()
	RegisterForKey(SitCrossLegged_Key)
EndEvent

 

 

 

Right now I'm trying to display a simple debug message when pressing the hotkey (without any success):

 

 

Scriptname SitAnywhere_KeyPress extends Quest 

SitAnywhere_MCM Property MCMScript Auto

Event OnKeyDown(int KeyCode)
	if KeyCode == SitAnywhere_MCM.SitCrossLegged_Key
		Debug.Notification("Hello, World!")
	endif
EndEvent

 

 

And another version after looking at RND sources (not working either but simply having those files to compile has been a pain):

 

 

Scriptname SitAnywhere_KeyPress extends ReferenceAlias

GlobalVariable Property SitCrossLegged_Key Auto

Actor Player

Event OnInit()
	Player = Game.GetPlayer()
	RegisterForKey(SitCrossLegged_Key.GetValueInt())
EndEvent

Event OnKeyDown(int keycode)
	if keycode == SitCrossLegged_Key
		Debug.Notification("Hello, World!")
	endif
EndEvent

 

 

 

I imagine that I need a way link those two scripts to each others for the second one to recognize SitCrossLegged_Key but I just can't understand how.

Link to comment
Share on other sites

First script is working? Looks like it should.

 

Second script is where? On its own quest or on the same quest as the MCM? If on the same quest as the MCM, it should work provided you properly assigned the property for the MCM script. By being on the same quest as the MCM, it will receive the key events that are registered on the MCM. If it is on a separate quest, you'll have to figure out how to register for the key there. Perhaps in an OnInit, but that would have limitations (see next for why).

 

Your third script would only work if you passed the SitCrossLegged_Key integer of the MCM script into an actual Global that you then accessed on the third script. Also, by registering for the key during the OnInit event of a reference alias you limit the ability to recognize changes to the assigned hot key to those times that the quest is started.

Link to comment
Share on other sites

Second script is attached to the same dummy quest. I probably failed to assign the property from the mcm. Except I'm not sure what you mean by that. Until now the most complex property I assigned was a name to the first script.

And I messed something else because the second script fails to compile: SitAnywhere_KeyPress.psc(6,31): a property cannot be used directly on a type, it must be used on a variable. 6, 31 being SitCrossLegged_Key

 

 

I've been able to write a non-working but at least able to compile version:

 

 

Scriptname SitAnywhere_KeyPress extends Quest

GlobalVariable Property SitCrossLegged_Key Auto

Event OnConfigInit()
	RegisterForKey(SitCrossLegged_Key.GetValueInt())
EndEvent

Event OnKeyDown(int KeyCode)
	if KeyCode == SitCrossLegged_Key.GetValueInt()
		Debug.Notification("Hello, World!")
	endif
EndEvent

 

 

 

I understand that OnConfigInit is an overall better choice over OnInit but I'm unsure if it's the optimal choice here.

I'm still stuck with the GlobalVariable. Trying to delete that line lead to more error messages :

SitAnywhere_KeyPress.psc(4,16): variable SitCrossLegged_Key is undefined

SitAnywhere_KeyPress.psc(4,35): none is not a known user-defined type

SitAnywhere_KeyPress.psc(4,1): type mismatch on parameter 1 (did you forget a cast?)

SitAnywhere_KeyPress.psc(8,15): variable SitCrossLegged_Key is undefined

SitAnywhere_KeyPress.psc(8,34): none is not a known user-defined type

SitAnywhere_KeyPress.psc(8,12): cannot compare a int to a none (cast missing or types unrelated)

Link to comment
Share on other sites

OnConfigInit() is for use with MCM scripts only. It will do you no good on any other script. Also for that latest script attempt, you can't just delete the property and expect it to compile. That variable is in use throughout the script and has to be defined so that the compiler understands what to do with it.

 

**************************************

Try this...

 

Just saw the error on the second script from earlier. You used the actual script name of your MCM script instead of the variable that you assigned it to (see change below). Also, when you click on the properties button and select the MCMScript property you'll get a drop down with each record that the SitAnywhere_MCM script is attached to, select the correct one (if more than one).

Scriptname SitAnywhere_KeyPress extends Quest 

SitAnywhere_MCM Property MCMScript Auto

Event OnKeyDown(int KeyCode)
	if KeyCode == MCMScript.SitCrossLegged_Key ;this is the line that was edited
		Debug.Notification("Hello, World!")
	endif
EndEvent
Link to comment
Share on other sites

Ok, understood. I need to call the MCMScript for the hotkey to be recognized. Makes sense.

But I'm still unable to display the notification. I tried to save/reload, the hotkey is registered and other mods can display than kind of notification. I'm probably still missing something (something obvious that will make me feel like a moron later on I'm sure of it) in my second script.

Link to comment
Share on other sites

You're over-complicating things by having multiple scripts. You can do all of the work from one. And because I've been thinking about doing something similar for a while now I went ahead and wrote the full script.

 

This version will work as long as you create an MCM quest with the required player alias and loading script then use this as the main quest script. You'll get a valid MCM menu as long as you also have the Sit Anywhere.esp loaded. It's entirely script driven, but the better version would fill the properties in the CK instead of using GetFormFromFile. (Look at the beginning of the OnConfigInit function.) My default key mappings are U for sit cross-legged, no default for sit on ledge, and ESC for the two create powers. Mapped keys remove the matching power and ESC isn't really a valid mapping but it will disable the matching powers anyway. So by default you'll still have the sit on ledge power and only be able to use the sit cross-legged hotkey. And that ModName property really, really should be set from the CK not from the script.

 

In theory it's using the MCM state system but the states are being treated more like variables in this case. You can technically put as many powers or spells as you like into the SitPower property and convert them to hotkey use without any extra coding. But the scripts assume the player already has the matching powers or spells so if not the player will end up getting them for free. (And spells cast through script functions don't play any sounds or animations which isn't a problem for these spells but might be for others.)

Scriptname SitAnywhere_MCM extends SKI_ConfigBase
{Replace Powers with configured Hotkeys.}

Spell[] Property SitPower Auto
{Fill with the four powers from the Sit Anywhere mod.}

int[] Property SitKeyDefault Auto
{Fill with 4 default key values (-1 for unmapped keys, 1 to simply disable the power).}

Actor Property PlayerRef Auto
{Auto-fill with PlayerRef.}


int[] SitKey
;Internal array used to track current configuration of keys.



Event OnConfigInit()
{Fill initial keycode array from defaults and enable or disable powers as appropriate.}

    ;; These should be filled in the CK not a script!
	ModName = "Sit Anywhere"
	if !SitPower
		if Game.GetModByName("Sit Anywhere.esp") == 255
			return
		endif
		SitPower = new Spell[4]
		SitPower[0] = Game.GetFormFromFile(0x000D68, "Sit Anywhere.esp") as Spell
		SitPower[1] = Game.GetFormFromFile(0x0048B2, "Sit Anywhere.esp") as Spell
		SitPower[2] = Game.GetFormFromFile(0x0012CF, "Sit Anywhere.esp") as Spell
		SitPower[3] = Game.GetFormFromFile(0x0012D3, "Sit Anywhere.esp") as Spell
	endif
	if !SitKeyDefault
		SitKeyDefault = new int[4]
		SitKeyDefault[0] = 22
		SitKeyDefault[1] = -1
		SitKeyDefault[2] = 1
		SitKeyDefault[3] = 1
	endif
	if !PlayerRef
		PlayerRef = Game.GetPlayer()
	endif
	;; End of stuff that should have been done in the CK

	int i
	int[] junk
	; No keys should be registered, but it doesn't hurt to make sure
	UnregisterForAllKeys()
	; SitPower is a manditory property fail if it's not set!
	if !SitPower || SitPower.length < 1
		return
	endif
	; SitKeyDefault is optional so fill it with unmapped keys if it is empty
	if !SitKeyDefault
		SitKeyDefault = Utility.CreateIntArray(SitPower.length)
		i = SitPower.length
		while i > 0
			i -= 1
			SitKeyDefault[i] = -1
		endwhile
	endif
	; If SitKeyDefault is misconfigured, try to fix it but preserve old values
	if SitKeyDefault.length != SitPower.length
		SitKey = Utility.CreateIntArray(SitPower.length)
		i = SitPower.length
		while i > 0
			i -= 1
			if i < SitKeyDefault.length
				SitKey[i] = SitKeyDefault[i]
			else
				SitKey[i] = -1
			endif
		endwhile
		SitKeyDefault = SitKey
		SitKey = junk ; empty at this point
	endif
	; Copy defaults into SitKey but preserve any existing values
	if !SitKey || SitKey.length != SitKeyDefault.length
		junk = Utility.CreateIntArray(SitKeyDefault.length)
		i = SitKeyDefault.length
		while i > 0
			i -= 1
			if i < SitKey.length
				junk[i] = SitKey[i]
			else
				junk[i] = SitKeyDefault[i]
			endif
		endwhile
		SitKey = junk ; not really junk this time
	endif
	; Register the hotkeys and add or remove the powers to match
	i = SitPower.length
	while i > 0
		i -= 1
		if SitKey[i] > 0
			if PlayerRef.HasSpell(SitPower[i])
				PlayerRef.RemoveSpell(SitPower[i])
			endif
		elseif !PlayerRef.HasSpell(SitPower[i])
			PlayerRef.AddSpell(SitPower[i])
		endif
		RegisterForKey(SitKey[i])
	endwhile
EndEvent


Event OnPageReset(string page)
{A short, single page configuration with one entry for each power/hotkey.}
	SetCursorFillMode(TOP_TO_BOTTOM)
	OnConfigInit() ; Just to make absolutely sure everything is configured it possible
	if !SitPower || SitPower.length < 1
		return ; Something is terribly wrong!
	endif
	int i = 0
	while i < SitPower.length
		AddKeyMapOptionST(i + "KEY", SitPower[i].GetName() + " Hotkey", SitKey[i], OPTION_FLAG_WITH_UNMAP)
		i += 1
	endwhile
EndEvent


Event OnHighlightST()
{Every option gets the same helper info text at the bottom of the screen.}
	SetInfoText("Setting a hotkey disables the matching power.\nUnmapping the hotkey restores the power.\nMapping to ESC will not create a hotkey but will disable the power.")
EndEvent

Event OnDefaultST()
{Default state is retreived from the array based on the current state.}
	int keyCode = SitKeyDefault[GetState() as int]
	;if SitKey.Find(keyCode) > -1
	;	ShowMessage("This key is already mapped to:\n'" + GetCustomControl(keyCode) + "'\n(Sit Anywhere)\nCan't restore the default right now.", false, "$OK")
	;	return
	;endif
	OnKeyMapChangeST(keyCode, GetCustomControl(keyCode), "Sit Anywhere") ; unmapped
EndEvent

Event OnKeyMapChangeST(int keyCode, string conflictControl, string conflictName)
{Make sure the key is valid then record it and unregister/register keys and add/remove power as needed.}
	if keyCode > 1
		if conflictName
			conflictName = "(" + conflictName + ")"
		else
			conflictName = ""
		endif
		if conflictControl
			if SitKey.Find(keyCode) > -1
				ShowMessage("This key is already mapped to:\n'" + conflictControl + "'\n" + conflictName+ "\nYou can't map the same key twice.", false, "$OK")
				return
			elseif !ShowMessage("This key is already mapped to:\n'" + conflictControl + "'\n" + conflictName+ "\nAre you sure you want to continue?", true, "$Yes", "$No")
				return
			endif
		endIf
	elseif keyCode == 1
		if !ShowMessage("Mapping a key to ESC will disable the power without creating a hotkey.\nAre you sure you wan tot continue?", true, "$Yes", "$No")
			return
		endif
	endif
	int i = GetState() as int
	if SitKey[i] > 1
		UnregisterForKey(SitKey[i])
	endif
	SitKey[i] = keyCode
	if keyCode > 1
		RegisterForKey(keyCode)
	endif
	if keyCode > 0
		if PlayerRef.HasSpell(SitPower[i])
			PlayerRef.RemoveSpell(SitPower[i])
		endif
	elseif !PlayerRef.HasSpell(SitPower[i])
		PlayerRef.AddSpell(SitPower[i])
	endif
	SetKeyMapOptionValueST(keyCode)
EndEvent


string function GetCustomControl(int keyCode)
{Allows reporting previously mapped keys in this mod for itself and other MCM pages.}
	int i = SitKey.Find(keyCode)
	if i < 0 || SitKey[i] == 1 ; ESC doesn't count
		return ""
	else
		return SitPower[i].GetName() + " Hotkey"
	endif
endFunction


Event OnKeyDown(int code)
{The real work of making the hotkey work like the matching powers is very simple.}
	SitPower[SitKey.Find(code)].Cast(PlayerRef)	
EndEvent

Link to comment
Share on other sites

That script is a lot more complex (or at least bigger) that anything I imagined and I'll probably (more than probably in fact) need some time to fully understand it.

Anyway thanks to the both of you. :smile:

Link to comment
Share on other sites

That's probably some stupid questions (and assertions) but meh.

Regarding the things that are supposed to be filled in the CK:

ModName and PlayerRef were easy enough and should be ok.

SitPower use the FormID of the spells from the mod.

 

For SitKeyDefault I'm a bit puzzled: It follows the same order that than SitPower but is that enough for key X to be assigned by default to spell[0] and key Y to spell[1] or is there something I missed?

Is GetModByName a property that I'm supposed to add to my script?

 

Am I supposed to add that block of code to my script in addition to the properties? Obviously yes, as fillind the property and pasting all your code makes things work flawlessly but as I said: Stupid questions.

Link to comment
Share on other sites

It's long because I put error and safety checks for everything I could think of to prevent problems. There's even that commented out section in OnDefaultST that gives a slightly different message if you try to reset a key to a default that has been remapped to another key in the mod.

 

To fill SitPower in the CK you first need to ESMify Sit Anywhere.esp then load up it and this mod in the CK. When you go to fill the properties on SitPower it's an array so you have to add 4 entries and select each of the powers individually. It's tedious compared to auto-fill but the script would be even longer if you used 4 individual properties. (Don't forget to ESPify Sit Anywhere.esp when you're finished.)

 

SitKeyDefault is also an array and you should add the same number of entries as powers. And yes, order matters to match them up. SitKeyDefault[2] will be the key for SitPower[2], etc. The somewhat complicated code in OnConfigInit makes sure that even if you fill SitKeyDefault incorrectly (having too many or too few entries) something sensible will happen when you go to use the mod.

 

GetModByName is one of the SKSE functions. If you fill the properties correctly you can completely remove that first chunk of code from OnConfigInit. Or you can leave it because if the properties are filled in the CK then the if statements will keep that section from doing anything anyway. (Except for the line about setting ModName where I forgot to wrap it in and "If !ModName" check like I did for the other 3 properties.

Link to comment
Share on other sites

Thanks to you things make sense to me.

 

There's still one last issue: the keys need to be deactivated in some occasion (mostly while menus are opened).

I came with this (slightly altered piece of code from frostfall):

Event OnKeyDown(int code)
{The real work of making the hotkey work like the matching powers is very simple.}
	if UI.IsMenuOpen("Console") || UI.IsMenuOpen("Book Menu") || UI.IsMenuOpen("BarterMenu") || UI.IsMenuOpen("ContainerMenu") || UI.IsMenuOpen("Crafting Menu") || UI.IsMenuOpen("Dialogue Menu") || UI.IsMenuOpen("FavoritesMenu") || UI.IsMenuOpen("InventoryMenu") || UI.IsMenuOpen("Journal Menu") || UI.IsMenuOpen("Lockpicking Menu") || UI.IsMenuOpen("MagicMenu") || UI.IsMenuOpen("MapMenu") || UI.IsMenuOpen("MessageBoxMenu") || UI.IsMenuOpen("Sleep/Wait Menu") || UI.IsMenuOpen("StatsMenu")
		return
	else
		SitPower[SitKey.Find(code)].Cast(PlayerRef)	
	endif
EndEvent
Is this correct? I'm asking because Chesko's code looks like this:

Event OnKeyDown(int keyCode)
	if UI.IsMenuOpen("Console") || UI.IsMenuOpen("Book Menu") || UI.IsMenuOpen("BarterMenu") || UI.IsMenuOpen("ContainerMenu") || UI.IsMenuOpen("Crafting Menu") || UI.IsMenuOpen("Dialogue Menu") || UI.IsMenuOpen("FavoritesMenu") || UI.IsMenuOpen("InventoryMenu") || UI.IsMenuOpen("Journal Menu") || UI.IsMenuOpen("Lockpicking Menu") || UI.IsMenuOpen("MagicMenu") || UI.IsMenuOpen("MapMenu") || UI.IsMenuOpen("MessageBoxMenu") || UI.IsMenuOpen("Sleep/Wait Menu") || UI.IsMenuOpen("StatsMenu")
		return
	endif
	if keyCode == _Frost_HotkeyWeathersense.GetValueInt()
		_Frost_Weathersense_Spell.Cast(PlayerRef)
	endif
EndEvent
I've tried to stay as close as possible to his code (contrary to me he probably knows what he's doing) but using the same syntax ("if, endif, if, endif" instead of "if, else, endif") but then the script failed to compile. He uses int keycode instead of int code which may explain everything.
Link to comment
Share on other sites

  • Recently Browsing   0 members

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