xWilburCobbx Posted August 2, 2020 Author Share Posted August 2, 2020 (edited) As always, I am very happy that you took the time to help me out. I know I tend to beat around the bush, I apologize. If you get to the last paragraph, I explain what the bug actually was. I was trying to include details just encase there is something I should know about those things that might have been the root cause. I got my answer for that one. This is essentially what testiger2 was suggesting, except with an int instead of a bool. I would probably just use a bool if my value could only be 1 of 2 things. I think I ultimately like the idea maxarturo had. Setting a state seems messy, but functionally faster. Instead of telling the script to do value assignments and to assess those values, I can just tell it to go to a state. It will automatically ignore anything not in the state, essentially disabling the OnActivate() event listener. When I finally get back on the computer, I am gonna show off my finished version and you guys can critique it. Edited August 2, 2020 by smashballsx88 Link to comment Share on other sites More sharing options...
NexusComa2 Posted August 2, 2020 Share Posted August 2, 2020 A Fist time Flag is a essential part of in depth programing. That concept is taught to you in the 1st year of a programing degree. Link to comment Share on other sites More sharing options...
ReDragon2013 Posted August 2, 2020 Share Posted August 2, 2020 There is only a part your scripts. Maybe something like that, I used your approach with disable/enable. AAAxWCxINVxPurchaseScript Scriptname AAAxWCxINVxPurchaseScript extends ObjectReference {Used to buy a property to generate income.} ; https://forums.nexusmods.com/index.php?/topic/8967343-script-bug-and-optimization-questions/ GlobalVariable PROPERTY gTotalPaid auto GlobalVariable PROPERTY gCntTotal auto GlobalVariable PROPERTY gCnt auto ; external quest scripts AAAxWCxINVxAliasControl PROPERTY AliasQS auto AAAxWCxINVxIncomeScript PROPERTY IncQS auto Quest PROPERTY qIncByInt auto Message PROPERTY mMenuP auto Message PROPERTY mMenuFailP auto MiscObject PROPERTY mCoin auto ; Gold001 MusicType PROPERTY muBuy auto Int PROPERTY iCost auto ; [default=0] Int PROPERTY ModObjID auto ; -- EVENT -- EVENT OnActivate(ObjectReference akActionRef) ; checks to see if the player has the money and shows the menu IF (akActionRef == Game.GetPlayer() as ObjectReference) ELSE RETURN ; - STOP - /0 not player activated for whatever reason ENDIF ;--------------------- self.DisableNoWait() ; to prevent activation spam and related bugs IF (akActionRef.GetGoldAmount() < iCost) mMenuFailP.Show(iCost) ; prompt fail message self.Enable() RETURN ; - STOP - /1 player has not enough money ENDIF ;--------------------- IF BuyMenu(akActionRef) ELSE self.Enable() RETURN ; - STOP - /2 player will not buy ENDIF ;--------------------- muBuy.Add() ; play sound effect myF_REM(1) ; self.GetLinkedRef() myF_REM(2) ; self.GetLinkedRef().GetLinkedRef() myF_REM(3) ; self.GetLinkedRef().GetLinkedRef().GetLinkedRef() ; controls the alias from a remote script AliasQS.AliasCtrl() ; run function "AliasCtrl()" within external script "AliasQS" self.Delete() ; mark scripted object for delete ENDEVENT ; -- FUNCTIONs -- 2 ;---------------------- FUNCTION myF_REM(Int i) ; internal helper ;---------------------- ; Fade/Delete custom sign objects objectReference oRef = self.GetNthLinkedRef(i) ; get a linkedRef to remove them IF ( oRef ) oRef.Disable(TRUE) ; disable the object by fading out oRef.Delete() ; mark this object for delete ENDIF ENDFUNCTION ;------------------------------------------------- Bool FUNCTION BuyMenu(ObjectReference akActionRef) ;------------------------------------------------- IF (mMenuP.Show(iCost) < 1) ; change button position to prevent ESC pressed error !!! ; https://www.creationkit.com/index.php?title=Show_-_Message Return False ; no buy ENDIF ;--------- ; (1) take players money akActionRef.RemoveItem(mCoin, iCost) ; decrease players gold amount gTotalPaid.Mod(iCost as Float) ; update global ; https://www.creationkit.com/index.php?title=ModObjectiveGlobal_-_Quest ; (2) adds 1 to the objective qIncByInt.ModObjectiveGlobal(1.0, gCnt, ModObjID, gCntTotal.GetValue(), TRUE, TRUE, TRUE) ; (3) runs an income update from a remote script IncQS.UpdStatVal() ; run function "UpdStatVal()" within external script "IncQS" IF qIncByInt.IsRunning() ; quest is already running ELSE qIncByInt.Start() ; start the income quest (gives the first stage), if disabled ; "Quests started this way while the game is in menu mode will not initialize until the menu has been exited." ENDIF Debug.Notification("Property purchased!") Return TRUE ; buying successful ENDFUNCTION Link to comment Share on other sites More sharing options...
xWilburCobbx Posted August 2, 2020 Author Share Posted August 2, 2020 (edited) Well ReDragon2013 gave me a new idea, funny enough it's actually related to a completely different issue with the script, the way you re-did my delete function with "myF_REM(Int i = 0)" got me thinking about it. I didn't remember I could only do 1 Linked Reference per keyword on an object, so I changed the system to dynamically erase every object in a linked chain. "change button position to prevent ESC pressed error" I have never heard of this one before, when I go into the game I can't use ESC to exit, and when I turn my controller on it forces me to pick one of the options. Is this related to SkyUI? I have been testing this mod on the vanilla version, trying to keep it away from dependencies since the mod is just too simple. Also, you really go out of your way to re-write the script in your own technique, that's pretty awesome. What I notice is you really like to use IF statements then manually force the script to stop with RETURN. Why is that? Do you just like to keep things on 1 level for read-ability? maxarturo I decided to use your way because it makes me feel like my script has more stable security against multiple activations. Though I don't know if it actually does, or if using a bool/int does the job equally well. testiger2 & NexusComa2 I am no programmer! So I don't understand concepts like "Fist time Flag" yet. I am scratching the surface on learning more advanced methods of scripting, so I appreciate the knowledge. So, this is what I have come up with, it does 1 new operation but it does essentially the same thing. The only thing that I am unsure of is using a while loop in "Function DeleteRef()". I heard this can bloat saves, but is it fine if its just fired for a brief amount of time? I feel like uninstalling the mod while its actively doing this in a save is a highly unlikely phenomenon. Scriptname AAAxWCxINVxPurchaseScript extends ObjectReference {Used to buy a property to generate income.} ;---------------- ;Local properties ;---------------- bool property IsInn = false auto {Increases price by 50%} GlobalVariable Property gCnt auto {Objective counter} GlobalVariable Property gCntTotal auto {Objective counter cap for completion} GlobalVariable Property gBasePrice auto {Starting price tier} int property ModObjID auto {ID of objective to count} AAAxWCxINVxAliasControl property AliasQS auto {Alias to control} ;-------------------- ;Universal properties ;-------------------- Actor property PlayerREF auto float fCost GlobalVariable Property gTotalPaid auto MiscObject Property mCoin auto Message property mMenuP auto Message property mMenuFailP auto MusicType property muBuy auto Quest property qIncByInt auto AAAxWCxINVxIncomeScript property IncQS auto ;------ ;States ;------ Auto State Enabled ;Debug--allows the player to activate the trigger Event OnActivate(ObjectReference akActionRef) ;Checks to see if the player has the money and changes state fCost = gBasePrice.GetValue() if IsInn == true ;Calculates the cost of the property fCost *= 1.5 endif if PlayerREF.GetGoldAmount() >= fCost GotoState("Disabled") else mMenuFailP.Show(fCost) endif EndEvent endState State Disabled ;Debug--stops the player from activating trigger Event OnActivate(ObjectReference akActionRef) ;Do nothing EndEvent Event OnBeginState() ;Shows the menu BuyMenu() endEvent endState ;--------- ;Functions ;--------- Function BuyMenu(int aiButton = 0) ;Takes the player's money/adds 1 to the objective/runs an income update from a remote script aiButton = mMenuP.Show(fCost) if aiButton == 0 ;Yes PlayerREF.RemoveItem(mCoin, fCost as int) gTotalPaid.Mod(fCost) qIncByInt.ModObjectiveGlobal(1, gCnt, ModObjID, gCntTotal.GetValue(), true, true) IncQS.UpdStatVal() if qIncByInt.IsRunning() == False ;Starts the income quest if its disabled qIncByInt.Start() endif Debug.Notification("Property purchased!") muBuy.Add() ;Plays sound effect DeleteRef(self.GetLinkedRef(), self.countLinkedRefChain()) else ;No GotoState("Enabled") endif endFunction Function DeleteRef(ObjectReference LRef = none, int iLCnt = 0) ;Fades/Deletes any custom sign objects and controls the alias from a remote script self.Disable() ;Disables initially for AliasCtrl AliasQS.AliasCtrl() while iLCnt > 1 ;Loops till all linked references are erased from the chain LRef.Disable(true) LRef.Delete() LRef = LRef.GetLinkedRef() iLCnt -= 1 endWhile Self.Delete() EndFunction Edited August 3, 2020 by smashballsx88 Link to comment Share on other sites More sharing options...
foamyesque Posted August 5, 2020 Share Posted August 5, 2020 Don't need to explicitly declare the empty activation in the Disabled state. Undeclared events fall back to their defaults, which is an empty block anyway. However, my suggestion would be to go to the Disabled state immediately on activation, regardless of whether or not the player has the gold, etc. Do all those checks there, and then either display the menu or go back to the Active state. This gives the smallest possible window for malformed input and should functionally guarantee that all the code finishes running, in either path, before a new trigger can start it again. For clarity I'd probably rename them from Active -> Waiting and Disabled -> Active. Link to comment Share on other sites More sharing options...
xWilburCobbx Posted August 7, 2020 Author Share Posted August 7, 2020 (edited) Don't need to explicitly declare the empty activation in the Disabled state. Undeclared events fall back to their defaults, which is an empty block anyway. However, my suggestion would be to go to the Disabled state immediately on activation, regardless of whether or not the player has the gold, etc. Do all those checks there, and then either display the menu or go back to the Active state. This gives the smallest possible window for malformed input and should functionally guarantee that all the code finishes running, in either path, before a new trigger can start it again. For clarity I'd probably rename them from Active -> Waiting and Disabled -> Active.Noted. Thanks! I changed it up, I wasn't aware changing the state didn't end the current function abruptly. Scriptname AAAxWCxINVxPurchaseScript extends ObjectReference {Used to buy a property to generate income.} ;---------------- ;Local properties ;---------------- bool property IsInn = false auto {Increases price by 50%} GlobalVariable Property gCnt auto {Objective counter} GlobalVariable Property gCntTotal auto {Objective counter cap for completion} GlobalVariable Property gBasePrice auto {Starting price tier} int property ModObjID auto {ID of objective to count} AAAxWCxINVxAliasControl property AliasQS auto {Alias to control} ;-------------------- ;Universal properties ;-------------------- Actor property PlayerREF auto float fCost GlobalVariable Property gTotalPaid auto MiscObject Property mCoin auto Message property mMenuP auto Message property mMenuFailP auto MusicType property muBuy auto Quest property qIncByInt auto AAAxWCxINVxIncomeScript property IncQS auto ;------ ;States ;------ Auto State Standby ;Debug--allows the player to activate the trigger Event OnActivate(ObjectReference akActionRef) ;Checks to see if the player has the money and changes state GotoState("") fCost = gBasePrice.GetValue() if IsInn == true ;Calculates the cost of the property fCost *= 1.5 endif if PlayerREF.GetGoldAmount() >= fCost BuyMenu() else mMenuFailP.Show(fCost) GotoState("Standby") endif EndEvent endState ;--------- ;Functions ;--------- Event OnActivate(ObjectReference akActionRef) ;Debug--stops the player from activating trigger ;Do nothing EndEvent Function BuyMenu(int aiButton = 0) ;Takes the player's money/adds 1 to the objective/runs an income update from a remote script aiButton = mMenuP.Show(fCost) if aiButton == 0 ;Yes PlayerREF.RemoveItem(mCoin, fCost as int) gTotalPaid.Mod(fCost) qIncByInt.ModObjectiveGlobal(1, gCnt, ModObjID, gCntTotal.GetValue(), true, true) IncQS.UpdStatVal() if qIncByInt.IsRunning() == False ;Starts the income quest if its disabled qIncByInt.Start() endif Debug.Notification("Property purchased!") muBuy.Add() ;Plays sound effect DeleteRef(self.GetLinkedRef(), self.countLinkedRefChain()) else ;No GotoState("Standby") endif endFunction Function DeleteRef(ObjectReference LRef = none, int iLCnt = 0) ;Fades/Deletes any custom sign objects and controls the alias from a remote script self.Disable() ;Disables initially for AliasCtrl AliasQS.AliasCtrl() while iLCnt > 1 ;Loops till all linked references are erased from the chain LRef.Disable(true) LRef.Delete() LRef = LRef.GetLinkedRef() iLCnt -= 1 endWhile Self.Delete() EndFunction Which brings me to my next consistently recurring train of thought. What will force a script to drop what it's doing, besides "return"? Would this work? Or will it only read the current running function before before having issues? Scriptname NewScript extends ObjectReference Auto State Waiting Event OnActivate(ObjectReference akActionRef) GoToState("") MyFunc() endEvent Function MyFunc() MyFunc2() endFunction endState Event OnActivate(ObjectReference akActionRef) ;Do nothing or something else endEvent Function MyFunc() ;Do nothing or something else endFunction Function MyFunc2() ;Do stuff GoToState("Waiting") endFunction I have a feeling this will fail to run "MyFunc()" inside the "Waiting" state, and will run the one inside the empty state instead. That would makes since to me, but let's make sure I am not going insane here. Edited August 7, 2020 by xWilburCobbx Link to comment Share on other sites More sharing options...
foamyesque Posted August 7, 2020 Share Posted August 7, 2020 Don't need to explicitly declare the empty activation in the Disabled state. Undeclared events fall back to their defaults, which is an empty block anyway. However, my suggestion would be to go to the Disabled state immediately on activation, regardless of whether or not the player has the gold, etc. Do all those checks there, and then either display the menu or go back to the Active state. This gives the smallest possible window for malformed input and should functionally guarantee that all the code finishes running, in either path, before a new trigger can start it again. For clarity I'd probably rename them from Active -> Waiting and Disabled -> Active.Noted. Thanks! I changed it up, I wasn't aware changing the state didn't end the current function abruptly. Scriptname AAAxWCxINVxPurchaseScript extends ObjectReference {Used to buy a property to generate income.} ;---------------- ;Local properties ;---------------- bool property IsInn = false auto {Increases price by 50%} GlobalVariable Property gCnt auto {Objective counter} GlobalVariable Property gCntTotal auto {Objective counter cap for completion} GlobalVariable Property gBasePrice auto {Starting price tier} int property ModObjID auto {ID of objective to count} AAAxWCxINVxAliasControl property AliasQS auto {Alias to control} ;-------------------- ;Universal properties ;-------------------- Actor property PlayerREF auto float fCost GlobalVariable Property gTotalPaid auto MiscObject Property mCoin auto Message property mMenuP auto Message property mMenuFailP auto MusicType property muBuy auto Quest property qIncByInt auto AAAxWCxINVxIncomeScript property IncQS auto ;------ ;States ;------ Auto State Standby ;Debug--allows the player to activate the trigger Event OnActivate(ObjectReference akActionRef) ;Checks to see if the player has the money and changes state GotoState("") fCost = gBasePrice.GetValue() if IsInn == true ;Calculates the cost of the property fCost *= 1.5 endif if PlayerREF.GetGoldAmount() >= fCost BuyMenu() else mMenuFailP.Show(fCost) GotoState("Standby") endif EndEvent endState ;--------- ;Functions ;--------- Event OnActivate(ObjectReference akActionRef) ;Debug--stops the player from activating trigger ;Do nothing EndEvent Function BuyMenu(int aiButton = 0) ;Takes the player's money/adds 1 to the objective/runs an income update from a remote script aiButton = mMenuP.Show(fCost) if aiButton == 0 ;Yes PlayerREF.RemoveItem(mCoin, fCost as int) gTotalPaid.Mod(fCost) qIncByInt.ModObjectiveGlobal(1, gCnt, ModObjID, gCntTotal.GetValue(), true, true) IncQS.UpdStatVal() if qIncByInt.IsRunning() == False ;Starts the income quest if its disabled qIncByInt.Start() endif Debug.Notification("Property purchased!") muBuy.Add() ;Plays sound effect DeleteRef(self.GetLinkedRef(), self.countLinkedRefChain()) else ;No GotoState("Standby") endif endFunction Function DeleteRef(ObjectReference LRef = none, int iLCnt = 0) ;Fades/Deletes any custom sign objects and controls the alias from a remote script self.Disable() ;Disables initially for AliasCtrl AliasQS.AliasCtrl() while iLCnt > 1 ;Loops till all linked references are erased from the chain LRef.Disable(true) LRef.Delete() LRef = LRef.GetLinkedRef() iLCnt -= 1 endWhile Self.Delete() EndFunction Which brings me to my next consistently recurring train of thought. What will force a script to drop what it's doing, besides "return"? Would this work? Or will it only read the current running function before before having issues? Scriptname NewScript extends ObjectReference Auto State Waiting Event OnActivate(ObjectReference akActionRef) GoToState("") MyFunc() endEvent Function MyFunc() MyFunc2() endFunction endState Event OnActivate(ObjectReference akActionRef) ;Do nothing or something else endEvent Function MyFunc() ;Do nothing or something else endFunction Function MyFunc2() ;Do stuff GoToState("Waiting") endFunction I have a feeling this will fail to run "MyFunc()" inside the "Waiting" state, and will run the one inside the empty state instead. That would makes since to me, but let's make sure I am not going insane here. What will happen there is, assuming it starts in the Waiting state and something triggers the OnActivate Event: The OnActivate event declared in the Waiting state starts processing. The first thing it does is put the script into the empty state, so any further function calls or event triggers will use the versions declared in the empty state. Then it proceeds to call MyFunct(), which will use the empty state version, and therefore not call MyFunct2(), so the script doesn't get put back into the Waiting state. Link to comment Share on other sites More sharing options...
maxarturo Posted August 7, 2020 Share Posted August 7, 2020 (edited) foamyesque and ReDragon2013 are more than capable to guide you through this. I just want to add that, although 'States' are one of the most reliable and fastest functions, they need an extensive care, you need to be very careful with them. One wrong 'State Call' or one simple 'Typo' can F**** UP EVERYTHING !!l. Plus the most important : TYPOS on the 'State' won't trigger a "Compile Error", so you can have a fully functional without errors script that in-game is not working correctly and all this because your 'State' has a 'Typo'. It happened to me with the mod i recently released. A few days before publishing it, i made a minor addition to one simple script and didn't test it, there was no need for it since it's just a simple script, but in-game the script wasn't working correctly. All of this from some stupid cookie crumbs on my keyboard !!, instead of writing "GoToState("WaitingPlayer")" i wrote "GoToState("WaitingPlayr")" without an "E". Edited August 7, 2020 by maxarturo Link to comment Share on other sites More sharing options...
xWilburCobbx Posted August 7, 2020 Author Share Posted August 7, 2020 foamyesque and ReDragon2013 are more than capable to guide you through this. I just want to add that, although 'States' are one of the most reliable and fastest functions, they need an extensive care, you need to be very careful with them. One wrong 'State Call' or one simple 'Typo' can F**** UP EVERYTHING !!l.Plus the most important : TYPOS on the 'State' won't trigger a "Compile Error", so you can have a fully functional without errors script that in-game is not working correctly and all this because your 'State' has a 'Typo'. It happened to me with the mod i recently released. A few days before publishing it, i made a minor addition to one simple script and didn't test it, there was no need for it since it's just a simple script, but in-game the script wasn't working correctly. All of this from some stupid cookie crumbs on my keyboard !!, instead of writing "GoToState("WaitingPlayer")" i wrote "GoToState("WaitingPlayr")" without an "E". You aren't kidding! I typo'd "GotoState("Standby")" and wrote "Standb" I had a bit of a hard time understanding why my trigger wasn't usable after I clicked away the fail message. I opened the .psc back up and immediately spotted it, luckily my script is small! Link to comment Share on other sites More sharing options...
xWilburCobbx Posted August 7, 2020 Author Share Posted August 7, 2020 What will happen there is, assuming it starts in the Waiting state and something triggers the OnActivate Event: The OnActivate event declared in the Waiting state starts processing. The first thing it does is put the script into the empty state, so any further function calls or event triggers will use the versions declared in the empty state. Then it proceeds to call MyFunct(), which will use the empty state version, and therefore not call MyFunct2(), so the script doesn't get put back into the Waiting state.Yeah, I figured as much, thanks for clarifying. Its super useful to know that once a function is ran, no matter what state the script changes to while running the function, it wont stop and it will only change for future runs. Link to comment Share on other sites More sharing options...
Recommended Posts