[TUTORIAL]Implementing a Version Controller

Post » Tue Jun 19, 2012 4:45 pm

NOTE: This tutorial requires you have knowledge of creating perks & form lists because it will not be discussed on creating one in this tutorial

First what we will want to do is open up the Creation Kit, and goto Characters -> Quest

Now we will want to fill in the window, lets call our new quest form ZZ_TestController
Select the following options
  • [x] Run at Start-up
  • [x] Repeat States
Now lets goto the Quest Stages tab, and right click on the index and select New, keep the number at 0
From here we place a check at [x] Start Up Stage right click and also select New in the Log Entry, now the Papyrus Fragment should be greyed out from here we will continue and click OK at the dialog box to close it out

Now goto Gameplay -> Papyrus Manager right click and select New
We will name this script ZZ_VersionHandler and have it extend Quest
Now right click on our new Script and select Open External Editor

Spoiler

ScriptName ZZ_VersionHandler Extends Quest  ;These are variables that will pretty much tell us if we should update or not;Current Version (New Version if any);Installed Version (is the one that is found in the saved-game variable listGlobalVariable Property Ver_Current AutoGlobalVariable Property Ver_Installed AutoBook Property ManualBook Autofloat fUpdateInterval = 5.0bool bKeepAlive = TrueBool Function IsVersionOk()    If Ver_Current.GetValueInt() != Ver_Installed.GetValueInt()        Return False    EndIf    Return TrueEndFunctionFunction CheckUpdate()    RegisterForSingleUpdate(fUpdateInterval)EndFunctionFunction AddManual()    Actor player = Game.GetPlayer()    If player.GetItemCount(ManualBook) > 0        player.RemoveItem(ManualBook,player.GetItemCount(ManualBook),False)    EndIf        player.AddItem(ManualBook,1)EndFunctionEvent OnUpdate()    If !IsVersionOk()        AddManual()        Ver_Installed.SetValueInt(Ver_Current.GetValueInt())        Debug.Notification("Please check Arcane Assassin Manual for updates")    EndIf        If bKeepAlive        RegisterForSingleUpdate(fUpdateInterval)    EndIfEndEvent

If you have followed the instructions and using the Notepad++ from the Creation Kit Wiki, press CTRL+F5 to compile the script
Lets create a second script and name this script ZZ_VersionHandler and have it extend ObjectReference
Now right click on our new Script and select Open External Editor

Spoiler

ScriptName ZZ_OurModManual Extends ObjectReference  Perk Property MyPerk AutoFormList Property MyPerkSpellsList AutoActor playerEvent OnRead()	player = Game.GetPlayer()	AddSpellsForPerk(MyPerk, MyPerkSpellsList)	;Repeat if more perks are availableEndEventFunction AddSpellsForPerk(Perk prk, FormList list)	If list != None		If player.HasPerk(prk)			Int i = 0			Int size = list.GetSize()			While i < size				If (list.GetAt(i) As Spell)					Spell i_spell = (list.GetAt(i) As Spell)					If !player.HasSpell(i_spell)						player.AddSpell(i_spell)					EndIf				ElseIf (list.GetAt(i) As Shout)					Shout i_shout = (list.GetAt(i) As Shout)					If !player.HasSpell(i_shout)						player.AddShout(i_shout)					EndIf				ElseIf (list.GetAt(i) As WordOfPower)					WordOfPower i_word = (list.GetAt(i) As WordOfPower)					If !Game.IsWordUnlocked(i_word)						Game.UnlockWord(i_word)						Game.TeachWord(i_word)					EndIf				EndIf										i += 1			EndWhile			EndIf	EndIfEndFunction

Also Compile this script
Ok lets go to the Object Window
  • Miscellaneous -> Global
  • Right Click in the Form List, select New
  • First Global Variable name it ZZ_VersionCurrent and select Short for the integer type 101 (new version number) and select Constant
  • Second Variable ZZ_VersionInstalled and select Short for the integer type 100 (this is the old version) and dont select Constant, because this number will always change depending if the developer has updated, this will be compared with the first variable we created
Lets go back to the Object Window list, scroll until you see Items -> Books and from this we will use a already created book as a template select the first book you see, it should be AtrFrgDaedricRecipe00 right click it and select Duplicate, select our duplicate book and press F2 to rename it to ZZ_OurTestManual

Double click our book, and attach the ZZ_OurModManual to it, and fill in the properties that hold our Perk and Perk Spells contained in a Form List

=================================================

Why use this method? Well first of all if you plan to add perks that give player spells / abilities or etc. Then your going to need a way to update your players spell list if he already has the perk (in-case we added to the spell list). A Book is great not only can it run a script when it is read, but it can contain version history and important information for the player to read.

Example: When a player downloads a update of your mod, and has a perk that gave spells already from his previous journey in his saved-game. It will no longer continue to give the new spells in the Form List, this is why this method is important. The quest will detect the MOD version number, and the SAVED-GAME version number of the previous mod. If the version number is different from the saved-game version it will remove the book and re-add the book.

So why remove / re-add the book? Well the book is a saved reference that contains all the property configurations from the previous mod, lets say the new mod added more FORM LISTS, then the old book wont contain these, so its important to remove the old book then add the new book so the player can have the updated features when he reads the book, because the script i gave allows you to add / remove perk and form lists as needed to keep the player updated

=================================================
Ok lets go back to the quest we created open that up and...

Select Script tab, and add the new script we created ZZ_VersionHandler
Fill in the Variables we created in the Properties

Go back to Quest Stages and in the Papyrus Fragment area select ZZ_VersionHandler as our kmyQuest handler

Spoiler

kmyQuest.CheckUpdate()

Thats it, from here at the very first tap select "Compile All Scripts" and you should be set
User avatar
xx_Jess_xx
 
Posts: 3371
Joined: Thu Nov 30, 2006 12:01 pm

Post » Tue Jun 19, 2012 1:34 pm

You may want to use a Message property instead of Debug.Messagebox, I heard it does not show in game unless enable by ini edit. Anyway, useful tutorial. :)
User avatar
Tanya
 
