init at game load and init at game restart

Post » Mon Jun 18, 2012 8:52 pm

Because OnInit () only runs once and SKSE has not yet been release with links to the CK,
I'm starting to need OBSE's OnGameLoaded functions.

This is the closest I've come to emulating this. Is there a better way?
Attached is the main Quest Script and the Player Script for my Eat, Drink and Sleep mod.
However, I also use this code in 4 other mods.
Spoiler
Scriptname kuEASQSMain extends Quest  Function debugTrace (String debugMessage, Int debugLevel = 1, Bool notify = False)	If debugMode && debugMode >= debugLevel		If notify			Debug.Notification ("!kuEASQSMain." + debugMessage)		EndIf		Debug.Trace ("!kuEASQSMain." + debugMessage)	EndIfEndFunction;these are version-control variables and properties.;best that these are not changed or removed.;mod-specific variables are listed below - closer to the initialisation functions for ease of editing.Bool Property started AutoFloat versionFloat lastVersionFloat lastOnUpdateTimeUpdateBool Property resetNow AutoInt Property debugMode AutoInt Property testCase AutoBool stoppedThenStartedFloat updateIntervalBool Property uninstallNow AutoInt menuModeBool initedMQ00QuestScript Property questMQ00 Auto;set this property to point to MQ00Event OnInit ()	;because OnInit () executes twice the first time around: http://www.creationkit.com/OnInit.html,	;a variable is set to prevent it doing anything the 2nd time even if what it does is to register an update.	If !inited		inited = True		Int oldDebugMode = debugMode		;show debugs in the log during initialisations. the current debugMode is saved.		;and the debugMode set high, so that debugs are logged.		debugMode = 10		debugTrace ("OnInit")		RegisterForSingleUpdate (1)		;restore the previous debugMode.		debugMode = oldDebugMode	EndIfEndEventEvent OnMenuMode (Int id = 1)	If menuMode != id		menuMode = id		debugTrace ("OnMenuMode " + menuMode)	EndIfEndEventEvent OnUpdate ()	;set version and update interval        ;in MQ00 Stage 5, it is suggested that some things shouldn't start at Start Game Enabled.        ;so I simply, check for this and not do anycode unless MQ00 is at stage 5 and over.	version = 0.53	updateInterval = 1	If questMQ00.GetStage () >= 5		Float timeNow = Utility.GetCurrentRealTime ()		If !menuMode && Math.abs (timeNow - lastOnUpdateTimeUpdate) > 60			;if current number of seconds is > 1 minute from the last recorded second in the game, it's			;likely that the game was reloaded.			initGameLoaded ()		ElseIf version != lastVersion || resetNow			;there is a new version. or the player manually reset the mod by typing in the console:			;SETPQV  RESETNOW TRUE			initGameLoaded ()		ElseIf uninstallNow			debugTrace ("OnUpdate uninstall")			uninstallNow = False			uninstall ()			debugTrace ("uninstalled", 0, True)			Stop ()		Else			;tick () is the main update function.			;add your code in that function instead of here, to keep this starting block free of			;mod-specific code.			tick ()			;register only for a single update.			;if RegisterForUpdate () is used, and the mod is deactivated, the Event manager will keep			;send OnUpdate () Event to the deactivated mod. problems will occur.			;with RegisterForSingleUpdate (), if the mod is deactivated, the Event manager will fail			;only once.			RegisterForSingleUpdate (updateInterval)		EndIf		;save the current number of seconds since game-start.		lastOnUpdateTimeUpdate = timeNow		menuMode = 0	Else		RegisterForSingleUpdate (updateInterval)	EndIfEndEventFunction initGameLoaded ()	Int oldDebugMode = debugMode	;show debugs in the log during initialisations.	debugMode = 10	Bool newReset	If version != lastVersion || !started || resetNow		If lastVersion && !stoppedThenStarted			;if the mod was previously installed (i.e. lastVersion is set), then reset the mod			;calling Stop () and Start ().			;because aliases are only ever filled at OnInit () Event, this is needed in case the			;updates adds or removes new aliases.			debugTrace ("initGameLoaded reset from v" + lastVersion + " to v" + version)			;do uninstall procedure			uninstall (True)			inited = False			UnregisterForUpdate ()			Stop ()			Utility.Wait (1)			;Start () will reinitiate the OnInit () Event.			Start ()			stoppedThenStarted = True			newReset = True		Else			;this is a new mod activation or a player initiated reset.			debugTrace ("initGameLoaded init v" + version)			stoppedThenStarted = False			lastVersion = version			resetNow = False			initNewVersion ()			started = True		EndIf	EndIf	If !newReset		;if the game wasn't reset above (i.e. Stop () and Start () wasn't called), then this		;must be a normal game load.		debugTrace ("initGameLoaded loaded v" + version)		initNewGameLoad ()		;RegisterForSingleUpdate () needed here because		RegisterForSingleUpdate (1)	EndIf	debugMode = oldDebugModeEndFunctionFunction initNewVersion ()	If !started		;initialise user customisable properties here.		;checking started variable ensures that any updates to the mod will not change user customisations.		userEndureMaxHours = 12		userEndureMaxLevel = 25	EndIf	;do other initialisations here	autoEatQuest.initNewVersion ()	eatQuest.initNewVersion ()	sleepQuest.initNewVersion ()EndFunctionFunction initNewGameLoad ()	;initialise any game-load specific stuff here.	autoEatQuest.initNewGameLoad ()	eatQuest.initNewGameLoad ()	sleepQuest.initNewGameLoad ()EndFunctionFunction uninstall (Bool resetting = False)	debugTrace ("uninstall")	eatQuest.uninstall ()	sleepQuest.uninstall ()	aliasesQuest.UnregisterForUpdate ()	aliasesQuest.Stop ()	If !resetting		uninstallMessage.Show ()	EndIfEndFunction;listed here are the mod-specific variables.;it's closer to the initialisation functions for ease of code editing.kuEASAutoEatQS Property autoEatQuest AutokuEASQSEat Property eatQuest AutokuEASQSSleep Property sleepQuest AutoMessage Property uninstallMessage AutoFloat Property userEndureMaxHours AutoFloat Property userEndureMaxLevel AutoFloat Property userEndureEffectsFactor AutoQuest Property aliasesQuest AutoFunction tick ()	;this is the main interval code.	autoEatQuest.tick ()	eatQuest.tick ()	sleepQuest.tick ()	If testCase		doTestCase (testCase)		testCase = 0	EndIfEndFunctionFunction doTestCase (Int testCaseToDo)	;if i need to do any "unit tests", i do it with this:	;SETPQV questname testCase testnumber	Int oldDebugMode = debugMode	debugMode = 10	debugTrace ("doTest " + testCaseToDo)	If testCaseToDo == 1		autoEatQuest.populateFoodRefs ()	EndIf	Debug.Notification ("!kuEASAutoEatQS.doTestCase " + testCaseToDo + " done.")	debugMode = oldDebugModeEndFunction

