Jump to content

[LE] Change global variable value when opening/closing activators?


Recommended Posts

Okay. I think my approach would be to run a search quest every time a cell attaches to the player, looking for, specifically, the cooking crafting stations (by keyword, I think). Shouldn't need more than a dozen aliases, I would say. A simple script goes on those to check for activations. Another script goes on the player via an alias, for state control, and to control the inventory swaps and check for menu closes. When one of the cooking stations is activated, it calls a function in the inactive state of the player script, which flips it into the listening state for an OnMenuClose. When the OnMenuClose fires, it flips the player script back into the inactive state. In the inactive state, there's no OnMenuClose, and in the active one, the attempted state-change function is empty. You can call your swaps on entering and leaving the active state. The state change functions are very quick to execute and should prevent weird race conditions from happening (e.g. the player triggering multiple swaps by spamming the activation button).

 

An alternate means to find nearby stations would be through a cloak magic effect, but that tends to interfere with brawls and certain other events.

Thanks for your reply, foamyesque.
I'd like to avoid the cloak method. It seems pretty messy to me and would be more of a last-ditch approach if nothing else works.
I've never worked with states before, so let me see if I understand what you're recommending.
Quest 1: Adds aliases to cooking stations (and tanning racks) when a cell is loaded.
Script 1: Attached to the aliases of Quest 1, this script checks for activation. When the aliases are activated, the script calls on a function within the inactive state of Script 2 to switch states.
Quest 2: Adds an alias to the player.
Script 2: A script is attached to the player alias in Quest 2. This script controls the states and does the swapping.
The only thing the inactive state (default) does is call a function to switch to the active/listening state. Calling this function will somehow be dependent upon activation of the aliases in Quest/Script 1 (use a new global variable that is set by Script 1??).
Upon entering active/listening state, the script will swap items in the inventory (I will either call on my existing spell or just copy some of that script here). When OnMenuClose fires, it swaps the items in the inventory back and then changes the state back to the inactive state.
Is that what you are recommending? Sorry for the questions; I've only ever made one quest alias and, as I said above, I've never worked with states before. It seems fairly straightforward though.
Link to comment
Share on other sites

Just a thought. If on menu closes works, & if your using SKSE RegisterForMenu( "Crafting Menu") why not use OnMenuOpen? It work on any Quest Script, without any of the fuss?

Scriptname SomeQuest extends Quest
Event OnInit()
    self.RegisterForMenu("Crafting Menu")
EndEvent
Event OnMenuClose(string menuName)
    If(menuName == "Crafting Menu")
        ;... Do Something cool.
    EndIf
EndEvent
Event OnMenuOpen(string menuName)
    If(menuName == "Crafting Menu")
        ;... Do Something cool.
    EndIf
EndEvent

Or am I misunderstanding something?

 

Anywho, another point of view.

 

Cheeers

Link to comment
Share on other sites

Just a thought. If on menu closes works, & if your using SKSE RegisterForMenu( "Crafting Menu") why not use OnMenuOpen? It work on any Quest Script, without any of the fuss?

Scriptname SomeQuest extends Quest
Event OnInit()
    self.RegisterForMenu("Crafting Menu")
EndEvent
Event OnMenuClose(string menuName)
    If(menuName == "Crafting Menu")
        ;... Do Something cool.
    EndIf
EndEvent
Event OnMenuOpen(string menuName)
    If(menuName == "Crafting Menu")
        ;... Do Something cool.
    EndIf
EndEvent

Or am I misunderstanding something?

 

Anywho, another point of view.

 

Cheeers

 

 

I've done a little research, and it looks to me like I might be able to do this fairly easy. This thread (https://forums.nexusmods.com/index.php?/topic/848279-how-to-use-the-onmenuopen-event-handler/) was helpful.

 

 

If I understand the process correctly (a big if!!), I would do the following:

 

Quest: Adds an alias to PlayerRef.

 

Script: Attached to PlayerRef via Alias, this script checks for the opening and closing of menus and does the switching. So something like this:

 

 

 

ScriptName CraftingItemSwap extends ReferenceAlias

Event OnInit()
     RegisterForMenu("CookingMenu")          ; I assume there is a single menu activated by the multiple cooking stations, but will check this
     RegisterForMenu("TanningRackMenu")      ; I will update the name when I have access to CK
EndEvent

Event OnMenuOpen(OpenedMenu)
     If (OpenedMenu == "CookingMenu") || (OpenedMenu == "TanningRackMenu")
          ; Insert code/function to switch items
          RegisterForMenu(OpenedMenu)
     EndIf