Posts: 3358
Joined: Fri Feb 16, 2007 6:01 am

Post » Tue Jun 19, 2012 2:51 pm

I didnt use Debug.MessageBox, do u mean Notification? And thanks :)
User avatar
kasia
 
Posts: 3427
Joined: Sun Jun 18, 2006 10:46 pm

Post » Tue Jun 19, 2012 8:03 am

Good topic, I've been wondering how to manage this too.

Setting a Global as constant is cool. I also like the idea of a book :)

I'm not following what makes the code in the quest stage fire every time a savegame is loaded though. It looks like it's meant to.
If the savegame already has the quest at started, then from my understanding the OnInit() and Start Stage Fragments won't run again. Unless the quest is reset().

My current solution is to use the OnUpdate block, and set a long (2 min) poll.
I did similar in FNV, but had NVSE functions to check when the game or a savegame was loaded.

The idea comparing to a Global constant is a cool way to catch the mod update though :)

There's some notes http://www.creationkit.com/Save_File_Notes_(Papyrus) on saved scripts. A savegame may continue to run old code if it had started the particular function. Fun stuff this new threading engine.
User avatar
Ridhwan Hemsome
 
Posts: 3501
Joined: Sun May 06, 2007 2:13 pm

Post » Tue Jun 19, 2012 10:05 am

Nice one :) Please consider porting this tutorial to the CK wiki for better availability.
User avatar
Shae Munro
 
Posts: 3443
Joined: Fri Feb 23, 2007 11:32 am

Post » Tue Jun 19, 2012 9:02 am

Thanks, ill try to place this on the wiki, do i place it under my USER talk space? or....
If i port it to the wiki, i guess i could use Picture Illustrations as well
User avatar
Michael Russ
 
Posts: 3380
Joined: Thu Jul 05, 2007 3:33 am

Post » Tue Jun 19, 2012 1:16 pm

Thanks :) It'll be best to create a new article for it.
User avatar
Queen Bitch
 
Posts: 3312
Joined: Fri Dec 15, 2006 2:43 pm

Post » Tue Jun 19, 2012 12:42 pm

Might I be so arrogant to suggest a few (IMO) improvements?

First, remove IsVersionOk. I can't see any reason why this can't be done by CheckUpdate itself.
Also, instead of using GlobalVariables, simple properties should suffice. One UsedVersion auto property and another (Installed/Actual)Version autoreadonly-hidden property, one is stored within the savedgame the other hard-coded.

Secondly, CheckUpdate should be renamed, since it not only does a version comparison but also applies the update if needed and it should account for the possibility of a failed update.
A TryUpdate function returning a bool should accomplish that.

Lastly, considering the above, AddManual should be renamed ApplyUpdate and return an int status-code. That status-code can be used to tell player what went wrong. A few autoreadonly-hidden properties (Status_Success, etc.) would give some clarity too.

Additionally providing a common library with a few global functions which do what AddSpellsForPerk does would go along way to prevent http://en.wikipedia.org/wiki/Don%27t_repeat_yourself.

(Argh, had to retype everything since Firefox crashed...)

Edit: While we are on the topic of version control (well Auto-Updating actually) everyone should strongly consider using some version control management for (at the very least) there scripts. >> http://git-scm.com/(https://github.com/), http://mercurial.selenic.com/.
User avatar
Theodore Walling
 