In another Script attached to a Player Alias:
Spoiler
Scriptname kuEASPlayerS extends ReferenceAlias{kuertee eat and sleep player script}kuEASQSEat Property eatQuest AutoEvent OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)        ;;eating from inventory - a sample code specific to the mod	;eatQuest.debugTrace ("kuEASPlayerS.OnItemRemoved " + akBaseItem + ", " + aiItemCount)	;FormList foodList = eatQuest.foodList	;FormList drinkList = eatQuest.drinkList	;If foodList.HasForm (akBaseItem) || drinkList.HasForm (akBaseItem)	;	If !akItemReference && !akDestContainer	;		eatQuest.eatFood (akBaseItem, aiItemCount)	;	EndIf	;EndIf	checkMenuMode ()EndEventFunction checkMenuMode (Int menuModeId = 1)        ;sets menumode to 1 in the main quest if player inventory changes.        ;this prevents initGameLoaded () from executing if the player stays in MenuMode for more than 60 seconds.	kuEASQSMain mainQuest = GetOwningQuest () as kuEASQSMain	If Utility.IsInMenuMode ()		mainQuest.OnMenuMode (menuModeId)	EndIfEndFunctionEvent OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)	checkMenuMode ()EndEventEvent OnMagicEffectApply(ObjectReference akCaster, MagicEffect akEffect)	checkMenuMode ()EndEventEvent OnObjectEquipped(Form akBaseObject, ObjectReference akReference)	checkMenuMode ()EndEventEvent OnObjectUnequipped(Form akBaseObject, ObjectReference akReference)	checkMenuMode ()EndEvent

Is there a better way? Have I missed something in the CK?

This new version is 99% accurate in determining these events:
  • first mod activation calls initNewVersion ()
  • every game load calls initNewGameLoad()
  • user-triggered uninstallation via SETPQV calls uninstall ()
  • when a new version is activated over the old version, it calls uninstall (), initNewVersion (), initNewGameLoad () while keeping user customisations intact

How I use this Script:
  • I copy everything to a new Quest Script.
  • I declare mod-specific variables.
  • I change the code in initNewVersion (), initNewGameLoad (), uninstall (), tick () and doTestCase ().
  • When I need a debug traced, I use debugTrace () instead of Debug.Trace () or Debug.Notification ().

Also, as suggested above, my mods can have user customisations via SETPQV.
In the example above, they can set the endure time of effects of being hungry, thirsty and exhausted.

I've found work-arounds for the problems previously listed.
So I've hidden them in a spoiler tag.
Spoiler

I can't rely on OnInit () because it only fires when the mod is first activated. So any initialisations attached to that during development will get initialised once only. And during development, variables change - a lot. So if once changes and I add it to OnInit (), my next play-test will not initialise that variable properly - because OnInit () was already triggered in a previous game.

Problems:
1. Because OnUpdate only runs in game time (i.e. not in MenuMode), initGameLoaded () will fire if the player stays in MenuMode for more than 60 seconds.
2. If the player stays on the Main Menu before loading a game for more than 10 seconds, initGameRestarted () will not execute.
3. Also, initGameRestarted () will not execute except for the first game loaded.

Luckily, I've not needed initGameRestarted () yet.
And initGameLoaded () firing if I stay in MenuMode for more than 60 seconds is simply an inconvenience.
(i.e. my variables do not initialise because "started" variable is set.)
User avatar
Nymph
 
Posts: 3487
Joined: Thu Sep 21, 2006 1:17 pm

Post » Tue Jun 19, 2012 3:06 am

I've refined the code a bit. I removed the initGameRestarted () function. I simply couldn't get it to execute reliably.

Spoiler
Scriptname kuSomeQuestScript extends Quest  {kuertee eat and sleep quest script - sleep}Float versionFloat lastVersionFloat lastTimeUpdateBool Property resetNow AutoBool startedEvent OnInit ()	;register 1 second or less here, simply so that the quest starts and always runs	RegisterForUpdate (1)EndEventFunction initGameLoaded ()	version = 0.5	If !started || version != lastVersion		started = True		lastVersion = version		initVars ()	EndIf	;register for updates here, change to what you really need	RegisterForUpdate (5)	RegisterForSleep ()EndFunctionEvent OnUpdate ()	Float timeNow = Utility.GetCurrentRealTime ()	If Math.abs (timeNow - lastTimeUpdate) > 60		initGameLoaded ()	ElseIf resetNow		;allow the user to reset variables via the console with setQPV kuSomeQuest resetNow True		initVars ()	EndIf	lastTimeUpdate = timeNow	tick ()EndEventFunction initVars ()	resetNow = False	;...add initialisation hereEndFunctionFunction tick ()	;...your main function is hereEndFunctionFunction OnSleepStart (Float sleepStartTime, Float sleepDuration)EndFunction
User avatar
anna ley
 
Posts: 3382
Joined: Fri Jul 07, 2006 2:04 am

Post » Tue Jun 19, 2012 6:21 am