EndEvent

Event OnMenuClose(ClosedMenu)
     If (ClosedMenu == "CookingMenu") || (ClosedMenu == "TanningRackMenu")
          ; Insert code/function to switch items
          RegisterForMenu(ClosedMenu)
     EndIf
EndEvent

 

 

 

Note that my mod already requires SKSE, so that isn't an impediment to what I'm trying to do here.

Edited by candlepin
Link to comment
Share on other sites

Just a thought. If on menu closes works, & if your using SKSE RegisterForMenu( "Crafting Menu") why not use OnMenuOpen? It work on any Quest Script, without any of the fuss?

Scriptname SomeQuest extends Quest

Event OnInit()

self.RegisterForMenu("Crafting Menu")

EndEvent

Event OnMenuClose(string menuName)

If(menuName == "Crafting Menu")

;... Do Something cool.

EndIf

EndEvent

Event OnMenuOpen(string menuName)

If(menuName == "Crafting Menu")

;... Do Something cool.

EndIf

EndEvent

Or am I misunderstanding something?

 

Anywho, another point of view.

 

Cheeers

 

This would work, except that it will fire on all crafting menus, not just cooking. That's why you need some method to specifically distinguish cooking stations from anything else. You might be able to couple it with a GetCurrentCrosshairRef call to achieve that, though.

Edited by foamyesque
Link to comment
Share on other sites

 

Okay. I think my approach would be to run a search quest every time a cell attaches to the player, looking for, specifically, the cooking crafting stations (by keyword, I think). Shouldn't need more than a dozen aliases, I would say. A simple script goes on those to check for activations. Another script goes on the player via an alias, for state control, and to control the inventory swaps and check for menu closes. When one of the cooking stations is activated, it calls a function in the inactive state of the player script, which flips it into the listening state for an OnMenuClose. When the OnMenuClose fires, it flips the player script back into the inactive state. In the inactive state, there's no OnMenuClose, and in the active one, the attempted state-change function is empty. You can call your swaps on entering and leaving the active state. The state change functions are very quick to execute and should prevent weird race conditions from happening (e.g. the player triggering multiple swaps by spamming the activation button).

 

An alternate means to find nearby stations would be through a cloak magic effect, but that tends to interfere with brawls and certain other events.

Thanks for your reply, foamyesque.
I'd like to avoid the cloak method. It seems pretty messy to me and would be more of a last-ditch approach if nothing else works.
I've never worked with states before, so let me see if I understand what you're recommending.
Quest 1: Adds aliases to cooking stations (and tanning racks) when a cell is loaded.
Script 1: Attached to the aliases of Quest 1, this script checks for activation. When the aliases are activated, the script calls on a function within the inactive state of Script 2 to switch states.
Quest 2: Adds an alias to the player.
Script 2: A script is attached to the player alias in Quest 2. This script controls the states and does the swapping.
The only thing the inactive state (default) does is call a function to switch to the active/listening state. Calling this function will somehow be dependent upon activation of the aliases in Quest/Script 1 (use a new global variable that is set by Script 1??).
Upon entering active/listening state, the script will swap items in the inventory (I will either call on my existing spell or just copy some of that script here). When OnMenuClose fires, it swaps the items in the inventory back and then changes the state back to the inactive state.
Is that what you are recommending? Sorry for the questions; I've only ever made one quest alias and, as I said above, I've never worked with states before. It seems fairly straightforward though.

 

 

Not quite.

 

One of the neatest things about states is that a script will listen only for events declared within that state (or in the empty state). This can be very useful to avoid things like stack dumps for inventory events, or complicated state checks inside OnUpdates, etc. It's less needed with events you need to register for, but I prefer it, particularly when you want to do a large process on a state change, since you can do the state change with effectively zero latency and allow the OnBeginState event to do the hard work. So you'd have two scripts, as follows:

Scriptname candlepinCraftingStationAlias extends ReferenceAlias

candlepinPlayerAlias Property PlayerAlias Auto

Event OnActivate(ObjectReference akActionRef)
    PlayerAlias.Listen()
EndEvent
 
scriptname candlepinPlayerAlias extends ReferenceAlias

Quest Property SearchQuest Auto

;triggers the search quest in a variety of load states
Event OnInit()
    RegisterForMenu("Crafting Menu")
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

Event OnPlayerLoadGame()
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

Event OnCellLoad()
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

;throws out any Listen() calls in incorrect states
Function Listen()
EndFunction

;Begins in the inactive state
Auto State Inactive
    Function Listen()
        GotoState("Listening")
    EndFunction