Posts: 3420
Joined: Sat Jun 02, 2007 12:48 pm

Post » Tue Jun 19, 2012 3:38 am

Updated the ZZ_VersionHandler, now updates every 5 seconds in-game to check if we changed
User avatar
Trevi
 
Posts: 3404
Joined: Fri Apr 06, 2007 8:26 pm

Post » Tue Jun 19, 2012 5:43 pm

  • First Global Variable name it ZZ_VersionCurrent and select Short for the integer type 101 (new version number) and select Constant
  • Second Variable ZZ_VersionInstalled and select Short for the integer type 100 (this is the old version) and dont select Constant, because this number will always change depending if the developer has updated, this will be compared with the first variable we created

How does this ZZ_VersionInstalled globalvariable actually work here in these two steps? The reason I ask is that I have globalvariables that are not constant and if I change them in my mod, the old value is persisted in my game saves, so I don't understand how updating the value allows this to work and a change to be detected. Obviously it is for you, so what part am I not getting my head wrapped around here?

-MM
User avatar
Ysabelle
 
Posts: 3413
Joined: Sat Jul 08, 2006 5:58 pm

Post » Tue Jun 19, 2012 5:48 pm

You actually don't need external globals which require to be bound in the CK, you can just use properties, and thus keep it tidy and clean.
int property UserVersion auto ; stored in users savegameint property ActualVersion = 0 autoreadonly hidden ; hardcoded by you, increment on each release
The important bit here is how the properties are declared -- http://www.creationkit.com/Variables_and_Properties#Auto_Properties versus http://www.creationkit.com/Variables_and_Properties#Auto_Read-only_Properties hidden.
User avatar
Amiee Kent
 
Posts: 3447
Joined: Thu Jun 15, 2006 2:25 pm

Post » Tue Jun 19, 2012 9:28 am

Thanks for the comment Xetrill. So am I wrong in my thoughts/suspicions about the globalvariable use? Also one last question using your suggestion:
int property UserVersion auto ; stored in users savegameint property ActualVersion = 0 autoreadonly hidden ; hardcoded by you, increment on each releaseEvent OnUpdate()if UserVersion != ActualVersion   UnregisterForUpdate()else   RegisterForSingleUpdate(1)endifEndEvent


Would something like this work to keep....old remnants of scripts from constantly executing or would the remnant still have the original ActualVersion in it? I've been trying to work around some of the problems described http://www.gamesas.com/topic/1348833-old-versions-of-mods-conflicting-with-newer-ones/page__fromsearch__1 (but more around just older versions of scripts still referenced...not hard coded form ids, etc,.). While it usually doesn't make for a problem for my end users, DEBUGGING becomes a pain in the neck with all the bloody entries like this flooding into the log:

[04/01/2012 - 11:50:46PM] error: Unable to call Stop - no native object bound to the script object, or object is of incorrect typestack:[None].mm_BeluaRQ01MainQuestScript.Stop() - "" Line ?[None].mm_BeluaRQ01MainQuestScript.OnUpdate() - "mm_BeluaRQ01MainQuestScript.psc" Line ?[04/01/2012 - 11:50:46PM] error: Cannot call GetValue() on a None object, aborting function callstack:[None].mm_beluavampirequestscript.FixupAliases() - "mm_BeluaVampireQuestScript.psc" Line ?[None].mm_beluavampirequestscript.OnUpdate() - "mm_BeluaVampireQuestScript.psc" Line ?[04/01/2012 - 11:50:46PM] warning: Assigning None to a non-object variable named "::temp4"stack:[None].mm_beluavampirequestscript.FixupAliases() - "mm_BeluaVampireQuestScript.psc" Line ?[None].mm_beluavampirequestscript.OnUpdate() - "mm_BeluaVampireQuestScript.psc" Line ?

Ugh....
User avatar
anna ley
 
Posts: 3382
Joined: Fri Jul 07, 2006 2:04 am

Post » Tue Jun 19, 2012 10:02 am

So am I wrong in my thoughts/suspicions about the globalvariable use?
No you are correct but misunderstood taewon, I guess. He means exactly the same thing as me, or rather I mean the same thing as him. Though, I like my names a lot more for properties and functions. Anyway, it's the same, one constant - the actual version - and one non-constant - persist-able, user version.

And yeah old scripts can be quite an issue... but I haven't read the whole thread, but from what I have seen from it, people have looked and experimented a lot more than me, so I don't know enough to help you there.
User avatar
mike
 
Posts: 3432
Joined: Fri Jul 27, 2007 6:51 pm


Return to V - Skyrim