I've refined my start-up code for Scripts again:
Spoiler
Scriptname X extends Quest  {kuertee encumbering loot armour and weapons quest script}Bool startedFloat versionFloat lastVersionFloat lastTimeUpdateBool Property resetNow AutoBool busyEvent OnInit ()	RegisterForUpdate (1)EndEventEvent OnUpdate ()	If !busy		busy = True		version = 0.5		Float timeNow = Utility.GetCurrentRealTime ()		;debugTrace ("OnUpdate " + timeNow + ", " + lastTimeUpdate)		If Math.abs (timeNow - lastTimeUpdate) > 60			initGameLoaded ()		ElseIf resetNow || version != lastVersion			initGameLoaded ()		Else			tick ()		EndIf		lastTimeUpdate = timeNow		busy = False	EndIfEndEventFunction debugTrace (String debugMessage)	;Debug.Notification ("!." + debugMessage)	Debug.Trace ("!" + debugMessage)EndFunctionFunction initGameLoaded ()	If version != lastVersion || !started || resetNow		debugTrace ("initGameLoaded")		lastVersion = version		started = True		resetNow = False                ;...init at mod's first run here	EndIf        ;...init per game load here.        ;note: that i don't put anything outside the If block above because at the moment, this will execute if the user is in a menu for more than a minute.        ;see Notes below.EndFunction;i declare my variables specific to the script here.;saves me from having to scroll right up the top.;also, it keeps the code start-up code above initGameLoaded () clean of anything specific to the Script.Function tick ()        ;...main code here at every OnUpdate ()EndFunction

Notes:
  • So, it still runs off the assumption that when you load the game, 60 seconds has passed since the last time the mod recorded GetCurrentRealTime ().
    It's not the best option because if you stay on a Menu for more than 60 seconds initGameLoaded () will run.
  • Check of "!busy" at OnUpdate is required because OnUpdate () executes before the previous OnUpdate () is finished when its registered interval is high (e.g. RegisterForUpdate (0.01).
  • It has a version check: The initGameLoaded () will execute when the version variable is set to something new. The mod author should do this at every new release.
  • And as before, the user (via the console) or another event can reset the mod by setting the property "ResetNow".
    Sometimes this may be cleaner rather than doing an actual Reset ().
  • Removing the main code from OnUpdate () and putting it in a custom function tick () just makes writing the code easier.
User avatar
barbara belmonte
 
Posts: 3528
Joined: Fri Apr 06, 2007 6:12 pm

Post » Mon Jun 18, 2012 6:03 pm

Here's my updated code.

  • It now has user uninstallation.
  • Better handling of OnUpdate () Events - in particular:
    • RegisterForSingleUpdate () at the end of each iteration ensures that the whole code is complete before the next interval runs.
    • and it prevents the problem of RegisterForUpdate () pinging deactivated ESPs / Scripts as I've found it does and with the problem
      described in this page's Discussion: http://www.creationkit.com/Save_File_Notes_%28Papyrus%29
  • better handling of debug Traces
  • the code is commented.

Spoiler
Function debugTrace (String debugMessage, Int debugLevel = 1, Bool notify = False)    If debugMode && debugMode >= debugLevel        If notify            Debug.Notification ("." + debugMessage)        EndIf        Debug.Trace ("." + debugMessage)    EndIfEndFunctionBool startedFloat versionFloat lastVersionFloat lastOnUpdateTimeUpdateBool Property resetNow AutoInt Property debugMode AutoInt Property testCase AutoBool stoppedThenStartedFloat updateIntervalBool Property uninstallNow AutoEvent OnInit ()    Int oldDebugMode = debugMode    ;show debugs in the log during initialisations. the current debugMode is saved.    ;and the debugMode set high, so that debugs are logged.    debugMode = 10    debugTrace ("OnInit")    RegisterForSingleUpdate (1)    ;restore the previous debugMode.    debugMode = oldDebugModeEndEventEvent OnUpdate ()    ;SET VERSION AND NORMAL UPDATE INTERVAL    version = 0.501    updateInterval = 1.0    Float timeNow = Utility.GetCurrentRealTime ()    If Math.abs (timeNow - lastOnUpdateTimeUpdate) > 60        ;if current number of seconds is > 1 minute from the last recorded second in the game, it is        ;likely that the game was reloaded.        initGameLoaded ()    ElseIf version != lastVersion || resetNow        ;there is a new version. or the player manually reset the mod by typing in the console:        ;SETPQV  RESETNOW TRUE        initGameLoaded ()    ElseIf uninstallNow        debugTrace ("OnUpdate uninstall")        uninstallNow = False        uninstall ()    Else        ;tick () is the main update function.        ;add your code in that function instead of here, to keep this starting block free of        ;mod-specific code.        tick ()        ;register only for a single update.        ;if RegisterForUpdate () is used, and the mod is deactivated, the Event manager will keep        ;send OnUpdate () Event to the deactivated mod. problems will occur.        ;with RegisterForSingleUpdate (), if the mod is deactivated, the Event manager will fail        ;only once.        RegisterForSingleUpdate (updateInterval)    EndIf    ;save the current number of seconds since game-start.    lastOnUpdateTimeUpdate = timeNowEndEventFunction initGameLoaded ()    Int oldDebugMode = debugMode    ;show debugs in the log during initialisations.    debugMode = 10    Bool newReset    If version != lastVersion || !started || resetNow        If lastVersion && !stoppedThenStarted            ;if the mod was previously installed (i.e. lastVersion is set), then reset the mod            ;calling Stop () and Start ().            ;because aliases are only ever filled at OnInit () Event, this is needed in case the            ;updates adds or removes new aliases.            debugTrace ("initGameLoaded reset")            ;UnregisterForUpdate () so that any queued update is removed.            UnregisterForUpdate ()            Stop ()            Utility.Wait (1)            ;Start () will reinitiate the OnInit () Event.            Start ()            stoppedThenStarted = True            newReset = True        Else            ;this is a new mod activation or a player initiated reset.            debugTrace ("initGameLoaded init")            stoppedThenStarted = False            lastVersion = version            started = True            resetNow = False            initNewVersion ()        EndIf    EndIf    If !newReset        ;if the game wasn't reset above (i.e. Stop () and Start () wasn't called), then this        ;must be a normal game load.        debugTrace ("initGameLoaded loaded")        initNewGameLoad ()        ;RegisterForSingleUpdate () needed here because        RegisterForSingleUpdate (1)    EndIf    debugMode = oldDebugModeEndFunctionFunction initNewVersion ()    ;initialise data for new activations or mod resets here.    ;for example: initialise variables that will persist across the life span of the mod here.    ;for example, initialise arrays here.EndFunctionFunction initNewGameLoad ()    ;initialise data for new game loads here.    ;for example: if you need the mod to always go to a certain State everytime the player loads the    ;game, add it here.EndFunctionFunction uninstall ()    UnregisterForUpdate ()    ;unregister events here.    ;remove player spells here - or else their stats will get corrupted.    Stop ()EndFunction