EndState

State Listening
    Event OnBeginState()
        ;do swap
    EndEvent

    Event OnMenuClose(string asMenuName)
        GotoState("Inactive")
    EndEvent

    Event OnEndState()
        ;do swap
    EndEvent
EndState

I believe this should work. If you want to include tanning stations as well as cooking stations you'll want to expand the alias count some, particularly as some mods like to go really overboard with the crafting station counts, but it won't be unmanageable. Minor correction to before: Wiki says it is OnCellLoad that works with the player, not OnCellAttach, so the code above reflects that.

Edited by foamyesque
Link to comment
Share on other sites

 

Not quite.

 

One of the neatest things about states is that a script will listen only for events declared within that state (or in the empty state). This can be very useful to avoid things like stack dumps for inventory events, or complicated state checks inside OnUpdates, etc. It's less needed with events you need to register for, but I prefer it, particularly when you want to do a large process on a state change, since you can do the state change with effectively zero latency and allow the OnBeginState event to do the hard work. So you'd have two scripts, as follows:

Scriptname candlepinCraftingStationAlias extends ReferenceAlias

candlepinPlayerAlias Property PlayerAlias Auto

Event OnActivate(ObjectReference akActionRef)
    PlayerAlias.Listen()
EndEvent
 
scriptname candlepinPlayerAlias extends ReferenceAlias

Quest Property SearchQuest Auto

;triggers the search quest in a variety of load states
Event OnInit()
    RegisterForMenu("Crafting Menu")
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

Event OnPlayerLoadGame()
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

Event OnCellLoad()
    SearchQuest.Stop()
    SearchQuest.Start()
EndEvent

;throws out any Listen() calls in incorrect states
Function Listen()
EndFunction

;Begins in the inactive state
Auto State Inactive
    Function Listen()
        GotoState("Listening")
    EndFunction
EndState

State Listening
    Event OnBeginState()
        ;do swap
    EndEvent

    Event OnMenuClose(string asMenuName)
        GotoState("Inactive")
    EndEvent

    Event OnEndState()
        ;do swap
    EndEvent
EndState

I believe this should work. If you want to include tanning stations as well as cooking stations you'll want to expand the alias count some, particularly as some mods like to go really overboard with the crafting station counts, but it won't be unmanageable. Minor correction to before: Wiki says it is OnCellLoad that works with the player, not OnCellAttach, so the code above reflects that.

 

 

Hmm. It seems like maybe my understanding is lacking somewhere (not surprising). I'll take another stab at it. Is this a more accurate description/understanding? I'd prefer to understand the code I'm using and am willing to learn
Quest 1: Adds aliases to cooking stations (and tanning racks). Also adds an alias for the player. It would be fine to use the same quest to attach all these aliases, correct?
Script 1: Attached to the cooking station/tanning rack aliases. This script checks for activation. When the cooking stations/tanning racks are activated, the script calls on a function within the inactive state of Script 2 to switch states.
Script 2: Attached to the PlayerRef alias of Quest 1. This script controls the states and does the swapping. Your excellent example script above also calls for the stopping and restarting of the quest OnInit, OnPlayerLoadGame, and OnCellLoad. This allows the aliases of script 1 to be applied to new references in the loaded area (right?). OnInit also calls RegisterForMenu(), which allows for the detection of OnMenuClose().
The inactive state described in Script 2 encodes a function to switch to the active/listening state. This function, which doesn't fire until called upon, will be triggered following OnActivate() in Script 1. Thus, when one of the crafting stations with the alias is activated, the function in the inactive state is triggered. This function, as stated above, causes the state to change to active/listening.
Upon entering active/listening state, OnBeginState is triggered. This is where I would add the script to swap items in the inventory. When OnMenuClose fires, it codes for a state change back to the inactive state. This then triggers OnEndState, where I will add the script to swap the items back.

Is that roughly correct? If you could tell me what I am misunderstanding that would be extremely helpful.

 

I also had a couple more questions about your last post. First you said:

 

If you want to include tanning stations as well as cooking stations you'll want to expand the alias count some, particularly as some mods like to go really overboard with the crafting station counts, but it won't be unmanageable.

As I said before, I've only ever done a single Alias (it was a PlayerRef Alias). I though I could just choose the different types of crafting menus from the Alias list (something like StationCookingSpit, StationCookingPot, and StationTanningRack or whatever they are called). From your comment about "expanding the alias count" though, it seems I am missing something more fundamental here.

 

My second question is about this bit of your code:

;throws out any Listen() calls in incorrect states
Function Listen()
EndFunction

Again, please forgive my ignorance, but why wouldn't that fire following the PlayerAlias.Listen() call in the first script? And if it was called, wouldn't it fail to trigger state change within the inactive state? I understand you also have the Listen() function in the inactive state, but is there something fundamental about states that gives them priority?

 

PS: Thanks again for all the help. It is much appreciated!!

Link to comment
Share on other sites

I would use two separate quests. If you have the player alias in the search quest, when the search quest restarts to refill the aliases, you'll lose all state information on any scripts attached to that quest. It shouldn't be a major issue here, since the trigger for that is unlikely to fire in any state except the inactive one, which is the default, but it's good practice.

 

Aliases can only point to one reference each. So if there's ten crafting stations, you'll need ten aliases to apply your scripts to all of them.

The concept behind a search quest is that aliases can be filled conditionally, looking for any reference that matches a set of conditions provided. By setting those aliases to be optionally filled, and by setting the conditionals up, and then repeatedly restarting the quest, you have a fast and flexible way of finding essentially anything in the game, in potentially quite large quantities.

 

In your case, you'd want two conditions, both HasKeyword, and ORd together, looking for the CraftingCookpot and CraftingTanningRack keywords. That will allow them to work on anything someone can craft food or leather at. You will also want to flag them to operate only in the loaded area. You could also prioritize finding things close to the player, but that shouldn't be necessary here. Once you have one alias all set up, with the conditions and the attached script and its property filled, you can clone it to however many you think will be needed.

 

Function lookup in states will check the current state first. If it doesn't find one, it will then check the empty state. Any function declared in any state must have a matching function declared somewhere in that script's empty state, which is why there's the empty Listen function in the empty state in my example. There's some complexity when you start working through an inheritance tree but that's not relevant here.

 

Otherwise your understanding looks good.

 

 

Quests and aliases are a really powerful tool. Figuring out how they operate is an area that repays the time investment. :smile:

Edited by foamyesque
Link to comment
Share on other sites

Ok, I've tried this and it seems I'm doing something wrong. Here is how I did it.

 

I made two quests; CP_MI_CraftingSwap_CraftingAlias and CP_MI_CraftingSwap_PlayerAlias.

 

Attached to the CP_MI_CraftingSwap_CraftingAlias quest I have 20 aliases for crafting stations. Here is what the aliases look like:

 

 

 

w4Nvwza.jpg

 

 

 

 

Attached to each alias I attached the following script:

 

 

Scriptname CP_MI_CraftingAliasScript extends ReferenceAlias  
{This script is attached to specific crafting stations and is used to trigger an inventory swap (performed by another script) whenever one of these crafting stations is acivated.}

CP_PlayerAliasScript Property PlayerAlias Auto

Event OnActivate(ObjectReference akActionRef)
	PlayerAlias.Listen()
EndEvent

 

 

 

Attached to the CP_MI_CraftingSwap_PlayerAlias quest I have an alias for the player. Here is what the alias looks like:

 

 

 

 

6YJ5Lx9.jpg

 

 

 

And here is what the script attached to the player alias looks like:

 

 

 

