Advice on script organization and segmentation

Post » Mon Nov 19, 2012 5:19 am

Hello,

I have been getting every single day a little bit more adept with scripting (thanks to very helpful users in this forum) and now I slowly manage to get my scripts to do more complicated things how I want and when I want. This is great, but I'm starting to hit a problem which is more about organisation than scripting itself... I'm getting lost in my script!!!

Although my script is not too long (around 130 lines), I'm getting lost in it. Also, due to the many different conditions inside the same script, changing a simple if function can easily break my script and have me 3 hours trying to repair it...

So, I'm asking for advice on how to organize a script for better use, easier editing, and optimization...

I'm sure there are explanations already flying around, but I'm having a hard time finding them. In my case, my mod is set up like the following:

Two quests, one is not "start game enabled" and contains aliases, scenes, dialogues, etc... The other one is "start game enabled" and contains only a single main script that handles all the info in the other quest. Apart from this, I have several spells and dummy objects for achieving several things (Example: find LOS with nearby NPC's).

Where I'm having real troubles, though, is with my main script. I need to find a way to organise it better before it gets out of hand.

The script right now starts right on game start and contains an OnInit() event with a RegisterForUpdate(3) inside. Then follows an OnUpdate() event, and I have in here absolutely all my code except functions, which are separated at the bottom. This means that I have a bunch of nested if's all working fairly well together that decide what to do with the NPC's, scenes and dialogues.

The thing is that now I want to add more scenes which make different things happen depending on several conditions, and this script will soon be messy. How would be best to organize this? I'm starting to look into states, but can I run OnUpdate() events in each different state? Would it be better to have a script for each situation I want to have playing (would have to create more aliases and properties...)? A single quest for each possible scene, or all scenes in a single quest? Is there a better way?

Sorry for the wall of text, but I hope someone can give me some advice on this...

My main script is the one following. I know it's messy and that there are still checks that aren't defined. This script is controlling at the moment one single scene, alias, and a single dialogue, but I need to add more... If you look at the script, at the moment it fires only, per example, between 6PM and 6AM, but I will want to add conditions so that during the day something different happens, or maybe if in a different location, or depending on player reputation... All in the same script and organized like this??? Hope you get the picture...
Spoiler

Scriptname AAaaDielosRStarter extends Quest ConditionalQuest Property AADielosRQuest  AutoQuest Property AADielosStarterQuest  Auto  Alias Property AliasAttacker  AutoScene Property SceneFollow  Auto  Package Property PackageKeepEye  AutoPackage Property PackageForceGreet  Autoint Property ApproachToTalk Auto Conditionalint Property SceneStarted Auto Conditionalint Property DropItemsVar Auto ConditionalSPELL Property FindLOSSpell  AutoFormList Property AAStalkerFindLOSFormList  Autoint SceneUpdateCount = 0EVENT onInit()registerForUpdate(3)gotoState ("Start")endEVENTSTATE Start  EVENT onUpdate()  ;***********************************************************Things to check every loop  if DropItemsVar == 1   DropEquippedItems() ;Test, should end being a steal non equiped equipment function  EndIf  if SceneStarted == 0   ;***********************************************************Here come conditions to start Stalkers like time of day and location (check keyword locations in wiki)   Debug.Notification("Starting quest, scene, and trying to grab aliases...")   if AADielosRQuest.IsRunning() == False	AADielosRQuest.Start()   EndIf   if (AliasAttacker As ReferenceAlias).GetRef() == None || (AliasAttacker As ReferenceAlias).GetActorReference().IsDead() || ApproachToTalk != 1	ResetQuest()   endif   if AADielosRQuest.IsRunning() && SceneStarted == 0 && (AliasAttacker As ReferenceAlias).GetRef() != None && ApproachToTalk != 1	float TimeOfDay = GetCurrentHourOfDay()	int RandomChance = Utility.RandomInt() ;Not working or not applying correctly...	if TimeOfDay >= 18 || TimeOfDay <= 6 && RandomChance <= 1	 Debug.Notification("Alias found during the night and starting scene...")	 SceneFollow.Start()	 SceneStarted = 1	Else	 Debug.Notification("No stalkers during the day at the moment...")	 ResetQuest()	EndIf   endif  EndIf  if SceneStarted == 1   if (AliasAttacker As ReferenceAlias).GetActorReference().GetCurrentPackage() == PackageKeepEye	DebugPrintStalkerName() ;Print stalker name for debugging...	;***********************************************************Here come conditions to ForceGreet	FindLOSSpell.cast(Game.GetPlayer())	;Debug.Notification("Firing Spell...")	Utility.Wait(1)	if AAStalkerFindLOSFormList.GetSize() == 1 && AAStalkerFindLOSFormList.HasForm((AliasAttacker As ReferenceAlias).GetReference())	 SceneUpdateCount = AAStalkerFindLOSFormList.GetSize()	 Debug.Notification("The LOS list has " + SceneUpdateCount + " and the only entry is the Stalker, so it applies...")	 ApproachToTalk = 1	 Debug.Notification("ForceGreet should start now...")	ElseIf AAStalkerFindLOSFormList.GetSize() == 0	 SceneUpdateCount = AAStalkerFindLOSFormList.GetSize()	 Debug.Notification("The LOS list has " + SceneUpdateCount + " and the Stalker is still following, so it applies...")	 ApproachToTalk = 1	 Debug.Notification("ForceGreet should start now...")	Else	 ;SceneUpdateCount = AAStalkerFindLOSFormList.GetSize()	 ;Debug.Notification("The LOS list has " + SceneUpdateCount + " entries, thus doesn't apply for ForceGreet...")	EndIf	SceneUpdateCount = 0	AAStalkerFindLOSFormList.Revert()  	;***********************************************************Here come conditions to abort Scene	if Game.GetPlayer().GetDistance((AliasAttacker As ReferenceAlias).GetReference()) > 10000 || (AliasAttacker As ReferenceAlias).GetRef() == None || (AliasAttacker As ReferenceAlias).GetActorReference().IsDead()	 Debug.Notification("Stalker lost victim...")	 ResetQuest()	EndIf   Elseif ApproachToTalk != 1 && SceneStarted == 0	ResetQuest()   EndIf  EndIfEndEventEndState;*************************** F U N C T I O N S **********************************string Function DebugPrintStalkerName()String AliasName = (AliasAttacker As ReferenceAlias).GetReference().GetBaseObject().GetName()if AliasName == ""  Debug.Notification("The stalker is a stranger...")Else  Debug.Notification("The stalker is: " + AliasName)EndIfEndFunctionfloat Function GetCurrentHourOfDay()float Time = Utility.GetCurrentGameTime()Time -= Math.Floor(Time) ; Remove "previous in-game days passed" bitTime *= 24 ; Convert from fraction of a day to number of hoursReturn TimeEndFunctionint function ResetQuest();**************Add Cooldown token to alias before clearing!!!(AliasAttacker As ReferenceAlias).Clear()AADielosRQuest.Stop()Utility.Wait(1)AADielosRQuest.Start()SceneFollow.Stop()SceneStarted = 0endFunctionint function DropEquippedItems() ;Testing finding equipped items, can see if result fires if player nakedint slot = 1int iwhile (i < 32)  Form TempForm = Game.GetPlayer().GetWornForm(slot)  if (TempForm)   Game.GetPlayer().DropObject(TempForm)  endif  slot *= 2  i += 1endwhileDropItemsVar = 0endFunction

Thanks in advance!!!
User avatar
Mackenzie
 
Posts: 3404
Joined: Tue Jan 23, 2007 9:18 pm

Post » Mon Nov 19, 2012 5:19 pm

One thing that I find helps me quite a lot is actually quite simple - add white space (and comments where necessary) to separate sections. For example, here's a slightly trimmed version (I've collapsed the non-auto properties) of the header of quite a long (519 lines) script I've been working on:
Spoiler
;/ Data interface==============================/;Actor Property PlayerRef Auto{An auto property set to the unique reference 0x14 ('PlayerRef')in the Creation Kit is the most efficient way to refer to the player}Spell Property CASMOptionsPower Auto{The power the player can use to open the options menu}CASMKeyWatcher Property CASMKeyWatcherQuest Auto{The quest that watches for CASM's hotkeys being pressed}GlobalVariable Property CASMSave Auto{Set to true while creating a CASM save, then false afterwards.Can be checked on save load to determine whether or not a particular save was made by CASM.Never checked or set directly, only via IsCASMSave}Bool Property IsCASMSave Hidden{Access CASMSave property as though it were storing a Bool instead of an Int}	...EndProperty; Options menu componentMessage Property CASMOptionsMenu AutoMessage Property CASMChangeOptionsMenu AutoMessage Property CASMChangeNumSavesMenu AutoMessage Property CASMChangeQuicksaveOptionsMenu AutoMessage Property CASMChangeAutosaveOptionsMenu AutoMessage Property CASMChangeTriggeredSaveOptionsMenu AutoMessage Property CASMChangeQuicksaveHotkeyMenu AutoMessage Property CASMChangeAutosaveIntervalMenu AutoMessage Property CASMChangeAutosaveConditionsMenu Auto;/ Configuration properties==============================/;Float Property TryAgainDelay = 2.0 Auto{The time (in seconds) to wait after a failed autosave before trying again}Float Property TriggeredSaveBuffer = 10.0 Auto{The minimum length of time (in seconds) required after a CASM save before triggered saves can be made}Float LastCASMSaveTime;/ SKSE properties==============================/;Float Property SKSEVersion Auto Conditional{The version number of SKSE, or 0.0 if it's not present}Bool SKSEMessageShown = falseMessage Property CASMSKSEVersionMessage Auto;/ Option properties==============================/;Int Property NumSaves = 10 Auto{The number of saves through which to cycle}Int SaveIndex = 0; AutosaveBool AutosaveSwitch_val = trueBool Property AutosaveSwitch{Whether or not CASM should autosave periodicallyAlso handles registering and unregistering for updates when set}	...EndPropertyFloat Property AutosaveInterval = 300.0 Auto{The time (in seconds) between CASM autosaves}Bool Property AutosaveCombatSwitch = false Auto{Whether or not CASM should save while the player is in combat}Bool Property AutosaveWeaponSwitch = true Auto{Whether or not CASM should save while the player's weapon is drawn}Bool Property AutosaveSneakSwitch = true Auto{Whether or not CASM should save while the player is sneaking}; QuicksaveBool QuicksaveSwitch_val = trueBool Property QuicksaveSwitch{Whether or not CASM should allow quicksavesAlso handles registering and unregistering of CASMKeyWatcherQuest for updates}	...EndPropertyInt Property QuicksaveHotkey Hidden{Hotkey for quicksavesShortcut for CASMKeyWatcherQuest.SaveKey}	...EndProperty; Tracked stats triggered saveBool Property TrackedStatLocationsDiscoveredSwitch = false Auto{Whether or not CASM should save when the player discovers a new location}Bool Property TrackedStatDungeonsClearedSwitch = true Auto{Whether or not CASM should save when the player clears a dungeon}Bool Property TrackedStatDragonSoulsCollectedSwitch = true Auto{Whether or not CASM should save when the player collects another dragon soul}Bool Property TrackedStatWordsOfPowerLearnedSwitch = true Auto{Whether or not CASM should save when the player learns a new word of power}; Story manager triggered saveBool Property StoryChangeLocationSwitch = true Auto{Whether or not CASM should save when the story manager sends an OnStoryChangeLocation event}Bool Property StoryIncreaseLevelSwitch = true Auto{Whether or not CASM should save when the story manager sends an OnStoryIncreaseLevel event};/ Events==============================/;...

Another thing that I find useful, which depends on your text editor (I strongly recommend http://www.sublimetext.com/), is collapsing sections of code. For example, if I'm writing a series of If/ElseIf conditions, each with their own body of code, I might collapse down the sections that are finished. In that case, my code might look like this in Sublime:
Spoiler
;/ Utility functions==============================/;Function Menu()	Bool bMenu = true	Int iMessage = 0	Int iButton = 0	If !SKSEMessageShown [...]	EndIf	;/0:CASMOptionsMenu		0	1:CASMChangeOptionsMenu				0	2:CASMChangeNumSavesMenu				1	3:CASMChangeQuicksaveOptionsMenu						1	6:CASMChangeQuicksaveHotkeyMenu				2	4:CASMChangeAutosaveOptionsMenu						1	7:CASMChangeAutosaveIntervalMenu						2	8:CASMChangeAutosaveConditionsMenu				3	5:CASMChangeTriggeredSaveOptionsMenu	/;	TraceCASM("Options Menu Opened")	While bMenu		If iMessage == 0 ; CASMOptionsMenu [...]		ElseIf iMessage == 1 ; CASMChangeOptionsMenu [...]		ElseIf iMessage == 2 ; CASMChangeNumSavesMenu [...]		ElseIf iMessage == 3 ; CASMChangeQuicksaveOptionsMenu [...]		ElseIf iMessage == 4 ; CASMChangeAutosaveOptionsMenu [...]		ElseIf iMessage == 5 ; CASMChangeTriggeredSaveOptionsMenu [...]		ElseIf iMessage == 6 ; CASMChangeQuicksaveHotkeyMenu [...]		ElseIf iMessage == 7 ; CASMChangeAutosaveIntervalMenu [...]		ElseIf iMessage == 8 ; CASMChangeAutosaveConditionsMenu			iButton = CASMChangeAutosaveConditionsMenu.Show(AutosaveCombatSwitch as Int, AutosaveWeaponSwitch as Int, AutosaveSneakSwitch as Int)			If iButton == 0 ; Combat				TraceCASM("Autosave combat switch set from  " + AutosaveCombatSwitch + " to " + !AutosaveCombatSwitch)				AutosaveCombatSwitch = !AutosaveCombatSwitch			ElseIf iButton == 1 ; Weapon				TraceCASM("Autosave weapon switch set from  " + AutosaveWeaponSwitch + " to " + !AutosaveWeaponSwitch)				AutosaveWeaponSwitch = !AutosaveWeaponSwitch			ElseIf iButton == 2 ; Sneaking				TraceCASM("Autosave sneak switch set from  " + AutosaveSneakSwitch + " to " + !AutosaveSneakSwitch)				AutosaveSneakSwitch = !AutosaveSneakSwitch			ElseIf iButton == 3 ; Back				iMessage = 4			Else ; Done				bMenu = false			EndIf		EndIf	EndWhileEndFunction
That's over 200 lines of code collapsed down to a state where I can see the general flow of the function without any scrolling, while I work on the contents of the last condition.

Another useful thing that can be done in Sublime, which I only discovered recently, is that if you're using the Papyrus package for Sublime Text (available via the Creation Kit Wiki's http://www.creationkit.com/Sublime_Text_Setup page) you can view and search within a list of the type defined by the script and the functions defined in the script via the keyboard shortcut Ctrl+R (or Ctrl+Shift+P to open the command palette then starting the query with an @ symbol).

For example, in the script I've been using as an example, I see this list:
Spoiler
CASMQuestTypeOnInitOnPlayerLoadGameOnUpdateOnTrackedStatsEventOnStoryChangeLocationOnStoryIncreaseLevelOnSaveKeyPressedMenuRequestCASMSaveRequestCASMSaveGetTimeSinceLastCASMSaveRequestTrackedStatCASMSaveTraceCASMPrintDebugLog
Note that the function RequestCASMSave is listed twice because it's been defined in 2 different states. When a function or event is defined in multiple states, the version of the function that is called will be determined by the scripted object's current state. By default, the version in the empty state will be called.

Perhaps the most useful thing about this search box is that I can use it to quickly jump to a function definition. It uses fuzzy searching so, for example, "@oskp" would find OnSaveKeyPressed, and "@plog" would find both OnPlayerLoadGame and PrintDebugLog. Once I've selected the right choice I can just hit Enter to jump to that section.

You can also set bookmarks in Sublime Text to allow you to easily jump to different sections of a script, but I haven't used those so I can't really tell you much about them.

Looking at the script you've posted, I'd also strongly recommend you make more use of indentation. If you use the most up to date version of the Papyrus package for Sublime Text available on the wiki, it should automatically indent your scripts as you write them.

I hope that helps a bit. I certainly understand what it can feel like to get lost within one of your own scripts once it gets a bit lengthy.

Cipscis
User avatar
Star Dunkels Macmillan
 
Posts: 3421
Joined: Thu Aug 31, 2006 4:00 pm

Post » Mon Nov 19, 2012 11:48 am

Thank you Cipscis!!!!

That's a great deal of advice there!

I'm actually doing heavy use of indentation, empty lines for separation, and commenting, but somehow adding the code to the forum has removed much of it. I'm using Notepad++, but the features that Sublime seems to have sure look interesting! I think I mostly like the part of colapsing parts of code! Also, the setup seems easier than Notepad++. I haven't managed to compile a single script yet from within Notepad++, so I always have to go to the CK and compile there for any minimal change. I'm going to install Sublime right away.

I'm trying to wrap my brain around states right now. I have the feeling that it's something that will help to organize the code on sections and run that code part only on demand without having to rely so heavily on "if's". From what I read from your comments, you use them quite a bit yourself. I hope I'm understanding their use correctly. The time I tried them I failed horribly. What I don't understand about states is how to wrap them around OnUpdate() events. From the wiki, it says that events have to be inside the states, and not the other way around, but that would mean I would have many OnUpdate's in a single script. Does it really work like this?

Example
EVENT onInit()registerForUpdate(3)gotoState ("Start")endEVENTSTATE Start  EVENT onUpdate()  if something == other   gotoState ("Continue1")  else   gotoState ("Continue2")  endifEndEventEndStateSTATE Continue1EVENT onUpdate()  ;do something here  gotoState ("Start")EndEventEndStateSTATE Continue2EVENT onUpdate()  ;do something here  gotoState ("Start")EndEventEndState

Would it really be like this? Or the OnUpdate event should be fired only inside the start state and not in the others?

I'm a little confused with this as I have not yet seen a state example with OnUpdate events.

By the way, I'm studying your great tutorial on states! :smile:

Thanks again for your help!
User avatar
Marine Arrègle
 
Posts: 3423
Joined: Sat Mar 24, 2007 5:19 am

Post » Mon Nov 19, 2012 8:41 pm

The whole point of states is that they allow you to have alternative sets of events. For example, if you have activated a lever and are executing an OnActivated event, you are probably in a state ("IAmOnItAlreadyGiveMeAChance")where you don't want a further call to Activate to do anything, so you would like an OnActivate event that did nothing. Or if you might want several events to do different things if you were in a Cursed state, because of wearing a Cursed Hat of Ruining Everything, for example.

I do wonder why you are doing everything inside a single OnUpdate event, though.
User avatar
Motionsharp
 
Posts: 3437
Joined: Sun Aug 06, 2006 1:33 am

Post » Mon Nov 19, 2012 11:32 am

Mostly because I'm a real newbie with a lot of learning ahead of me... I have read already that the OnUpdate event should not be overused or could cause problems, but at the moment it works for me and I mostly understand it. I don't have alternatives yet that I control or understand...

I don't know much about states yet. But I'm studying them now. The script above might be really messy, but it works... But I know I will break it at some point as soon as I start adding more things to it. That's why I need to reorganize everything, and states might be the answer. I'm going to rewrite that script to use states, and learn them so on the way. I didn't actually think I would get such a script to even run at all, so I already came further than I initially thought.
User avatar
Erika Ellsworth
 
Posts: 3333
Joined: Sat Jan 06, 2007 5:52 am

Post » Mon Nov 19, 2012 9:48 am

The forum has 2 modes for writing a post, and you can switch between them by clicking the switch icon in the upper left hand corner. I always use the simple one, which can be identified by its lack of formatting, use of a fixed-width font, and the fact that all the buttons aside from that switch are disabled. In this mode, the forum shouldn't strip out any of your extra whitespace, so it's perfect for posting code.

When I first started learning and writing Papyrus, during the Creation Kit's beta testing period, I was also using Notepad++. I'd used it while I was modding Fallout 3 and New Vegas as well, and I'd written syntax highlighting definition for that language as well, as well as a bit of maintenance on the Notepad++ syntax highlighting definition for Papyrus. I'd heard about Sublime but hadn't tried it out.

In June, that all changed. I figured I may as well take a quick look at Sublime, since I'd heard about it again and again. Since that quick look I haven't looked back once. It's really fantastic. The Papyrus package for Sublime is pretty good too, and if you're ever interested in looking at what happens to Papyrus when you send it to the compiler I've also written a package for http://skyrim.nexusmods.com/mods/24009/.

I probably sound like I'm trying to sell it to you, which is probably a bit ridiculous and unnecessary since you've already said you're going to install it. It's just that great that I like to write about how awesome it is, haha.

As for states, you seem to have about the right idea. A lot of the time, though, you'll want to use states in the way Ingenue has mentioned, where they're used to essentially disable an event like http://www.creationkit.com/OnActivate_-_ObjectReference which can't be otherwise prevented from being called again before a previous instance of it has finished executing.

States are pretty similar to just a different type of conditional statement. Indeed, you could write a function in the empty state only that checks the state via http://www.creationkit.com/GetState_-_All_Scripts in order to decide what to do instead of calling a different version of the script. The outcome would be the same, but the using states properly is more efficient.

The main limitation of states is, of course, that the script can only have a single state at any one time. Sometimes you'll want to use states, but often you'll find that a conditional statement is better. It's just going to depend on what you're trying to do.

I recommend you take a look at this thread, which shows the dangers of a long http://www.creationkit.com/OnUpdate_-_Form event coupled with http://www.creationkit.com/RegisterForUpdate_-_Form. At the very least, you should be using a "chain" of http://www.creationkit.com/RegisterForSingleUpdate, like this:
Spoiler
Event OnInit()	RegisterForSingleUpdate(3)EndEventEvent OnUpdate()	; Do stuff	RegisterForSingleUpdate(3)EndEvent

Also note that, instead of sending your scripted object into a particular state when your script starts, you can specify one state as the "auto" state, and the object will enter that state whenever it is initialised or reset:
Spoiler
Function ReportState()	Debug.Notification("I'm in an unrecognised state: \"" + GetState() + "\"")EndFunctionAuto State StartState	Function ReportState()		Debug.Notification("I'm in the \"StartState\" state")	EndFunctionEndState

Cipscis
User avatar
Jennifer Rose
 
Posts: 3432
Joined: Wed Jan 17, 2007 2:54 pm

Post » Mon Nov 19, 2012 10:59 am

Well, I downloaded and installed Sublime right after you told me about it, and you're right, it really is great. Alone the possibility to collapse parts of code is extremely helpful! Also, highlighting and autocompletion seem better, and I managed to make it compile the scripts (and found out why Notepad++ wasn't...). This tool is really making it easier for me!

About states... I have been playing around with them and they seem to be what I was looking for. But I also found limitations and I have already found a wall that is making me crazy. I have fragmented the code I posted at the beginning in two states. The first state chooses through conditions the different situations I want to code, and the following states will contain a longer script with it's scene playing form start to finnish. Maybe some future scenes might need several states, but the one I have now is short enough to fit into one. I have dropped the OnUpdate event and I'm starting the state through the OnBeginState.

The wall I've hit is the following. I have in the original script a spell firing every 3 seconds inside the OnUpdate event and catching nearby NPC's with LOS. I have now moved this spell into the main state, with exactly the same conditions as before (copy/paste). But inside the state it just isn't firing! I have added a Utility.Wait(1) after the spell to give it time to fill the Form List, just like before, but it just doesn't want to fire... The debug notification right after the state does show... I also tried changing PlayerRef for game.getplayer() and viceversa, as this had been an issue before with the spell script trying to fill the form lists, but nothing.

I think it might be because the spell was firing before once each 3 seconds and now it's trying to fire it too often and failing. Could this be the reason? And if it is, how to solve it? Could I place the spell inside a state with OnUpdate event and fill the form list there instead? Can I then pass this info into the main state?

And to test the second edit mode from the forum that you said (nice), here is how the code looks now, although I still have to pass over a couple of things:
Spoiler
Scriptname AAaaDielosRStarter extends Quest ConditionalQuest Property AADielosRQuest  AutoQuest Property AADielosStarterQuest  Auto    Alias Property AliasAttacker  AutoActor Property PlayerRef  Auto Scene Property SceneFollow  Auto    Package Property PackageKeepEye  AutoPackage Property PackageForceGreet  Auto  int Property ApproachToTalk Auto Conditionalint Property SceneStarted Auto Conditionalint Property DropItemsVar Auto Conditionalint Property SpellScriptFinished Auto ConditionalSPELL Property FindLOSSpell  AutoFormList Property AAStalkerFindLOSFormList  Auto  int SceneUpdateCount = 0EVENT onInit()	registerForUpdate(1)	gotoState ("Start")endEVENT;EVENT OnUpdate() ;Things to check every second;endEVENT;Conditions to check for starting scenes, like location or time of dayAuto STATE Start 	EVENT onBeginState()		Debug.Notification("Start State Running")		gotoState ("NightTownStalking")	EndEventEndStateSTATE NightTownStalking	EVENT onBeginState()		if SceneStarted == 0			;***********************************************************Here come conditions to start Stalkers like time of day and location (check keyword locations in wiki)			Debug.Notification("Starting quest, scene, and trying to grab aliases...")			if AADielosRQuest.IsRunning() == False				AADielosRQuest.Start()			EndIf			if (AliasAttacker As ReferenceAlias).GetRef() == None || (AliasAttacker As ReferenceAlias).GetActorReference().IsDead() && ApproachToTalk != 1				Debug.Notification("I'm reseting the quest...")				ResetQuest()			endif			if AADielosRQuest.IsRunning() && SceneStarted == 0 && (AliasAttacker As ReferenceAlias).GetRef() != None && ApproachToTalk != 1				float TimeOfDay = GetCurrentHourOfDay()				int RandomChance = Utility.RandomInt() ;Not working???				if TimeOfDay >= 18 || TimeOfDay <= 6 ;&& RandomChance <= 100					Debug.Notification("Alias found during the night and starting scene...")					SceneFollow.Start()					SceneStarted = 1				Else					Debug.Notification("No stalkers during the day...")					ResetQuest()				EndIf			endif		EndIf		if SceneStarted == 1			if (AliasAttacker As ReferenceAlias).GetActorReference().GetCurrentPackage() == PackageKeepEye				DebugPrintStalkerName()				Utility.Wait(1)				FindLOSSpell.cast(PlayerRef)				Debug.Notification("Firing spell...")				Utility.Wait(1)				if AAStalkerFindLOSFormList.GetSize() == 1 && AAStalkerFindLOSFormList.HasForm((AliasAttacker As ReferenceAlias).GetReference())					SceneUpdateCount = AAStalkerFindLOSFormList.GetSize()					Debug.Notification("The LOS list has " + SceneUpdateCount + " and the only entry is the Stalker, so it applies...")					ApproachToTalk = 1					Debug.Notification("ForceGreet should start now...") 				ElseIf AAStalkerFindLOSFormList.GetSize() == 0					SceneUpdateCount = AAStalkerFindLOSFormList.GetSize()					Debug.Notification("The LOS list has " + SceneUpdateCount + " and the Stalker is still following, so it applies...")					ApproachToTalk = 1					Debug.Notification("ForceGreet should start now...") /;				Else					Debug.Notification("ForceGreet doesn't apply...")				EndIf				SceneUpdateCount = 0				AAStalkerFindLOSFormList.Revert()			endif		endif		GoToState ("NightTownStalking")	endEVENTEndSTATEState Busy	Function ResetQuest()		; Do nothing	EndFunctionEndState;*************************** F U N C T I O N S **********************************string Function DebugPrintStalkerName() 	String AliasName = (AliasAttacker As ReferenceAlias).GetReference().GetBaseObject().GetName()	if AliasName == ""		Debug.Notification("The stalker is a stranger...")	Else		Debug.Notification("The stalker is: " + AliasName)	EndIfEndFunctionfloat Function GetCurrentHourOfDay() 	float Time = Utility.GetCurrentGameTime()	Time -= Math.Floor(Time) ; Remove "previous in-game days passed" bit	Time *= 24 ; Convert from fraction of a day to number of hours	Return TimeEndFunctionfunction ResetQuest()	;**************Add Cooldown token to alias before clearing!!!	GoToState("Busy")	(AliasAttacker As ReferenceAlias).Clear()	AADielosRQuest.Stop()	Utility.Wait(1)	AADielosRQuest.Start()	SceneFollow.Stop()	SceneStarted = 0	ApproachToTalk = 0	SceneUpdateCount = 0	gotoState ("Start")endFunction  
User avatar
Emma Louise Adams
 
Posts: 3527
Joined: Wed Jun 28, 2006 4:15 pm


Return to V - Skyrim