Jump to content

[LE] Script bug and optimization questions


Recommended Posts

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 by smashballsx88
Link to comment
Share on other sites

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

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 by smashballsx88
Link to comment
Share on other sites

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

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 by xWilburCobbx
Link to comment
Share on other sites

 

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

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 by maxarturo
Link to comment
Share on other sites

 

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

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

  • Recently Browsing   0 members

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