candlepin Posted June 23, 2017 Author Share Posted June 23, 2017 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 More sharing options...
PeterMartyr Posted June 23, 2017 Share Posted June 23, 2017 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 More sharing options...
candlepin Posted June 23, 2017 Author Share Posted June 23, 2017 (edited) 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 June 23, 2017 by candlepin Link to comment Share on other sites More sharing options...
foamyesque Posted June 23, 2017 Share Posted June 23, 2017 (edited) 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 QuestEvent OnInit() self.RegisterForMenu("Crafting Menu")EndEventEvent OnMenuClose(string menuName) If(menuName == "Crafting Menu") ;... Do Something cool. EndIfEndEventEvent OnMenuOpen(string menuName) If(menuName == "Crafting Menu") ;... Do Something cool. EndIfEndEventOr 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 June 23, 2017 by foamyesque Link to comment Share on other sites More sharing options...
foamyesque Posted June 23, 2017 Share Posted June 23, 2017 (edited) 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 June 23, 2017 by foamyesque Link to comment Share on other sites More sharing options...
candlepin Posted June 23, 2017 Author Share Posted June 23, 2017 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 More sharing options...
foamyesque Posted June 23, 2017 Share Posted June 23, 2017 (edited) 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 June 23, 2017 by foamyesque Link to comment Share on other sites More sharing options...
candlepin Posted June 29, 2017 Author Share Posted June 29, 2017 (edited) 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: 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: 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 June 29, 2017 by candlepin Link to comment Share on other sites More sharing options...
IsharaMeradin Posted June 29, 2017 Share Posted June 29, 2017 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 More sharing options...
candlepin Posted June 29, 2017 Author Share Posted June 29, 2017 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 More sharing options...
Recommended Posts