Function tick ()
;main code iteration
EndFunction

So, the only bits of code that I change for each Quest Script are:
1. in debugTrace (). This is so my Debug.Trace() are distinguishable in the logs.
2. version and updateInterval in OnUpdate ().
3. The code in initNewVersion (), initNewGameLoade (), uninstall () and tick ().
4. And instead of calling Debug.Trace () or Debug.Notifcation (), I call my custom function of debugTrace ().

And here's this code working in my unreleased Auto-first-third person view mod:
Spoiler
Scriptname kuAFTPVQS extends Quest  {kuertee auto-first-third-person view player script}Function debugTrace (String debugMessage, Int debugLevel = 1, Bool notify = False)	If debugMode && debugMode >= debugLevel		If notify			Debug.Notification ("!kuAFTPVQS." + debugMessage)		EndIf		Debug.Trace ("!kuAFTPVQS." + debugMessage)	EndIfEndFunctionBool startedFloat versionFloat lastVersionFloat lastOnUpdateTimeUpdateBool Property resetNow AutoInt Property debugMode AutoInt Property testCase AutoBool stoppedThenStartedFloat updateIntervalBool Property uninstallNow AutoEvent OnInit ()	Int oldDebugMode = debugMode	;show debugs in the log during initialisations. the current debugMode is saved.	;and the debugMode set high, so that debugs are logged.	debugMode = 10	debugTrace ("OnInit")	RegisterForSingleUpdate (1)	;restore the previous debugMode.	debugMode = oldDebugModeEndEventEvent OnUpdate ()	;SET VERSION AND NORMAL UPDATE INTERVAL	version = 0.501	updateInterval = 1.0	Float timeNow = Utility.GetCurrentRealTime ()	If Math.abs (timeNow - lastOnUpdateTimeUpdate) > 60		;if current number of seconds is > 1 minute from the last recorded second in the game, it's		;likely that the game was reloaded.		initGameLoaded ()	ElseIf version != lastVersion || resetNow		;there is a new version. or the player manually reset the mod by typing in the console:		;SETPQV  RESETNOW TRUE		initGameLoaded ()	ElseIf uninstallNow		debugTrace ("OnUpdate uninstall")		uninstallNow = False		uninstall ()	Else		;tick () is the main update function.		;add your code in that function instead of here, to keep this starting block free of		;mod-specific code.		tick ()		;register only for a single update.		;if RegisterForUpdate () is used, and the mod is deactivated, the Event manager will keep		;send OnUpdate () Event to the deactivated mod. problems will occur.		;with RegisterForSingleUpdate (), if the mod is deactivated, the Event manager will fail		;only once.		RegisterForSingleUpdate (updateInterval)	EndIf	;save the current number of seconds since game-start.	lastOnUpdateTimeUpdate = timeNowEndEventFunction initGameLoaded ()	Int oldDebugMode = debugMode	;show debugs in the log during initialisations.	debugMode = 10	Bool newReset	If version != lastVersion || !started || resetNow		If lastVersion && !stoppedThenStarted			;if the mod was previously installed (i.e. lastVersion is set), then reset the mod			;calling Stop () and Start ().			;because aliases are only ever filled at OnInit () Event, this is needed in case the			;updates adds or removes new aliases.			debugTrace ("initGameLoaded reset")			;UnregisterForUpdate () so that any queued update is removed.			UnregisterForUpdate ()			Stop ()			Utility.Wait (1)			;Start () will reinitiate the OnInit () Event.			Start ()			stoppedThenStarted = True			newReset = True		Else			;this is a new mod activation or a player initiated reset.			debugTrace ("initGameLoaded init")			stoppedThenStarted = False			lastVersion = version			started = True			resetNow = False			initNewVersion ()		EndIf	EndIf	If !newReset		;if the game wasn't reset above (i.e. Stop () and Start () wasn't called), then this		;must be a normal game load.		debugTrace ("initGameLoaded loaded")		initNewGameLoad ()		;RegisterForSingleUpdate () needed here because		RegisterForSingleUpdate (1)	EndIf	debugMode = oldDebugModeEndFunctionFunction initNewVersion ()	;initialise data for new activations or mod resets here.	;for example: initialise variables that will persist across the life span of the mod here.	;for example, initialise arrays here.	userWhenRangedWeaponDrawn = 100	userWhenMeleeWeaponDrawn = 100	userWhenSprinting = 0	userWhenRunning = -1	userWhenSneaking = 100	userWhenInInterior = 100	userWhenOtherwise = 0EndFunctionFunction initNewGameLoad ()	;initialise data for new game loads here.	;for example: if you need the mod to always go to a certain State everytime the player loads the	;game, add it here.	GotoState ("ChangeView")EndFunctionFunction uninstallNow ()	UnregisterForUpdate ()	;unregister events here.	;remove player spells here - or else their stats will get corrupted.	Stop ()EndFunctionInt viewInt lastView;scores: 0=3rd, 100=1stInt Property userWhenRangedWeaponDrawn AutoInt Property userWhenMeleeWeaponDrawn AutoInt Property userWhenSprinting AutoInt Property userWhenRunning AutoInt Property userWhenSneaking AutoInt Property userWhenInInterior AutoInt Property userWhenOtherwise AutoBool equippedRangedLeftBool equippedRangedRightInt scoreFactorInt currentViewFunction tick ()	;code that needs to run at every OnUpdate () is added here.EndFunctionState ChangeView	Function tick ()		Actor aPlayer = Game.GetPlayer ()		If (Game.IsCamSwitchControlsEnabled () && aPlayer.IsInKillMove () == 0)			Float tempInt			If aPlayer.IsWeaponDrawn ()				equippedRangedLeft = aPlayer.GetEquippedItemType (0) > 6 && aPlayer.GetEquippedItemType (0) < 10				equippedRangedRight = (aPlayer.GetEquippedItemType (1) > 6 && aPlayer.GetEquippedItemType (1) < 10)				If userWhenRangedWeaponDrawn > -1 && (equippedRangedLeft || equippedRangedRight)					tempInt = userWhenRangedWeaponDrawn				ElseIf userWhenMeleeWeaponDrawn					tempInt = userWhenMeleeWeaponDrawn				EndIf			ElseIf userWhenSprinting > -1 && aPlayer.IsSprinting ()				tempInt = userWhenSprinting			ElseIf userWhenRunning > -1 && aPlayer.IsRunning ()				tempInt = userWhenRunning			ElseIf userWhenSneaking > -1 && aPlayer.IsSneaking ()				tempInt = userWhenSneaking			ElseIf userWhenInInterior > -1 && aPlayer.IsInInterior ()				tempInt = userWhenInInterior			Else				tempInt = userWhenOtherwise			EndIf			If tempInt > 51				view = 1			Else				view = 3			EndIf			If view != lastView				lastView = view				If (view == 1)					Game.ForceFirstPerson ()				ElseIf (view == 3)					Game.ForceThirdPerson ()				EndIf			EndIf			;GotoState ("UpdateScores")		EndIf	EndFunctionEndStateState UpdateScores	Function tick ()		Actor aPlayer = Game.GetPlayer ()		If (Game.IsCamSwitchControlsEnabled () && aPlayer.IsInKillMove () == 0)			;todo: change this to get the view from the game			;currentView = get camera view			If currentView == view				scoreFactor = 1			Else				;user must have changed the view				view = currentView				scoreFactor = 100			EndIf			If (aPlayer.IsWeaponDrawn ())				If (equippedRangedLeft || equippedRangedRight)					If currentView == 1						userWhenRangedWeaponDrawn += scoreFactor					ElseIf currentView == 3						userWhenRangedWeaponDrawn -= scoreFactor					EndIf					If userWhenRangedWeaponDrawn < 0						userWhenRangedWeaponDrawn = 0					ElseIf userWhenRangedWeaponDrawn > 100						userWhenRangedWeaponDrawn = 100					EndIf				Else					If currentView == 1						userWhenMeleeWeaponDrawn += scoreFactor					ElseIf currentView == 3						userWhenMeleeWeaponDrawn -= scoreFactor					EndIf					If userWhenMeleeWeaponDrawn < 0						userWhenMeleeWeaponDrawn = 0					ElseIf userWhenMeleeWeaponDrawn > 100						userWhenMeleeWeaponDrawn = 100					EndIf				EndIf			ElseIf (aPlayer.IsSprinting ())				If currentView == 1					userWhenSprinting += scoreFactor				ElseIf currentView == 3					userWhenSprinting -= scoreFactor				EndIf				If userWhenSprinting < 0					userWhenSprinting = 0				ElseIf userWhenSprinting > 100					userWhenSprinting = 100				EndIf			ElseIf (aPlayer.IsRunning ())				If currentView == 1					userWhenRunning += scoreFactor				ElseIf currentView == 3					userWhenRunning -= scoreFactor				EndIf				If userWhenRunning < 0					userWhenRunning = 0				ElseIf userWhenRunning > 100					userWhenRunning = 100				EndIf			ElseIf (aPlayer.IsSneaking ())				If currentView == 1					userWhenSneaking += scoreFactor				ElseIf currentView == 3					userWhenSneaking -= scoreFactor				EndIf				If userWhenSneaking < 0					userWhenSneaking = 0				ElseIf userWhenSneaking > 100					userWhenSneaking = 100				EndIf			ElseIf (aPlayer.IsInInterior ())				If currentView == 1					userWhenInInterior += scoreFactor				ElseIf currentView == 3					userWhenInInterior -= scoreFactor				EndIf				If userWhenInInterior < 0					userWhenInInterior = 0				ElseIf userWhenInInterior > 100					userWhenInInterior = 100				EndIf			Else				If currentView == 1					userWhenOtherwise += scoreFactor				ElseIf currentView == 3					userWhenOtherwise -= scoreFactor				EndIf				If userWhenOtherwise < 0					userWhenOtherwise = 0				ElseIf userWhenOtherwise > 100					userWhenOtherwise = 100				EndIf			EndIf		EndIf		GotoState ("ChangeView")	EndFunctionEndState
I also have the same procedure working in my other unreleased mods: Auto-save and time, Battle fatigue and injuries, Clothing matters, Eat and sleep, Encumbering loot armour and weapons and Gold adjustment (a minified version of the theNiceOne's Oblivion Gold Adjustment mod).

Basically, this start-up code works.


EDIT: I'm sure a lot of this will become obsolete once SKSE is out with OnGameLoaded () and OnGameRestarted () Events.
User avatar
JLG
 
Posts: 3364
Joined: Fri Oct 19, 2007 7:42 pm

Post » Mon Jun 18, 2012 8:43 pm

EDIT: I'm sure a lot of this will become obsolete once SKSE is out with OnGameLoaded () and OnGameRestarted () Events.
Still, it's nice work--useful now, and good as an aid to understanding what's possible without extenders. Thanks for sharing it!
User avatar
Tha King o Geekz
 
Posts: 3556
Joined: Mon May 07, 2007 9:14 pm

Post » Mon Jun 18, 2012 7:00 pm

I am finding many times my quest script will not start up on game load. I have to load my save game a second time to get it to start.

I am fearful that we cannot take anything at face value, we should test every command we use even if it is vanilla and seems like it should work fine.

Like this:



Scriptname aadpTestQuest extends QuestEVENT onInit()RegisterForSingleUpdate(0.2)Playerref = Game.GetPlayer()if PlayerRef == Game.GetPlayer()	debug.messagebox("this works")endifendEVENTEVENT onUpdateif PlayerRef == Game.GetPlayer()	debug.messagebox("this works as well")  ;  I NEVER SEE THIS MESSAGE!endifRegisterForSingleUpdate(0.2)endEVENT
User avatar
MatthewJontully
 
Posts: 3517
Joined: Thu Mar 08, 2007 9:33 am

Post » Tue Jun 19, 2012 12:41 am

Thanks, DreamKing!


Spooky,
...if PlayerRef == Game.GetPlayer()	debug.messagebox("this works as well")  ;  I NEVER SEE THIS MESSAGE!endif...

Agreed re: testing. I'm finding that in high intervals, ie. low numbers in RegisterForSingleUpdate (), OnInit Event fires twice.

More specifically about your debug.MessageBox, ensure that the PlayerRef variable is scoped at the whole instance of the Script rather than as a local variable in the one Function.

My detection of initialising at the three possible times works for me.
1. on new version of the mod
2. on loaded game
3. on user reset

The only caveat is if the user returns to the game world after staying in a menu for more than 60 seconds.
Hence the required differntiation between "new version init" and "game loaded" init.

My code in "game loaded" init is very minimal - or none at all.
E.g. I reset my timers for Auto-save and time at every game load (or for more than 60 seconds in a menu).
Because my timers run off "GetCurrentRealTime ()", it's very often that the current real time is less than the previously recorded time.
E.g. GetCurrentRealTime () is seconds from when you fired up the game. So if you just started a game session, this will be 0.
While the previous recorded time would be WELL over this - and more likely than not, WELL over 60 seconds.





Anyway, here's another GOTCHA in regards to variables:
initialised variables outside the functions only get initialised ONCE and ONCE only.
E.g. it is like OnInit () getting called once and once only - unless the Quest is restarted or Stop()ped then Start()ed.

What I mean by these are variables/properties initialised at their declaration.
E.g.: Float Property userNewGoldFactor = 0.25 Auto.

The variable usernewGoldFactor will only initialise as 0.25 ONCE.
If you need that variable to be different - for e.g. as an update to your mod, you'll need to set it somewhere.
You can put a new variable in that declaration e.g.:
Float Property userNewGoldFactor = 0.75 Auto,
but it won't take effect until the Quest is restarted.

After the uer updates the mod, it will still have 0.25 as its value - instead of your intended 0.75.
This is because the game inserts 0.25 from the saved game after instantiating it.

And you can't expect a user to do a clean-save if he simply wants to udpate the mod - unless you can't help it.
So, it's best to detect a new version (with variables) then reinitialise usernewGoldFactor to 0.75 inside a function that only runs when new versions are detected - e.g. "initNewVersion ()"



Moral of the story:
Loading a game will override the value of any pre-existing "instance" variables declared in the script - this is just common-sense and as expected - but not that obvious sometimes.
(Glossary: "instance" variables are variables that are declared in the main body of the Script and is available to everything in the Script.)
User avatar
jeremey wisor
 
Posts: 3458
Joined: Mon Oct 22, 2007 5:30 pm

Post » Tue Jun 19, 2012 12:01 am

HA! OH YES I CAN....I been doing that as that was the only way to avoid an entire Shmorgishborg of player game errors. (...unless you cant help is right!)

Until we feel we have found MOST of the gotcha I will keep telling my players to do so.

But lets say this quest var change issue is the ONLY reason my mod require a clean install for each upgrade, then why not just declare ALL variable at the tip of the script instead of doing so in the script and risk the issue you are describing? Or is there a way to restart the quest again via a script?


And I do not entirely believe this is the only issue that warrant doing a clean save install for each upgrade. At least for combat mods like my own.


Errors I get when I do not do a clean install each time I change my mod:
  • All the NPC EXCEPT my companion use their magic ability script I give to ALL actors.
  • Phantom scripts still running even when deleted form my mod. This happen in oblivion as well but when I first reported it in 2007 to other moders on the forums that said I was crazy.
  • ALL custom abilities scripts stop running.
  • SOME abilities scripts run others do not in the same spell.
  • When using getvalue for a custom global in a Magic effect script it reports 0, but in the console it reports 2 as it is in my CK. This one is the most scary so far for me.
  • Animations begin to act like OTHER animations (two weapon fighter was acting like it had a torch in the other hand when it was actually a sword).

There are other but I am too tired to go on. In all case the issues went away after a clean install.
The only thing I am doing in my quest mod is giving the abilities to all the npc via get random actor command in a 2000 unit radius. NOTHING ELSE!


And you can't expect a user to do a clean-save if he simply wants to udpate the mod - unless you can't help it.
User avatar
Hussnein Amin
 
Posts: 3557
Joined: Sun Aug 05, 2007 2:15 am

Post » Tue Jun 19, 2012 12:52 am

BUT setting variable like ME to the spell target locally does work in a magic script, this works all the time:

Scriptname aadpFastRestScript extends activemagiceffectActor MeEvent OnEffectStart(Actor akTarget, Actor akCaster)		Me = akTarget		RegisterForSingleUpdate(0.05)EndEventEvent OnUpdate()if Me.GetAV("Stamina") > 0   Me.RestoreActorValue("Stamina", Me.GetBaseActorValue("Stamina") * 0.02)   RegisterForSingleUpdate(0.05)endifEndEvent


So is your advice only for quests?



More specifically about your debug.MessageBox, ensure that the PlayerRef is scoped at the whole instance of the Script rather than as a local variable in the one Function.
User avatar
Lynne Hinton
 
Posts: 3388
Joined: Wed Nov 15, 2006 4:24 am

Post » Tue Jun 19, 2012 5:06 am

Hey Spooky, agreed re: clean installs. It's still always safer to suggest the procedure when upgrading.

About setting the variable inside functions, your last one worked because Me is declared (i.e. Actor Me) at the "instance level". That means this variable is settable/gettable (i.e. Me = akTarget) at the whole instance of the Script - regardles of what scope (e.g. Function or State) the Script is in.

However, your previous example didn't show where Playerref was declared at. So, I assumed it was declared at a local level (e.g. inside a Function). Actually, looking at it again, it is not declared anywhere - so I think you missed out on pasting it or writing or something.

So, to directly answer your question about it being only in Quests script: No. This applies to any Script.
Any variables declared and given a value at the local level will have a different value elsewhere.

So...in the sample below, Playerref will have different values in OnInit () and OnUpdate ().
Spoiler
Event OnInit ()    Actor Playerref = Game.GetPlayer ()    RegisterForSingleUpdate (0.2)EndEventEvent OnUpdate ()    Actor Playerref = GetAlias (1)    doThisFunction ()EndEventFunction doThisFunction ()    Actor Playerref = GetAlias (2)EndFunction

However, the sample below, changes to Playerref will carry across all the scope (Events and Functions).
Spoiler
Actor PlayerrefEvent OnInit ()    Playerref = Game.GetPlayer ()    RegisterForSingleUpdate (0.2)EndEventEvent OnUpdate ()    Playerref = GetAlias (1)    doThisFunction ()EndEventFunction doThisFunction ()    Playerref = GetAlias (2)EndFunction

Sorry, if you know this already. I'm just assuming that your previous example had Playerref scoped locally and so even if you are setting it to Game.GetPlayer () at OnInit (), it won't have that value in OnUpdate ().
User avatar
maya papps
 
Posts: 3468
Joined: Mon Aug 07, 2006 3:44 pm

Post » Mon Jun 18, 2012 11:31 pm

I've updated the Script and the description of the code in the original post.
The new version is 99% accurate in determining these events:
1. first mod activation calls initNewVersion ()
2. every game load calls initNewGameLoad()
3. user-triggered uninstallation via SETPQV calls uninstall ()
4. when a new version is activated over the old version calls uninstall (), initNewVersion (), initNewGameLoad () while keeping user customisations intact.
The rest of the new information is also in the original post.
User avatar
N Only WhiTe girl
 
Posts: 3353
Joined: Mon Oct 30, 2006 2:30 pm

Post » Tue Jun 19, 2012 1:16 am

Agreed re: testing. I'm finding that in high intervals, ie. low numbers in RegisterForSingleUpdate (), OnInit Event fires twice.
Don't know if you still have this issue or I missed something reading the thread but you should use goToState() instead of that "bool busy" and it's logic. http://www.creationkit.com/Using_States_In_Papyrus

Nice stuff btw.

Edit: nevermind, registerforsingleupdaet is better
User avatar
brenden casey
 
Posts: 3400
Joined: Mon Sep 17, 2007 9:58 pm

Post » Mon Jun 18, 2012 6:13 pm

I am finding many times my quest script will not start up on game load. I have to load my save game a second time to get it to start.

I am fearful that we cannot take anything at face value, we should test every command we use even if it is vanilla and seems like it should work fine.

Like this:



Scriptname aadpTestQuest extends QuestEVENT onInit()RegisterForSingleUpdate(0.2)Playerref = Game.GetPlayer()if PlayerRef == Game.GetPlayer()	debug.messagebox("this works")endifendEVENTEVENT onUpdateif PlayerRef == Game.GetPlayer()	debug.messagebox("this works as well")  ;  I NEVER SEE THIS MESSAGE!endifRegisterForSingleUpdate(0.2)endEVENT

I assume that PlayerRef is defined somewhere? Is it a variable or a property?
User avatar
Laura Hicks
 
Posts: 3395
Joined: Wed Jun 06, 2007 9:21 am

Post » Mon Jun 18, 2012 5:22 pm

yes, with all my other var, sorry I did not post that for advice on the script but as an example of what I was talking about. But I probably still should include the entire script even so.

I assume that PlayerRef is defined somewhere? Is it a variable or a property?
User avatar
Haley Cooper
 
Posts: 3490
Joined: Wed Jun 14, 2006 11:30 am

Post » Tue Jun 19, 2012 3:40 am

Here's a working GetGameLoaded() function:
Bool Function GetGameLoaded(LeveledItem akLeveledItem = None, Form apForm = None, ObjectReference akContainer = None)        akContainer.AddItem(akLeveledItem, 1, True)        If akContainer.GetItemCount(apForm)                akContainer.RemoveItem(apForm, akContainer.GetItemCount(apForm), True)                Return False        Else ; Once per save load                akLeveledItem.AddForm(apForm, 1, 1)                Return True        EndIfEndFunction
For testing/demonstrating efficacy:
Spoiler
ScriptName FunctionTestingScript extends QuestArmor Property UnplayableARMO AutoLeveledItem Property EmptyLVLI AutoEvent OnInit()	RegisterForSingleUpdate(0.1)EndEventEvent OnUpdate()	RegisterForSingleUpdate(3)	If GetGameLoaded(EmptyLVLI, UnplayableARMO, Game.GetPlayer())		Debug.Notification("GetGameLoaded()") ; Shows once per save load	Else		Debug.Notification("!GetGameLoaded()") ; Shows repeatedly	EndIf	EndEventBool Function GetGameLoaded(LeveledItem akLeveledItem = None, Form apForm = None, ObjectReference akContainer = None)        akContainer.AddItem(akLeveledItem, 1, True)        If akContainer.GetItemCount(apForm)                akContainer.RemoveItem(apForm, akContainer.GetItemCount(apForm), True)                Return False        Else                akLeveledItem.AddForm(apForm, 1, 1)                Return True        EndIfEndFunction 
User avatar
Amy Gibson
 
Posts: 3540
Joined: Wed Oct 04, 2006 2:11 pm

Post » Mon Jun 18, 2012 7:48 pm

Excellent! Will try this out.
User avatar
Alexxxxxx
 
Posts: 3417
Joined: Mon Jul 31, 2006 10:55 am

Post » Mon Jun 18, 2012 5:51 pm

Here's a working GetGameLoaded() function.

ScriptName GetGameLoadedScript extends QuestArmor Property DecapitatedHead AutoLeveledItem Property GetGameLoadedLVLI AutoEvent OnInit()	RegisterForUpdate(3)EndEvent;;;;;;;;;;;;;;;;;;;;;;;;;;;;Event OnUpdate()	If GetGameLoaded()		Debug.Notification("bGetGameLoaded == True") ; Shows once per save load	Else		Debug.Notification("bGetGameLoaded == False") ; Shows repeatedly	EndIf	EndEvent;;;;;;;;;;;;;;;;;;;;;;;;;;;;Bool Function GetGameLoaded()	Game.GetPlayer().AddItem(GetGameLoadedLVLI, 1, True)	If Game.GetPlayer().GetItemCount(DecapitatedHead)		Game.GetPlayer().RemoveItem(DecapitatedHead, 1, True)		Return False	Else		GetGameLoadedLVLI.AddForm(DecapitatedHead, 1, 1)		Return True	EndIfEndFunction
A good idea, but using registerforupdate is taking to much risks, especially as the script will be running constantly. Better to use a registerforsingleupdate and place another registerforsingleupdate at the end of the onupdate event. This avoids the risk of the game starting bloatting exponentially the first time the script is slowed down enough for a new onupdate event triggering before the previous one ends

Another issue is that this take advantage of a game bug. So, anyone using this should keep in mind that future patches could fix the bug and make this method useless.

Finally there is an issue with CTDs. I think it could be related to not checking the use all check box in the leveled list form, but It's only a supposition, you may known this issue better, as if I'm not mistaken you find that problem while updated leveled lists with your swords.
User avatar
Rachael Williams
 
Posts: 3373
Joined: Tue Aug 01, 2006 6:43 pm

Post » Tue Jun 19, 2012 6:50 am

Ahhh...is that an engine bug. That AddForm doesn't stick in the saved file. That's what I am assuming.

Other than that, I do suggest (as amgepo does) that you refrain from using RegisterForUpdate () and user RegisterForSingleUpdate (): http://www.creationkit.com/Talk:Save_File_Notes_%28Papyrus%29 (The secion titled ESP and Script deactivation tests - OnUpdate () Event, Variables and Properties ().)
User avatar
Louise Andrew
 
Posts: 3333
Joined: Mon Nov 27, 2006 8:01 am

Post » Mon Jun 18, 2012 7:59 pm

Ahhh...is that an engine bug. That AddForm doesn't stick in the saved file. That's what I am assuming.
The problen is not in the addform instruction, it works propperly in formlists. The problem is in the leveled lists.
User avatar
Alkira rose Nankivell
 
Posts: 3417
Joined: Tue Feb 27, 2007 10:56 pm

Post » Mon Jun 18, 2012 10:53 pm

A good idea, but using registerforupdate is taking to much risks, especially as the script will be running constantly.

Ahhh...is that an engine bug. That AddForm doesn't stick in the saved file. That's what I am assuming.

Other than that, I do suggest (as amgepo does) that you refrain from using RegisterForUpdate () and user RegisterForSingleUpdate (): http://www.creationkit.com/Talk:Save_File_Notes_%28Papyrus%29 (The secion titled ESP and Script deactivation tests - OnUpdate () Event, Variables and Properties ().)
The function is all that matters. The rest of the code was just set up for testing/demonstrating the function's efficacy.
Another issue is that this take advantage of a game bug. So, anyone using this should keep in mind that future patches could fix the bug and make this method useless.
Yeah, if ever LeveledItem.AddForm() were to persist, the function would cease to work.
Finally there is an issue with CTDs. I think it could be related to not checking the use all check box in the leveled list form, but It's only a supposition, you may known this issue better, as if I'm not mistaken you find that problem while updated leveled lists with your swords.
Hmmm.. I didn't crash at all while testing it, but the "Use All" flag of GetGameLoadedLVLI was ticked.
User avatar
Robert DeLarosa
 
Posts: 3415
Joined: Tue Sep 04, 2007 3:43 pm

Post » Tue Jun 19, 2012 4:16 am

The function is all that matters. The rest of the code was just set up for testing/demonstrating the function's efficacy.
Yeah, if ever LeveledItem.AddForm() were to persist, the function would cease to work.
Hmmm.. I didn't crash at all while testing it, but the "Use All" flag of GetGameLoadedLVLI was ticked.
I'm not totally sure of the "use all" being the determinant factor but while this never crashed to me, I read in a threath a while ago other two users (I thought you were one of them, maybe I was mistaken) where having that problem, and the only obvious difference I find between two methods was that checkbox.
User avatar
Darrell Fawcett
 
Posts: 3336
Joined: Tue May 22, 2007 12:16 am

Post » Tue Jun 19, 2012 1:37 am

Just an other JustinOther? Wasn't me, that is, in a different thread. I guess, if using that method, the "Use All" flag would be advised in that case.
User avatar
Chris Duncan
 
Posts: 3471
Joined: Sun Jun 24, 2007 2:31 am

Post » Tue Jun 19, 2012 1:35 am

Just an other JustinOther? Wasn't me, that is, in a different thread. I guess, if using that method, the "Use All" flag would be advised in that case.
I mistake you with Jaysus (I stay in that thread two months ago). This is the thread: http://www.gamesas.com/topic/1347706-add-item-to-a-leveled-list-via-a-script/page__st__30__p__20404001__hl__leveled__fromsearch__1#entry20404001
User avatar
Emily Martell
 
Posts: 3469
Joined: Sun Dec 03, 2006 7:41 am

Post » Tue Jun 19, 2012 1:47 am

Ah... Well here's hoping if Beth sets LeveledItem.AddForm() to persist in saves, that they'll let us set a "Constant" GLOB with Papyrus or something else so we can have a non-SKSE reliant GetGameLoaded(). If we could set a "Constant" bGetGameLoadedGLOB from 1 to 0, that would be an easy way. Works in the Fallouts...
User avatar
Killah Bee
 
Posts: 3484
Joined: Sat Oct 06, 2007 12:23 pm

Post » Tue Jun 19, 2012 6:21 am

What about an onload on the player through an alias? I suppose things like werewolf transformation could cause the object 3d to be unloaded, but a check could be placed inside the onload event to ensure no other thing beside the game being loaded caused the 3d object to be drawed.

Another prossibility would be to play with the clock. Let's say that an onupdate event takes the system clock each 10 seconds. Then the taken time is compared with the previous one. If the different is too great to blame a little stuttering, let's say a minute, it only can be caused by the game being loaded.

EDIT: There seem to not be any function to get the system clock. Maybe using random functions... though it seem really complex.
User avatar
REVLUTIN
 
Posts: 3498
Joined: Tue Dec 26, 2006 8:44 pm

Next

Return to V - Skyrim