Scriptname CP_PlayerAliasScript extends ReferenceAlias  
{This script swaps the player's inventory to vanilla versions of items when certain crafting menus are open.}

Quest Property CP_MI_CraftingSwap_CraftingAlias Auto
ObjectReference Property PlayerRef Auto
GlobalVariable Property CP_GV_swap auto
Spell Property CP_MI_Spell Auto
Bool Property SwapAtStation = False Auto

Event OnInit()  			;triggers the search quest in a variety of load states
	RegisterForMenu("Crafting Menu")
	CP_MI_CraftingSwap_CraftingAlias.Stop()
	CP_MI_CraftingSwap_CraftingAlias.Start()
EndEvent

Event OnPlayerLoadGame()
	CP_MI_CraftingSwap_CraftingAlias.Stop()
	CP_MI_CraftingSwap_CraftingAlias.Start()
EndEvent

Event OnCellLoad()
	CP_MI_CraftingSwap_CraftingAlias.Stop()
	CP_MI_CraftingSwap_CraftingAlias.Start()
EndEvent

Function Listen()		;throws out any Listen() calls in incorrect states
EndFunction

Auto State Inactive		;Begins in the inactive state
	Function Listen()
		GotoState("Listening")
	EndFunction
EndState

State Listening
	Event OnBeginState() 
		If (CP_GV_swap.GetValue() == 1)
			; no nothing - items should already be vanilla version
		ElseIf (CP_GV_swap.GetValue() == 0)
			SwapAtStation = True
                        ; swap items via the spell - GV of 0 = ingredient form, see spell
			CP_MI_Spell.Cast(PlayerRef, PlayerRef)
		EndIf
	EndEvent

	Event OnMenuClose(string asMenuName)
		GotoState("Inactive")
	EndEvent

	Event OnEndState()
		;do swap
		If (SwapAtStation == False)
			; no nothing - items should already be vanilla version
		ElseIf (SwapAtStation == True)
			; swap items back to ingredient form by spell
			CP_MI_Spell.Cast(PlayerRef, PlayerRef)
		EndIf
	EndEvent
EndState

 

 

 

I double checked to make sure all Properties were properly filled. Also, both quests are Start Game Enabled, but still nothing.

 

Help would be very much appreciated!

Edited by candlepin
Link to comment
Share on other sites

Easiest solution would be to setup a hotkey that when pressed and the current crosshair target is a valid workstation, run your swap spell, then activate the workstation so that the player does not need to press two keys. Use a bool toggle to indicate that the station is the correct type and use OnMenuClose to swap the items again. All that on a single script on either a management quest or player alias.

 

Here is an incomplete example:

 

Int Property MyKey Auto
{Set to desired DXScanCode value}
ObjectReference Ref
FormList Property MyList Auto
{Set to a formlist containing all workstations you want to work with - must be furniture type}
Form Base
Actor PlayerRef
Bool ValidStation

Event OnKeyDown(Int KeyCode)
	If !PlayerRef
		PlayerRef = Game.GetPlayer()
	EndIf
	If KeyCode == MyKey
		Ref = Game.GetCurrentCrosshairRef()
		If 	Ref != None
			Base = Ref.GetBaseObject()
			If Base.GetType() == 40 ;is our target a furniture item
				If MyList.HasForm(Base) ;our target is a desired crafting station
					ValidStation = true
					RegisterForMenu("Crafting Menu")
					;swap items
					Ref.Activate(PlayerRef)
				EndIf
			EndIf
		EndIf
	EndIf
EndEvent

Event OnMenuClose(String MenuName)
	If MenuName == "Crafting Menu" && ValidStation == true
		ValidStation = false
		;swap items back
		UnRegisterForMenu()
	EndIf
EndEvent

 

 

Obviously, you can use your MCM menu to let the player decide what hot key to use and whether or not it automatically activates the workstation or if they need to press the actual activation key after the swap. But that can always be added later.

Link to comment
Share on other sites

Easiest solution would be to setup a hotkey that when pressed and the current crosshair target is a valid workstation, run your swap spell, then activate the workstation so that the player does not need to press two keys. Use a bool toggle to indicate that the station is the correct type and use OnMenuClose to swap the items again. All that on a single script on either a management quest or player alias.

 

Here is an incomplete example:

 

Int Property MyKey Auto
{Set to desired DXScanCode value}
ObjectReference Ref
FormList Property MyList Auto
{Set to a formlist containing all workstations you want to work with - must be furniture type}
Form Base
Actor PlayerRef
Bool ValidStation

Event OnKeyDown(Int KeyCode)
	If !PlayerRef
		PlayerRef = Game.GetPlayer()
	EndIf
	If KeyCode == MyKey
		Ref = Game.GetCurrentCrosshairRef()
		If 	Ref != None
			Base = Ref.GetBaseObject()
			If Base.GetType() == 40 ;is our target a furniture item
				If MyList.HasForm(Base) ;our target is a desired crafting station
					ValidStation = true
					RegisterForMenu("Crafting Menu")
					;swap items
					Ref.Activate(PlayerRef)
				EndIf
			EndIf
		EndIf
	EndIf
EndEvent

Event OnMenuClose(String MenuName)
	If MenuName == "Crafting Menu" && ValidStation == true
		ValidStation = false
		;swap items back
		UnRegisterForMenu()
	EndIf
EndEvent

 

 

Obviously, you can use your MCM menu to let the player decide what hot key to use and whether or not it automatically activates the workstation or if they need to press the actual activation key after the swap. But that can always be added later.

 

Thanks for the script, IsharaMeradin. I'd really prefer that this is something that happens automatically though (no need to press a hotkey). I think it would get kind of annoying for someone who does a lot of crafting to have to remember to do this every time (or to have to exit a menu, press the hotkey, and re-enter the menu because they forgot to).

Link to comment
Share on other sites

  • Recently Browsing   0 members

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