Dwip's Guide to Scripting

Post » Tue May 18, 2010 11:29 pm

I have to say you're example script is the most helpful bit I have ever seen on scripting! You should be a teacher! Infact you did such a good job explaining, I would like you to go into more detail :) I have not found a wiki anywhere near as informitive, and mostly because you did the next closest thing to a visual representation by using the code box, using a dummy script, but using detailed notes to provide information on what that specific line of script was doing!
You are a brilliant person sir! (like I said in my post, I would give whoever wrote me a guide on scripting a lot of praise. and this is not because I am svcking up to you, but honestly, I cannot be thankful enough you would donate your time to do such.)

But I cannot withhold this cornerstone of scripting info from everyone, so that is why I am creating a new thread, as recommended by Dwip himself, so everyone can read for those who are not exactly the brightest at scripting (like me).

LESSON 1:

Spoiler
I'd go roughly in this order trying to puzzle things out:

1. Script types - Quest, Object, Magic Effect, quest stage, dialogue. Quest scripts are tied to quests, only run when that quest is running, but are available globally, as opposed to Object scripts, which are tied to objects (NPCs/weapons/armor/activators/etc), and only run when that particular object is actively loaded or referenced. Magic Effect scripts only work with spells. Quest stage and dialogue scripts only run one time when the quest stage or dialogue is triggered.

2. Blocktypes - I won't go into all of them here, but you should know how gamemode blocks work (by looping over and over - quests at 5 seconds by default, objects I think every frame). You won't use most of the others nearly as often.

3. Variables - shorts, floats, refs, and I think one other. Each type has a specific meaning (shorts are whole numbers, floats are decimal numbers, refs are object references), and can do just about anything. Doonce is a variable.

4. If/Else/Elseif/Endif - The building blocks of scripts. If CONDITION, do stuff, Elseif CONDITION do other stuff, Else do other stuff, ending with an Endif.

5. Basic Conditions - == (EQUAL TO), != (NOT EQUAL TO), > (GREATER THAN), < (LESS THAN), >= (GREATER THAN OR EQUAL TO), <= (LESS THAN OR EQUAL TO). Have a variety of uses, but typically used with condition functions like GetStage or GetIsID, or with variables like Doonce. When a script has something like "If Doonce == 1" it's saying to only run the code following it if Doonce has a value of 1 (because you set it to 1 using Set somewhere else). If it has a value of 0 or 2 (or whatever), it won't be true and the code won't run.

6. Complex Conditions - && (AND), and || (OR), which work like, say, "If Doonce == 1 && player.getitem gold001 >= 100". In that line, the code following the if would run only if both Doonce was equal to 1 AND the player had 100 or more gold. By contrast, "If Doonce == 1 || player.getitem gold001 >= 100" would run if either one of those conditions was true.

In the specific case of Doonce, it's usual method of usage is something like this:
scn ExampleScript ; Comments are denoted by a semicolon.  scn is short for scriptname - the name of the scriptshort Doonce ; you always have to define your variables, usually at the start of a scriptbegin gamemode ; blocktype.  A gamemode block will run repeatedly  if Doonce == 0 ; only run the following code if Doonce is equal to 0.  Variables are 0 by default.    player.additem gold001 100 ; Adds 100 gold to the player    set Doonce to 1 ; sets Doonce to 1.  The next time the script runs, Doonce will no longer be 0, and this part won't run again  Else    player.removeitem gold001 100 ; removes 100 gold from the player ; Because of the else, this only runs if Doonce is not 0  Endifend ; ends the block, but not the script.  You could add more blocks after this if you wanted.


In that bit of code, the script would run, adding 100 gold to the player, and on the next and all runs after, it would remove 100 gold from the player.

User avatar
Scarlet Devil
 
Posts: 3410
Joined: Wed Aug 16, 2006 6:31 pm

Post » Wed May 19, 2010 3:29 am

Glad it helps. Figuring out what was what in scripting and questing took a lot of pain for me at the start, so if I can help somebody else avoid a little bit of that...

What would you like me to go into more detail about?

[edit] Slightly annotated version of my previous post: [/edit]

I'd go roughly in this order trying to puzzle things out:

1. Script types - http://cs.elderscrolls.com/constwiki/index.php/Quest_scripts, http://cs.elderscrolls.com/constwiki/index.php/Object_scripts, http://cs.elderscrolls.com/constwiki/index.php/Magic_effect_scripts, quest stage, dialogue. Quest scripts are tied to quests, only run when that quest is running, but are available globally, as opposed to Object scripts, which are tied to objects (NPCs/weapons/armor/activators/etc), and only run when that particular object is actively loaded or referenced. Magic Effect scripts only work with spells. Quest stage and dialogue scripts only run one time when the quest stage or dialogue is triggered.

2. http://cs.elderscrolls.com/constwiki/index.php/Category:Blocktypes - I won't go into all of them here, but you should know how gamemode blocks work (by looping over and over - quests at 5 seconds by default, objects I think every frame). You won't use most of the others nearly as often.

3. http://cs.elderscrolls.com/constwiki/index.php/Declaring_variables - shorts, floats, refs, and I think one other. Each type has a specific meaning (shorts are whole numbers, floats are decimal numbers, refs are object references), and can do just about anything. Doonce is a variable.

4. http://cs.elderscrolls.com/constwiki/index.php/If - The building blocks of scripts. If CONDITION, do stuff, Elseif CONDITION do other stuff, Else do other stuff, ending with an Endif.

5. Basic Conditions - == (EQUAL TO), != (NOT EQUAL TO), > (GREATER THAN), < (LESS THAN), >= (GREATER THAN OR EQUAL TO), <= (LESS THAN OR EQUAL TO). Have a variety of uses, but typically used with condition functions like GetStage or GetIsID, or with variables like Doonce. When a script has something like "If Doonce == 1" it's saying to only run the code following it if Doonce has a value of 1 (because you set it to 1 using Set somewhere else). If it has a value of 0 or 2 (or whatever), it won't be true and the code won't run.

6. Complex Conditions - && (AND), and || (OR), which work like, say, "If Doonce == 1 && player.getitem gold001 >= 100". In that line, the code following the if would run only if both Doonce was equal to 1 AND the player had 100 or more gold. By contrast, "If Doonce == 1 || player.getitem gold001 >= 100" would run if either one of those conditions was true.

In the specific case of Doonce, it's usual method of usage is something like this:

scn ExampleScript ; Comments are denoted by a semicolon.  scn is short for scriptname - the name of the scriptshort Doonce ; you always have to define your variables, usually at the start of a scriptbegin gamemode ; blocktype.  A gamemode block will run repeatedly  if Doonce == 0 ; only run the following code if Doonce is equal to 0.  Variables are 0 by default.    player.additem gold001 100 ; Adds 100 gold to the player    set Doonce to 1 ; sets Doonce to 1.  The next time the script runs, Doonce will no longer be 0, and this part won't run again  Else    player.removeitem gold001 100 ; removes 100 gold from the player ; Because of the else, this only runs if Doonce is not 0  Endifend ; ends the block, but not the script.  You could add more blocks after this if you wanted.


In that bit of code, the script would run, adding 100 gold to the player, and on the next and all runs after, it would remove 100 gold from the player.
User avatar
Evaa
 
Posts: 3502
Joined: Mon Dec 18, 2006 9:11 am

Post » Tue May 18, 2010 2:58 pm

hmmm...well usually I can't think of things about scripting until I need it. But, I suppose you could try to expand on what you last said and perhaps a little more about scripting a quest :) since that is what I am going to be working on a lot.
Perhaps the different blocks?
User avatar
Cagla Cali
 
Posts: 3431
Joined: Tue Apr 10, 2007 8:36 am

Post » Tue May 18, 2010 2:41 pm

Just thought I'd chime in with something I've done recently and found it very helpfull.

I had a mod that had lots of object scripts that required variable values from each other. I decided to create a quest script that had no Begin or End block but is just a list of variables. Like float, short, long etc.

The scripts can then, instead of just making their own variables us myQuest.aVariable and set a variable that can then be used by any other of the scripts should they need to know the position of another script.

This has really cut down on my script sizes too.

Another thing I've found useful is to keep to a good quality coding convention. Like giving every new thing I make a name that starts with the initials of my mod and giving variables names that are meaningful to their purpose. Like, instead of doOnce and doOnce2, I could use doOnceMyMerchantChestActivation and doOnceMessageBoxStart etc.

Also I now give short variables a lowercase 's' at the start and float variables a lower case 'f' and so on.
User avatar
Danii Brown
 
Posts: 3337
Joined: Tue Aug 22, 2006 7:13 am

Post » Wed May 19, 2010 6:15 am

@Fillythebish - I hadn't thought of making a script with nothing but variables. Could be a pretty good idea, although I prefer keeping mine in their "home" script. Depends on how you like to organize things, I imagine. Also depends on the variables. Obviously no sense in putting every single Doonce in there, but there are lots of other things.

I'm going to talk about naming conventions at length here in a sec (along with several other things), but I didn't mention the s/f/r/l letters on variables. It's good practice, definitely, although one I'm guilty of not ever following in Oblivion scripting. I do it religiously in VB or C, just not here for some reason.

@Ntom - I don't know that there's a lot I can say about blocktypes besides what's in that CS Wiki article I linked. Read what's linked there and you'll know more than I can conveniently tell you.

How about I just go through and dissect a quest and talk about whatever randomly comes up. And more on that in the next post, or maybe even posts, because I just spent about 4 hours writing.

...hey, and that's post 100. Rank change, woo!
User avatar
Add Me
 
Posts: 3486
Joined: Thu Jul 05, 2007 8:21 am

Post » Tue May 18, 2010 11:38 pm

Because I'm familiar with it, let's go through SQ09, Go Fish - the quest Aelwin Merowald gives you in Weye. You'll want to have the CS open to follow along. I'll go through the basics of each window, and then I'll actually dissect the quest.

Start with the http://cs.elderscrolls.com/constwiki/index.php/Quest_Data_Tab tab. Most of this is pretty self-explanatory, although I want to hit on a few things:

Spoiler

1. Priority has to do with which dialogue takes precedence over which other dialogue, and the CS Wiki article discusses it pretty well. You can see this in action if you look at the dialogue window of any NPC - note how all the crime stuff is at the top, followed by other stuff. This is important, because unmodified by conditions, the topmost line of dialogue in any topic will be said first. I'll try to come back to that point - for the moment just accept that it exists.

There are some oddities in priority (most of the NQD prefix quests are 12 or 13 for whatever reason), but most of the time you can set this to somewhere in the 50-60 range and be ok.

2. Of the three checkboxes, you don't see a lot of use of the Allow repeated... boxes, and I can't really speak to their use offhand, but the Start Game Enabled box is important. Here's the difference:

- Quests that are enabled run their scripts (which you set via that little dropdown there), and their dialogue can be said by NPCs.

- For quests that are disabled, their scripts do not run (although you can still reference variables in said scripts, more on that later), and their dialogue WILL NOT be said by NPCs.

That last bit can run you into problems if you want to, say, have random people around town react to the end of the quest, such as the various reactions to the end of the main quests. If you've ever wondered about why the various FIN suffixed quests exist, this is why. If you want an example, check out the SQFIN quest. Aelwin Merowald has a greeting in that (the very bottom one in the GREETING topic) that's set to play after you complete the quest.

Two script functions that matter for this:

- StartQuest XX (ie, StartQuest SQ09) enables a quest. The script starts running, and its dialogue becomes active. Usually you put this at the end of another quest to start up the next quest in a chain (such as all the MQ and guild quests), but sometimes you might put it on an item, say, so you could pick up a book and start a quest.

- StopQuest XX (ie, StopQuest SQ09) disables a quest. The script stops running, and the dialogue isn't available. Usually you do this at the end of a quest for optimization reasons - you don't want dialogue sticking around forever, and scripts that are continually running hog memory and slow things down. We'll come back to this one later.

3. Last thing on the Quest Data tab I want to talk about is the Quest Conditions box. The conditions in here are added to every single piece of dialogue in that quest, in addition to whatever other conditions that dialogue might have. Two usual ones, GetPlayerInSEWorld == 0 and GetIsPlayableRace == 1. These mean that quests won't have dialogue in Shivering Isles, and only playable races (ie, not dremora) will have dialogue.

4. Actually, I lied. Notice that the quest is named SQ09 for "Settlement Quest 09". You're going to be a lot happier if you adopt a naming convention of this sort for pretty much everything you create specially for your mod. On the one hand, it helps avoid conflicts, but more importantly it helps you find your stuff. I prefix all of my NPCs, objects, quests, and scripts with "AFK". Other people use their initials, you could probably just use Ntom. Whatever you do, don't be lazy and use "aaa", because a ton of people do, and it's bad practice.

To further help yourself out, get in the habit of being consistent in your naming. Notice how the script for SQ09 is called SQ09Script. To take an example from my own work, I have an AFKLuciusDecimus NPC who has a script called AFKLuciusDecimusSCRIPT. Again, this makes it a lot easier to tell goes with what, and once you start looking at a few dozen scripts, it gets difficult to tell what's what without naming conventions.



Moving over to the http://cs.elderscrolls.com/constwiki/index.php/Quest_Stages_Tab

Spoiler

1. Indexes, what we usually call quest stages, are numbers 1-255. For most quests, most of these are multiples of 10 - 1, 10, 20, 30, 40, with the stage that completes the quest being 100, and stages that make the quest impossible to complete are 200. Slightly alternate stages are usually stages like 15, 25, etc, and when quest stages are used to create cutscenes, they usually drop to sequential values like 1, 2, 3, 4, 5, etc. The tutorial quest is a great example of this last version in action.

There's no real hard or fast rule about doing things this way, but it works well enough. As long as you understand what's going on, it's not really important.

2. Each Index has its own set of Log Entries, which is what that box that says "Log Entry | Conditions" is all about. You can have multiple Log Entries for a given quest stage, and I'll talk a little bit more about that in a minute. First let's talk about the other things in this tab.

The box confusingly called Log Entry (the big white one), is the journal text for that log entry, which obviously gets put in the player's journal in game. You don't have to put anything at all in this box, which is pretty common when you just want to set a quest stage for the Result Script box. Most of SQ09 works like this, so we'll come back here in a while.

The Result Script box is a script that gets run one time and one time only - when the quest stage gets triggered. Most of the time this is either nothing or a few short commands, although it is possible to use if/elseif/else blocks in result scripts. You will NOT, and I repeat NOT, be using blocktypes (like gamemode) or defining variables (like "short Doonce") here, although this is a common place to SET the value of already existing variables, and more on that later probably.

Like everything else, it's possible for a log entry to have conditions, which is why there's a conditions box. This is usually blank, but sometimes it's useful to have multiple possible journal entries for a stage during complex quests. SQ09 doesn't make use of that, but MS13, the quest to rescue Farwil Indarys, makes a lot of use of it. It's a little advanced, so I won't cover it now - just note that usually you'll be ignoring this box.


The http://cs.elderscrolls.com/constwiki/index.php/Quest_Targets_Tab Tab is pretty simple:

Spoiler

1. The box up top is where you put in quest targets, and shows their conditions. When you first create one, it will say Bad Target and have no conditions. That's what the Cell and Ref dropdowns are for, or you can hit the "Select Reference In Render Window" button, which will give you a crosshairs and put you in the render window.

The only thing I want to note is that the only things that can be quest targets are persistent objects. There are some subtleties to persistent objects, but the rule of thumb is that anything you want to reference via script, AI pack, or the like needs to be persistent. NPCs, XMarkers, and a couple other things are already persistent, but other objects you'll need to open up in the render window and click a box to make them persistent.

You can set any number of conditions for each quest target, and here you SHOULD set conditions or else you'll have things marked with floating green arrows forever. Usually this is a GetStage check (all the targets in SQ09 work this way, but it's pretty common to use GetStageDone (more on this perhaps later), GetQuestVariable (ditto), or other creative options.


On to the http://cs.elderscrolls.com/constwiki/index.php/Topics_Tab Tab.

Spoiler

This is one of the most important sections of the entire CS, and you'll be spending a lot of time here. I won't spend a lot of time on dialogue editing right now, but it's a highly important topic, and if you're at all unsure about it you should ask me about it. There's a lot of quirks.

1. The basic unit of dialogue is a Response - one line of dialogue, usually about a sentence or so. These are grouped into Infos, which are the level at which you set conditions, add scripts, and the like. Infos are in turn grouped into Topics, which exist to group dialogue within a quest, or in the case of Topics like GREETING, across multiple quests.

The only special Topic that you need to care about is GREETING, which gets played when an NPC first talks to you - things like "Hi!" go here. For most purposes, this is the only vanilla Topic you will ever, ever want to put dialogue in - most of the time you'll create your own Topics specific to each quest, which is what SQ09 does.

- Note the Topic Text box, which is where you'll put the line that the player is nominally saying to the NPC, such as "Bed" or "Fine weather today!" or "Die you evil villain!" GREETING should always be GREETING, however.

2. Each Topic has a number of Infos. As I said before, this is the level of dialogue at which almost everything takes place. Lots of things to look at here:

- The Response Text list box is where you'll add responses, which are again single sentences. These will get read by the NPC one after the other. There are things you can do to and with responses, but they're not important for our purposes, so we'll ignore them. Again, I can talk more in detail about dialogue editing if you want.

- The Result Script box is a place where you can put a script that gets run whenever that Info gets said. It's almost exactly like the Result Script box I talked about in the Quest Stages Tab. This is a handy spot to set quest stages, among other things.

- The Conditions box should look familiar, since everything else in the world has one. At the very minimum, you'll want to have a GetIsID condition in here for the speaker you want to speak the Info, and generally something like GetStage as well to tell them WHEN to speak it. More on this later.

- There are a bunch of checkmarks on the side, almost all of which you won't ever care about. Say Once and Goodbye are important however. Say Once does just what it says - the speaker will only ever say that Info once and once only. Goodbye exits dialogue immediately after the line is said, which is useful for, say, having your villain give an Evil Expository Speech and then attack.

- The Add Topic box will add a Topic to the list of those available to be said. This is important, because besides GREETING, topics aren't automatically available - you have to add them first. Note that because of the http://cs.elderscrolls.com/constwiki/index.php/Fixing_the_AddTopic_Bug affecting GREETING, I don't recommend using this box. Use an AddTopic function in the Result Script box instead. Vanilla Oblivion doesn't need to worry about this, but you do.

- Choices is a list of Topics that you'll have the choice of selecting. In fact, they're the ONLY topics you'll have the choice of selecting, meaning you lose the ability to ask about, say, rumors or other topics until you pick. If I remember correctly, you can't even exit the conversation while making a choice. This is how you create dialogue trees, and since SQ09 makes a lot of use of them, we'll be coming back here.

- Link From and Next Speaker are irrelevant for Topics.


Conversation, Combat, Persuasion, Detection, Service, and Misc. are mostly like dialogue, but are completely and utterly useless to you (with the exception of the INFOGENERAL topic in Conversations, which controls Rumors) without the addition of voice acted dialogue, so I won't be talking about them today.

Next up, I'll actually go through SQ09.
User avatar
Jade Barnes-Mackey
 
Posts: 3418
Joined: Thu Jul 13, 2006 7:29 am

Post » Tue May 18, 2010 3:10 pm

The basic plot of SQ09 is described in http://www.uesp.net/wiki/Oblivion:Go_Fish. Basically, the first time you talk to Aelwin Merowald in Weye, he tells you a sob story about how he needs to collect a bunch of slaughterfish scales, and can you help. You have a couple of chances to decline, or else you find yourself killing slaughterfish in Lake Rumare for their scales. When you get enough, return to Aelwin Merowald for the reward.

What I'm going to attempt to do here is to describe each piece of SQ09 seperately, and then I'm going to attempt to put it all together to show you how the pieces interact with each other. Hopefully it will all make sense at the end of the day.

First stop, the quest script, SQ09Script. I'll make my own comments in the script, with long ones afterwards denoted by a footnote, [FN1] or similar.

Spoiler

Scriptname SQ09Script ; Script name.  All non-Result Script box scripts must have this.  Can be abbreviated as scn.;Variable declarations. It's good practice to put them at the start of a script like this.short ScaleCount ; Variables. This one keeps track of how many slaughterfish scales the player has.short SQ09Complete ; Another variable. When 0, means SQ09 is not done, when 1 means that it is done.  Important as a dialogue condition later.begin gamemode ; Gamemode block.  Everything in here is getting run every 5 seconds. [FN1];Enables SlaughterfishScales as a quest object [FN2]if ( GetStage SQ09 >= 10 ) && ( GetStage SQ09 <= 90 ) ; [FN3]	set ScaleCount to Player.GetItemCount SQ09SlaughterfishScales	set SQ09Complete to 0endif;Tracks scales collected and advances stage [FN4]if ( GetStage SQ09 >= 10 ) && ( GetStage SQ09 <= 90 )	if ( ScaleCount == 1 )		setstage SQ09 15	endif	if ( ScaleCount == 2 )		setstage SQ09 20	endif	if ( ScaleCount == 3 )		setstage SQ09 25	endif	if ( ScaleCount == 4 )		setstage SQ09 30	endif		if ( ScaleCount == 5 )		setstage SQ09 35	endif	if ( ScaleCount == 6 )		setstage SQ09 40	endif	if ( ScaleCount == 7 )		setstage SQ09 45	endif	if ( ScaleCount == 8 )		setstage SQ09 50	endif	if ( ScaleCount == 9 )		setstage SQ09 55	endif	if ( ScaleCount == 10 )		setstage SQ09 60	endif	if ( ScaleCount == 11 )		setstage SQ09 65	endif	if ( ScaleCount == 12 )			setstage SQ09 90	endifendif;Disables SlaughterfishScales as a quest object and activates slaughterfish spawners [FN5]if ( GetStage SQ09 >= 100 ) && ( SQ09Complete != 1 )	SetQuestObject SQ09SlaughterfishScales 0	SQ09SlaughterfishTarget01.enable	SQ09SlaughterfishTarget06.enable	SQ09SlaughterfishTarget12.enable	set SQ09Complete to 1endifif ( GetStage SQ09 >=10 ) && ( GetStage SQ09 < 100 ) && ( SQ09Complete != 1 ) ; [FN6]	if ( AelwinMerowaldRef.GetDead == 1 )		setstage SQ09 200		set SQ09Complete to 1	endifendifend



[FN1] - You can modify how often quest scripts run by declaring the variable http://cs.elderscrolls.com/constwiki/index.php/FQuestDelayTime (with float fQuestDelayTime in your variable declarations), then using set QuestName.fQuestDelayTime # (SQ09.fQuestDelayTime for SQ09). This is somewhat advanced, and you don't need to do it unless you need to go through a gamemode loop very very fast (or very very slow). Useful for cutscenes.

[FN2] - Actually, it doesn't set anything to a quest item, because the item in question, the ingredient SQ09SlaughterfishScales, is already set to be a quest item. Go look at it. Note the check box. There are some intricacies to quest items, but the most important for inventory objects is that you can't drop them.

[FN3] - What this if statement is saying is that IF the quest stage of SQ09 is 10 or higher, AND the stage of SQ09 is 90 or lower (in other words, between 10-90, and this is the basic way you check ranges), set the variable ScaleCount to however many of the item SQ09SlaughterfishScales the player has in inventory. At the start of the quest, that number will be 0. At the end of the quest, it's going to be 12, as we'll see.

Note that we're in a gamemode block, so the game is performing this check every 5 seconds.

[FN4] - This does what it says on the box, albeit in a very inefficient manner that I'll talk about in a little while. Note that this is what's called a nested if. IF the stage of SQ09 is between 10 and 90 (see FN3), do each of the "if ( ScaleCount == #)" checks, each of which says IF the variable ScaleCount is exactly equal to a number, set the quest stage of SQ09 to 15 or 20 or whatever the number happens to be.

Note that because of the nature of nested ifs, IF the stage of SQ09 is NOT between 10 and 90, none of those other "if ( ScaleCount == #)" checks will be processed at all.

[FN5] - Again, this does what it says it does. Walking through it, IF the stage of SQ09 is greater than or equal to 100, AND the variable SQ09 is NOT equal to 1 (which it will be right now, because all newly defined variables are 0), SetQuestObject SQ09SlaughterfishScales 0 (which removes that Quest Item checkbox on the ingredient), and enable SQ09SlaughterfishTarget01, 06, and 12 (all of which are specific copies of the leveled creature SQ09RumareSlaughterfish1), plus set the variable SQ09Complete to 1, thus preventing this section of the script from running again the next time the gamemode runs.

If you go to the Leveled Creature bit of the Object Window tree in the CS, and scroll down to SQ09RumareSlaughterfish1, then right click it and go to Use Info, then double click any of those, the CS will crank for a bit and you'll find yourself face to face with a big pink M. Double click that, and you'll see three important things:

- The Reference Editor ID is filled in with something like SQ09SlaughterfishTarget02;
- The Persistent Reference box is checked;
- Initially Disabled is also checked.

In order, what those mean:

- A non-blank Reference Editor ID lets you use that reference in scripts, like we're doing, provided that...
- Persistent Reference is checked to make it persistent;
- Initially Disabled means its disabled, which is a sort of Schrodinger-like existance where the reference exists, and exists in that spot, but since it isn't enabled, it doesn't do anything - move, run scripts, anything. Setting it to enabled changes that, and it becomes a normal thing (in this case a slaughterfish) that does everything else normal things do.

[FN6] - IF the stage of SQ09 is between 10 and 99 (see that < in GetStage SQ09? That's different than <=, and means that 100 will NOT satisfy the condition) AND the variable SQ09Complete is NOT equal to 1, do the nested if, which is IF AelwinMerowald (or more preceisely his reference in the world, and go double click Aelwin Merowald to see), is dead (1 is true and 0 is false for many checks like this, including GetDead), set the stage of SQ09 to 200 and set the variable SQ09Complete to 1.

After this, the script completes, and in 5 seconds we'll do that whole block over again.

Now, let's take a minute to talk about efficient scripting practices. This is a terrible way to write a script, and here's why.

First, all of those parenthesis are complete irrelevant. It's possible to do order of operations stuff in scripts using parenthesis, such as If ( Condition1 && Condition2 ) || Condition3, but they're not doing anything here except for cluttering up the script. Which is fine if you want it to look that way, because they aren't hurting anything, but you don't technically NEED to put them in there.

Second, you should be aware that every condition you put in a script is work that the script, and therefore your computer, has to do, in this case every 5 seconds. It's not really a big deal on the level of an individual script, but everything you can do to not have things in gamemode blocks or hidden behind single if checks is helpful.

For example, you see the stuff under FN3 and FN4 and how they have the same conditions? You could combine those two sections and it would take half the work.

Even better, it's possible to eliminate vast portions of this script entirely via the following:

Spoiler

First, you add an object script to the ingredient SQ09SlaughterfishScales that goes something like this:

scn SQ09SlaughterfishScalesSCRIPTbegin OnAdd player ; This is an [url=http://cs.elderscrolls.com/constwiki/index.php/OnAdd]OnAdd block[/url] limited to the player, which means the script runs whenever the player adds this item to their inventory.if GetStage SQ09 >= 10 && GetStage SQ09 < 100  set SQ09.ScaleCount to SQ09.ScaleCount + 1 ;[FN7]  if SQ09.ScaleCount == 1 ; [FN8]    setstage SQ09 15  elseif SQ09.ScaleCount == 2    setstage SQ09 20  elseif SQ09.ScaleCount == 3    setstage SQ09 25  elseif SQ09.ScaleCount == 4    setstage SQ09 30  elseif SQ09.ScaleCount == 5    setstage SQ09 35  elseif SQ09.ScaleCount == 6    setstage SQ09 40  elseif SQ09.ScaleCount == 7    setstage SQ09 45  elseif SQ09.ScaleCount == 8    setstage SQ09 50  elseif SQ09.ScaleCount == 9    setstage SQ09 55  elseif SQ09.ScaleCount == 10    setstage SQ09 60  elseif SQ09.ScaleCount == 11    setstage SQ09 65  elseif SQ09.ScaleCount == 12    setstage SQ09 90  endifendifend


[FN7] - You can reference variables in scripts attached to quests or persistent objects via NAME.VARIABLE, where NAME is the name of the quest or object, in this case SQ09, and NOT the name of the script. This bit of math is setting the value of ScaleCount to whatever it was before, plus one to account for the object we're adding to the player just now.

[FN8] - I prefer to write long blocks of if checks on a single variable like this using elseif. You don't have to, but it's a lot more compact and clean.

Here's why we did that. That whole section in the gamemode block of SQ09Script was 14 condition checks running every 5 seconds during the greater part of the quest, which is a lot. By moving all of that code to an OnAdd block in an object script, we make sure that all of that code ONLY runs when the object (SQ09SlaughterfishScales) is added to the player's inventory, which will only happen a few times. The rest of the time, the code doesn't run, and we're saving some energy.

Also, you can eliminate the whole section under [FN6] by making an object script for Aelwin Merowald that looks like this:

scn SQ09AelwinMerowaldSCRIPTbegin OnDeath ; the [url=http://cs.elderscrolls.com/constwiki/index.php/OnDeath]OnDeath[/url] block runs when the actor is killedif GetStage SQ09 >=10 && GetStage SQ09 < 100 && SQ09.SQ09Complete != 1  setstage SQ09 200  set SQ09Complete to 1endifend


We're performing the same thing the other block was performing, only it only runs when and if Aelwin Merowald dies for whatever reason, instead of every 5 seconds. We can also eliminate that check to see if Aelwin is dead, because if this block is being called, he obviously is.

You could also move everything under FN5 as-is to the Result Script blocks of all of the stages 100 or above in SQ09 and free up some more room, but we'll pass on that for the moment. At the end of the day, our cut down SQ09Script looks like this:

Scriptname SQ09Scriptshort ScaleCountshort SQ09Completebegin gamemodeif ( GetStage SQ09 >= 10 ) && ( GetStage SQ09 <= 90 )	set ScaleCount to Player.GetItemCount SQ09SlaughterfishScales	set SQ09Complete to 0endif;Disables SlaughterfishScales as a quest object and activates slaughterfish spawnersif ( GetStage SQ09 >= 100 ) && ( SQ09Complete != 1 )	SetQuestObject SQ09SlaughterfishScales 0	SQ09SlaughterfishTarget01.enable	SQ09SlaughterfishTarget06.enable	SQ09SlaughterfishTarget12.enable	set SQ09Complete to 1endifend


Lots better, isn't it?


Now let's move to the Quest Stages tab.

Spoiler

Stage 0 - All quests are at stage 0 until explicitly set otherwise.

Stage 10 - This gets set in dialogue somewhere. As you can see, it provides us our initial journal entry, and enables the object reference SQ09SlaughterfishTarger01, which as we know is a leveled list.

Stage 15 - Notice the lack of journal entry. The player will never know this happened, but we're enabling another reference. This got set when we picked up scales off the slaughterfish from Target01.

Stage 20 through Stage 65 - No journal entry. This time we're enabling one reference, and disabling another. If you pay attention, you'll see that we're disabling the reference two below the current one. This is because if we disabled the one just below it, we'd be disabling the fish we just killed, and it would go poof right in front of us.

Stage 90 - No script this time, but we do get a journal telling us we collected 12 scales. This is the last of the stages set during that huge list of ScaleCount if checks in SQ09Script.

Stage 100 - Journal entry ending the quest, which is marked Complete Quest so that all the journal entries get moved over to the Completed Quests section of the journal. In the script, we're disabling the last two slaughterfish targets, and performing a StopQuest to disable the dialogue and stop the quest script. Note that you're not actually getting the magic ring in this script block - that's in dialogue, although it could be here, too.

Stage 110 - Same as Stage 100, but this is an alternate ending. Note that you ALSO need a StopQuest here, since we're never reaching stage 100 to stop it there.

Stage 200 - Same as the others, except Aelwin's dead. Note the StopQuest. Again, each of these stages needs its own Complete Quest and StopQuest, because stages 100, 110, and 200 are mutually exclusive due to the conditions we've set up elsewhere.


The Quest Targets tab should be pretty self-explanatory. IF GetStage SQ09 is equal to the right stage, quest marker the appropriate slaughterfish target, or Aelwin Merowald.

The Topics tab, however, is a bit more tricky, and I'll spend more time on it.

Spoiler

Start off in GREETING, because that's the first thing that will get said to the player. Note the top Info ("You know, stranger..."). This is the first thing that Aelwin Merowald will say to the player, for two reasons:

- It's at the very top of the list, and the topmost thing that can possibly be said is what gets said;

- The only condition is GetIsID AelwinMerowald == 1, which is the short way of saying that Aelwin Merowald, and only Aelwin Merowald, can say it, and because there's no other conditions, this is the first thing he'll say. He'll only say it once, because Say Once is checked.

- You'll note the two topics under choices, SQ09Ask and SQ09Laugh. These are the only two options you'll have to respond to Aelwin with. If you click over to SQ09Ask, you'll see that the Topic Text is "How can I help?" instead of GREETING. This is what the player sees in the dialogue menu. We know it belongs to Aelwin because of the GetIsID condition. You always want to have one of these, even on lines like this that will only ever be in a choice, because otherwise every NPC everywhere will have it. SQ09Ask has two further choices, SQ09Scales and SQ09Leave. We'll come back to those.

- First, click over to SQ09Laugh and look at the only Info there. It's conditioned for Aelwin. In the result script box, it's telling the game to modify Aelwin's disposition towards the player down by 60. This is opposed to SetDisposition -60, which would have made it -60 instead of just lowering it by 60. The checked Goodbye box means Aelwin will end dialogue after saying his line.

- Back looking at SQ09Scales, you'll see we have five different Infos to choose from. The one that's going to get said if we're getting this choice from SQ09Ask is the very last one. We know this because by looking at conditions for the others, we're at stage 0, so none of the GetStage checks are true, and the first one doubly cannot be true because we don't have 12 SQ09SlaughterfishScales, and more on that in a second.

In that last Info, Aelwin says some lines, and you'll see in the Result Script box the line "setstage SQ09 10", which sets the stage of SQ09 to 10. As you might recall from the Quest Stages tab, setting the stage to 10 adds the log entry for that stage, AND runs its result script, in this case "SQ09SlaughterfishTarget01.enable". Our quest is off and running, and we're off to go kill slaughterfish and pick up their scales.

- Go back and look at GREETING, and look at the second Info on the list. If you were to go back and talk to Aelwin immediately after, this is what he would say, because it satisfies the conditions of the speaker being Aelwin, and the quest stage is between 10 and 99. This Info adds a topic (SQ09Retirement), and sends us back to the SQ09Scales choice.

This time, it's a little more exciting. While the first Info cannot be true - it's not stage 90 and we don't have the scales, all of the next three Infos ARE true. Notice how the Random box is checked for each one. This means Aelwin will pick between those three Infos for which one he wants to say.

- Remember how the last GREETING added that SQ09Retirement topic? Let's go look at that. Notice the conditions, in particular GetDisposition player >= 70. That means if Aelwin's disposition to the player isn't at least 70, that Info won't even appear, and because it's the only Info in the Topic, the topic won't appear. If Aelwin's disposition rises to 70 or above, though, it will appear, and he'll give the player a key to a chest.

- Fast forward. We've collected all 12 scales, and we return to Aelwin. Notice the top GREETING's conditions. Stage is now 90 because of that mess of if checks in SQ09Script, and GetItemCount SQ09SlaughterfishScales is >= 12. Note that if you click the GetItemCount condition, the box "Run On Target" is checked, and it says Yes under Target in the list. This means that instead of the condition being checked against the speaker (Aelwin), the condition is being checked against the person Aelwin is speaking to (the player). If that box wasn't checked, the game would check Aelwin to see if HE had 12 scales.

The Result Script box has the following code:

Player.removeitem SQ09SlaughterfishScales 12additem SQ09SlaughterfishScales 12Player.additem SQ09RingReward 1Setstage SQ09 100


This removes 12 scales from the player, and adds 12 scales to Aelwin. Because we know from the conditions that it's Aelwin speaking the line, we don't need to explicitly declare Aelwin during the additem call - it automatically runs on the speaker. Then we add one item from the leveled list SQ09RingReward to the player and set the stage of SQ09 to 100, which then runs that stage's result script, ending the quest.

And we're done. Or are we? Click the quest SQFIN, and go to the GREETING topic. See how that bottom Info is attributed to Aelwin Merowald? Click that. Note the condition GetStage SQ09 >= 100. Having completed the quest, the condition is satisfied, and Aelwin will now use that line to greet the player. It also adds the topic SQ09Retirement if we didn't already have it.

- You'll see, in the topics list for SQFIN, SQ09Retirement. In it, you'll see an exact copy of the Info from SQ09. The reason for that is because when we completed SQ09 and ran the StopQuest, the copy of that Info in SQ09 became unavailable. SQFIN doesn't complete, so this version never goes away.

In fact, if we had wanted to, we could have just never put the SQ09 version of the SQ09Retirement topic in in the first place. Because it exists here, with conditions (note the GetStage) that can be satisfied while SQ09 is still running, the SQFIN version is perfectly fine by itself.


Thus endeth the lesson. There's a lot here, so feel free to ask questions. As a note to self, I should probably talk about quest construction and planning next, assuming no detours through the wilds of scripting and dialogue.
User avatar
Quick Draw
 
Posts: 3423
Joined: Sun Sep 30, 2007 4:56 am

Post » Tue May 18, 2010 4:50 pm

Just thought I'd chime in with something I've done recently and found it very helpfull.

I had a mod that had lots of object scripts that required variable values from each other. I decided to create a quest script that had no Begin or End block but is just a list of variables. Like float, short, long etc.


I see I'm not the only one who's done this then :)

I don't use it for mundane stuff like the DoOnce variables, but if your quest doesn't need to have anything done in a GameMode block, it can still be handy to use it to store variables you can use to change dialogue responses with, as well as conditions in other secondary scripts involved in the quest. Since a script with only variable declarations has no blocks, it also doesn't eat up any processor time. Which can make them handy for variable trackers in mods with multiple quests.
User avatar
Damian Parsons
 
Posts: 3375
Joined: Wed Nov 07, 2007 6:48 am

Post » Tue May 18, 2010 3:34 pm

I see I'm not the only one who's done this then :)

I don't use it for mundane stuff like the DoOnce variables, but if your quest doesn't need to have anything done in a GameMode block, it can still be handy to use it to store variables you can use to change dialogue responses with, as well as conditions in other secondary scripts involved in the quest. Since a script with only variable declarations has no blocks, it also doesn't eat up any processor time. Which can make them handy for variable trackers in mods with multiple quests.

Yeah I don't use it for mundane stuff that is only used explicitly by one mod.

But I use it for if I have a variable that is being checked by multiple scripts.

Like I have a mod that has 4 scripts that all need to know each others position; so they all alter and check the same variables in the quest script. One script can check to see if another script has done it's thing yet. It's great for setting up chained events between object scripts without having to use Quests and quest stages.
User avatar
Andrew Perry
 
Posts: 3505
Joined: Sat Jul 07, 2007 5:40 am

Post » Tue May 18, 2010 11:25 pm

I'd go one stage further and say your objective should be for quest scripts to always be like that - variables only. If you can do everything with result scripts on Topics, or object scripts (OnActivate, OnDeath etc. preferred to GameMode) then you're amazingly successful. However, you are going to need gamemode in the quest script to run timers, so you won't get there most times.

Gamemode blocks are running "all the time" so the engine has to repeatedly see if they need to do anything, whereas the OnEvent type of block runs only when triggered, so every bit of code you can move to those saves a "do I need to do anything?" test. Similarly, you want to avoid quests being initially active, and use StopQuest when they're completed, to take them out of the list to run.

There are a number of other little things that help performance:
Where a number of situations are mutually exclusive - e.g. they depend on the stages of the quest - put them in one big if...elseif...elseif...endif block instead of separate if...endif's. That way as soon as the right stage is found and executed, the rest of the code is skipped. Sometimes you can even put a single if at the top that's effectively
if (nothing to do)    return endif

Look for situations where you can do something once and keep the result, instead of repeating the work every time. An example from one of my mods was Open Cities compatibility. I check for OC once each time the game is loaded and save all the alternate FormIDs in variables, so I don't have to do the same OC check in every quest and select the right form multiple times.

And as I always ask - please don't call any variable Doonce. Give a real meaningful name like "NPCEnabled" that makes sense when you use ShowQuestVariables - you'll thank me for it.
User avatar
Emma Pennington
 
Posts: 3346
Joined: Tue Oct 17, 2006 8:41 am

Post » Wed May 19, 2010 3:18 am

omg so much information to read! :wacko:
But thank you so much for what you have done thus far! And I am sure there will be many others who benefit as well! continue with the lessons whenever you feel like it ;)
In the mean time I am going to study the information provided so far! :) thank you again.
User avatar
Laura Ellaby
 
Posts: 3355
Joined: Sun Jul 02, 2006 9:59 am

Post » Tue May 18, 2010 10:35 pm

Excellent resource Dwip.

Good job, thanks for taking the time to do it. :goodjob:
User avatar
Sebrina Johnstone
 
Posts: 3456
Joined: Sat Jun 24, 2006 12:58 pm

Post » Wed May 19, 2010 5:32 am

finally with a chance to finish reading the lesson, and read it slow enough to grasp the information given a little better, I am ready to ask a question!

I am sure this can be done similar as the:
scn SQ09SlaughterfishScalesSCRIPTbegin OnAdd playerif GetStage SQ09 >= 10 && GetStage SQ09 < 100  set SQ09.ScaleCount to SQ09.ScaleCount + 1  if SQ09.ScaleCount == 1    setstage SQ09 15  elseif SQ09.ScaleCount == 2    setstage SQ09 20  elseif SQ09.ScaleCount == 3    setstage SQ09 25  elseif SQ09.ScaleCount == 4    setstage SQ09 30  elseif SQ09.ScaleCount == 5    setstage SQ09 35  elseif SQ09.ScaleCount == 6    setstage SQ09 40  elseif SQ09.ScaleCount == 7    setstage SQ09 45  elseif SQ09.ScaleCount == 8    setstage SQ09 50  elseif SQ09.ScaleCount == 9    setstage SQ09 55  elseif SQ09.ScaleCount == 10    setstage SQ09 60  elseif SQ09.ScaleCount == 11    setstage SQ09 65  elseif SQ09.ScaleCount == 12    setstage SQ09 90  endifendifend


However, I would like to know how you might make a quest start after the player as gathered specific items. I am sure it can be done with a start gamemode block, however, I would like to know the most efficient way to go about it.
User avatar
Stu Clarke
 
Posts: 3326
Joined: Fri Jun 22, 2007 1:45 pm

Post » Wed May 19, 2010 2:49 am

It mostly depends on the item(s) in question.

Way #1 - If the items for the quest are custom items - weapons, armor, anything that can have a script attached to it.

This is pretty much going to be like my revised SQ09 scripting, but slightly different because we don't need all of those setstage commands SQ09 uses. For the sake of argument, let's say you need to collect 5 pieces of a map, and when you do, you'll get a whole map, remove all the pieces, and get the location of a cave somewhere.

You need a quest, a quest script, and some stages.

In the quest script, define a variable, and call it iMapPieces or something similar. Like ScaleCount in SQ09, this will count items.

In your stage list, create a stage 5. Create a journal topic for that stage, and in the results box put:

player.additem MAQWholeMap 1 ; Adds the whole map item.  MAQ is the prefix we're using, as discussed during naming conventionsplayer.removeitem MAQMapPiece 5 ; Removes all the map pieces from the player, MAQMapPiece is the item nameshowmap MAQTreasureCaveMarker ; MAQTreasureCaveMarker is a MapMarker you've placed outside your cave. This version of ShowMap adds the marker to the player's map but doesn't allow fast travel until they find it


Then, on every map piece item, you'd need an object script about like so:

scn MAQMapPieceSCRIPTbegin OnAdd playerset MAQMapQuest.iMapPieces to MAQMapQuest.iMapPieces + 1 ; MAQMapQuest is our quest name, we're incrementing the variable per SQ09if MAQMapQuest.iMapPieces >= 5  setstage MAQMapQuest 5endifend


Basically, what we're doing is saying that every time the player picks up a piece of map, the script on the piece runs, increments the variable in our quest script by 1, and if it reaches 5, we set the stage of the quest to 5, whereupon the code in the results box for stage 5 runs.

This should all look pretty familiar from my revised SQ09 code, with the added advantage that we don't need that huge block of if/elseif checks for each number in iMapPieces, because we're not using stages to spawn more slaughterfish like SQ09 does. This is, as has been said, the most efficient way of doing things, because the OnAdd blocks in object scripts will only ever run when (and if) the item is picked up and added to inventory. Thus the CPU/memory footprint is pretty small. We DO need the quest script and its variable, however, because variables attached to object scripts aren't persistent in the way we need - defining iMapPieces in the object script, and setting it there, would only increment iMapPieces for that object, making it 1. Quest script variables, on the other hand, can be referenced by objects and are persistent.

Way #2 - If the items you want to count are vanilla items, such as calipers, or iron longswords, or the like

The problem with the first method I listed is that, while you could attach object scripts to, say, iron longswords, you shouldn't attach scripts to iron longswords or any other vanilla item, because it will introduce massive incompatability issues in the event somebody else ALSO attaches a script to that item.

The workaround is actually easier than doing things the right way.

First, you need a quest, a quest script, and a stage 5. Let's call this one ILQLongswords, and for our stage 5, we'll flash a message at the player.

For your stage 5 result script, put:

message "You have collect five longswords! Return to that guy who gave you this quest to proceed!"


You wouldn't normally flash messages like this (that's why we use journal entries), but for the sake of demonstration we'll run with it.

In the quest script, you want something like this:

scn ILQLongswordsSCRIPTbegin gamemodeif player.getitemcount WeapIronLongsword >= 5  setstage ILQLongswords 5endifend


That's it. It's much simpler than the other method, but it's also less efficient - as I've said before, that gamemode block runs every 5 seconds, and what's more, in order for this to work the quest has to be enabled so that the script can run, either via StartQuest or by checking the Start Game Enabled box in the Quest Data tab. Thus it's possible to have this script that's cranking away every 5 seconds forever. This isn't a big deal individually, but over a bunch of quests it can add up.

Way #3 - Have some NPC with dialogue do all the work

This is the quick and dirty way to do fetch quests. No script needed, just dialogue and an NPC. Works with any sort of item.

You'll need a quest, a stage 5, an NPC, and some dialogue. We'll stick with ILQLongswords.

For stage 5, you'd want the guy to pay you for the swords, so maybe something like this in the results box:

player.additem gold001 500


We could actually do that code in the results box during dialogue, but I'm doing it this way to show that you can do things this way.

Then, in the Topics tab for ILQLongswords, right click the topics list and add GREETING. Create a new Info, and give it some response text, like "Hello, my brothers and I need some weapons to fight off the appropaching goblin hordes. Can you bring me five iron longswords?" and hit OK. Then, in the conditions, add the following:

GetIsID ILQQuestGiver ; ILQQuestGiver is our NPC. We're doing this so everyone in the game doesn't have this line.
GetStage ILQLongswords < 5 ; We're doing this so that once we set stage to 5, he'll stop saying the line. You could also write this as GetStage ILQLongswords == 0

Now right click in the topics list, and when the selection box appears, right click on the topic called EMPTY and select New. In the field that appears, type in something like ILQLongswordsTopic. Hit ok. Now, go back to your GREETING info, and in the results box for the dialogue, type AddTopic ILQLongswordsTopic. We're doing it this way to avoid the CS complaining about a non-existant topic and wiping out the script.

Now go back to ILQLongswordsTopic, and in Topic Text type "I have the longswords!" without the quotes. This is what appears to the player. Then, add a new Info, and in the response text, type "Thank you! This will be a great help. Here, take this gold in payment." and hit OK. Then, in the conditions, add the following:

GetIsID ILQQuestGiver
GetStage ILQLongswords < 5
GetItemCount WeapIronLongsword >= 5 ; Make sure Run On Target is checked here, so as to run this on the player, not the NPC.

In the results box for this Info, type setstage ILQLongswords 5

What we've done here is to have an NPC that greets the player with the GREETING topic, which adds the ILQLongswordsTopic. This topic won't appear to the player right away, however, because they don't yet have 5 iron longswords. When they do, they'll see "I have the longswords!" in their topics list during dialogue, and when they select it will advance the quest.

As always there are caveats. First, the quest must be enabled for the dialogue to appear. So either it's checked as enabled from the start, or you need to use StartQuest somewhere to make it run. Fortunately, this method is highly efficient - dialogue won't eat up CPU time, and you didn't have to write a bunch of scripts. On the downside, it's not really suitable if you don't want to have a quest giver - if you want to do the "pick up five pieces of map" treasure hunt thing where the player can simply stumble across map pieces without having the quest in advance, you'll need to do one of the first two methods. On the other hand, for quests with quest givers, dialogue scripting and the proper use of dialogue conditions can take you very far indeed, and you can often avoid the need for a quest script alltogether, as we did here.

Obviously this method can be given a lot more polish, as well - better dialogue, use of choices, and the like, but hopefully you get the idea.

Does that answer the question?
User avatar
Leticia Hernandez
 
Posts: 3426
Joined: Tue Oct 23, 2007 9:46 am

Post » Tue May 18, 2010 6:05 pm

That does, infact, answer my question :P Thank you Teacher! You make learning fun, ha ha ha.

anyways, you actually answered another question I had, which was way #3.
Anyways, I want to get my facts straight, so I want to double check on this: I was planning on making a quest for my mod, Camp Simplicity. While currently, the player collects 6 books and that is it, however, I want it (in a later release) so that a quest is given to the player after getting each book. This would be done by using Way #1, correct?

Okay, on another note, I would like to know about adding quest markers. You know, those things most people don't like because they make quests 10x easier? The things that point you in the direction you should go for a quest. Anyways, so as you stated here:
showmap MAQTreasureCaveMarker ; MAQTreasureCaveMarker is a MapMarker you've placed outside your cave. This version of ShowMap adds the marker to the player's map but doesn't allow fast travel until they find it

using that line of script shows the marker for the cave. In this instance - I am assuming - shows the fast travel point on the map. (correct me if I am wrong) So how would you go about telling the quest script to add a marker to point to someone or something?

Let's use the example you already used, and say you traveled a long ways away from the guy who needed iron swords, so at this point, thank god we added a quest marker on the guy who wanted them.
User avatar
Causon-Chambers
 
Posts: 3503
Joined: Sun Oct 15, 2006 11:47 pm

Post » Tue May 18, 2010 3:19 pm

Anyways, I want to get my facts straight, so I want to double check on this: I was planning on making a quest for my mod, Camp Simplicity. While currently, the player collects 6 books and that is it, however, I want it (in a later release) so that a quest is given to the player after getting each book. This would be done by using Way #1, correct?


Yes, that's probably the best way, although instead of an OnAdd block, you might want to use an http://cs.elderscrolls.com/constwiki/index.php/OnActivate block instead, since that would update every time the player read one of the books instead of every time they picked one up.

A bug prevention caveat or two, however:

Spoiler

- If you go the OnAdd route, you can use the script as written, but you'll need to make the books quest objects so that the player can't drop the book and keep picking it up to increment the counter variable.

- For the OnActivate block, or as an alternate way of doing the OnAdd block, you'll need to do some revision. First, make stages 1, 2, 3, 4, 5, 6, and 10:

1 ; Found book 1
2 ; Found book 2
3 ; Found book 3
4 ; Found book 4
5 ; Found book 5
6 ; Found book 6
10 ; Found all the books, start the main part of the quest

scn ntomBQBook01SCRIPT ; [FN1]begin OnAdd player ; or OnActivate, it works the sameif GetStageDone ntomBQ 1 != 1 ; [FN2]  setstage ntomBQ 1  set ntomBQ.iBookFoundCount to ntomBQ.iBookFoundCount + 1  if ntomBQ.iBookFoundCount >= 5    setstage ntomBQ 10  endifendifend


What we're doing here is adding another stage to the quest, stage 1, and checking to see if it's already been done or not. If it hasn't been done, we increment the book counter variable in our quest script, and set the stage to 1 so that this script won't fire again the next time we pick up the book.

You'll need a different one of these scripts for each book - the one for book02 references stage 2, book03 is stage 3, etc.

[FN1] - notice what I'm doing with names, here. ntom is a prefix denoting that it belongs to you, BQ is a simple two letter thing that stands for "Book Quest", which we're using because it's easier to find than "Book Quest", and our item name is ntomBQBook01, to distinguish it from ntomBQBook02, etc. The SCRIPT bit says that our script is a script. I find it handy to put SCRIPT in all caps, you don't have to.

[FN2] - http://cs.elderscrolls.com/constwiki/index.php/GetStageDone is a subtly different animal from http://cs.elderscrolls.com/constwiki/index.php/GetStage, but there are important time when you want to use it, such as now.

The way it works is that GetStage checks to see if the current stage, the one you're at right now, is true or false. This is perfectly fine if you're doing a linear quest, where you'll always progress from stage 0 to stage 10 to stage 20 to stage 30, etc. But sometimes you don't do that - in our case, it's possible to pick up book 6 before we pick up book 2 before book 4 - we could even pick up book 1 last. GetStageDone doesn't care if we're really at stage 20 when we do this, it cares only if we've ever completed stage 1 or whichever.

Sometimes we might not want to use quest stages for whatever reason. We could also use a set of quest variables. To do things this way, we would only need stage 10, but in the quest script, we would need to define several variables:

short iFoundBook01short iFoundBook02short iFoundBook03short iFoundBook04short iFoundBook05short iFoundBook06


Then, we'd make up our object scripts like so:

scn ntomBQBook01SCRIPTbegin OnAdd player ; or OnActivate, it works the sameif ntomBQ.iFoundBook01 != 1  set ntomBQ.iFoundBook01 to 1  set ntomBQ.iBookFoundCount to ntomBQ.iBookFoundCount + 1  if ntomBQ.iBookFoundCount >= 5    setstage ntomBQ 10  endifendifend


Obviously we need a different object script for each book, working off a different variable each time, just like with the stages.

Basically we check the value of ntomBQ.iFoundBook01, and if it's still 0 (and all variables start at 0), we increment iBookFoundCount and set iFoundBook01 to 1, so that the script won't fire again the next time we pick up the book.

Unlike some other choices on how to do things you'll need to make, there's no real right or wrong answer between quest stages and a variable here. It's mostly just a matter of personal preference or the needs of the quest - in AFK_Weye, I've hit spots where I'm revising a quest for a new version, and I'd need to have a block of 6 quest stages, and I can't do that, so I'll use variables instead. It's really up to you how you want to do things.


Okay, on another note, I would like to know about adding quest markers. You know, those things most people don't like because they make quests 10x easier? The things that point you in the direction you should go for a quest.


For the record, I'm a big supporter of quest markers, and heartily encourage their use when appropriate, especially in circumstances such as the one you mentioned.

First, you are correct about the usage of http://cs.elderscrolls.com/constwiki/index.php/ShowMap.

Second, supposing we want to add a quest marker to our quest giver for the longswords example in Way 2 and Way 3. It's a little bit different depending on which way you went originally.

Spoiler

As you'll recall from the SQ09 discussion, quest markers are handled through the Quest Targets tab of the Quest dialogue box.

Supposing Way 2, the quest marker back to the quest giver is pretty easy. The quest script sets the stage of the quest to 5 when we pick up all the longswords, so we can simply create a new quest target, direct it to our quest giver NPC using the Quest Target Data area, and then give it a condition of GetStage == 5. As long as the stage the quest is at is 5, we'll have that quest marker. This does mean that you'll need to provide a way out of stage 5, so that you don't have a pointless quest marker after you talk to the guy and get the reward. In this case you could accomplish it by having the result script for his reward dialogue set the stage to 10 (or 100 if we're completing the quest).

Way 3 is a little more complex, because we didn't really provide a good hook for the quest marker - we don't get any kind of notification in this way when we get the five swords, so we can't just run a stage check. There are a couple of ways to go here, depending on how we want to do things.

- Add in the script from Way 2 (or do everything via Way 1 if you can). When you get the swords, the gamemode block in the quest script sets a new stage, and you can make a quest marker just like we did for that. This is usually the best way of doing things, even if it's a little less efficient than we might like.

- You'd perhaps think that it would be as easy as making a new quest target, pointing it at our NPC, and giving it a condition of GetItemCount WeapIronLongsword >= 5 with Run on Target checked like we did for the dialogue condition. The problem with this is that http://cs.elderscrolls.com/constwiki/index.php/Quest_Targets_Tab, Run on Target doesn't work with quest targets, and without Run on Target, the condition will check the NPC, not the player, for the swords, which doesn't work.

- A less optimal way of doing things without the quest script would be to create a new variable definition in the quest script (it might be the only thing in said script, that's ok):

short iTalkedToNPC


Then, when we talk to him and he gives the quest, we use the result script box in the dialogue to set iTalkedToNPC to 1.

Then we can create a new quest target, and use the condition GetQuestVariable ILQLongswords iTalkedToNPC == 1, plus the condition GetStage ILQLongswords < 5.

This will add a quest marker to the NPC right after he gives us the quest, and he'll keep it until we give him the longswords and it sets the stage to 5.

We could also, as I discussed above, use a quest stage instead of a variable. Either way.

This way is less optimal because it's indescriminate - the NPC has a quest marker at times we might not care about him, such as when our focus is on getting the swords. In that case, the marker is just a misleading distraction, because the NPC isn't important at this point, although he'll be important (and we would want a marker) AFTER we get all the swords. That's why I'm telling you to use the other ways.


That make sense?
User avatar
James Hate
 
Posts: 3531
Joined: Sun Jun 24, 2007 5:55 am

Post » Tue May 18, 2010 10:08 pm

heh heh he, yes sir it makes sense :)
took me awhile to get around to getting this to sink into my head (and it is not entirely so I will be going back here for reference) but let's start the next lesson now!
hmmm...is there something you feel is important to cover or should I try to think ahead of this quest I am making to a point where I will not know what to do? :P
User avatar
Iain Lamb
 
Posts: 3453
Joined: Sat May 19, 2007 4:47 am

Post » Tue May 18, 2010 5:13 pm

Well, if there's something you want me to talk about, I should do that. Otherwise, I think the next logical step is, having gone through the specifics of dissecting a quest, going through the mechanics of constructing your own. That's probably going to cover a bunch of stuff you already know, but the steps I talk about may or may not help.

I think we're at the point with scripting where I've talked through all the basics I can think of offhand, and we're left with whatever you come up with to ask. I should probably write a post covering basic timers and message boxes and a couple other tricky but useful things, but other than that I'm out of stuff to say without input.
User avatar
chloe hampson
 
Posts: 3493
Joined: Sun Jun 25, 2006 12:15 pm

Post » Tue May 18, 2010 8:51 pm

...Otherwise, I think the next logical step is, having gone through the specifics of dissecting a quest, going through the mechanics of constructing your own....

I think we're at the point with scripting where I've talked through all the basics I can think of offhand, and we're left with whatever you come up with to ask. I should probably write a post covering basic timers and message boxes and a couple other tricky but useful things, but other than that I'm out of stuff to say without input.


heh, a bunch of stuff I know :P that makes me laugh. I am rather familiar with landscaping, beautifying, and item placement in the CS (havok'd items still get me though when placing in the CS and trying to get them to stack properly) but scripting...well...before all what you taught me, all I knew was a basic script to make adding an item or spell to the player once...and the reason I knew that is because I asked in here in the construction set about doing that.
So, I think it would be good (plus, I am trying to keep in mind others may be using this as reference) if you went ahead and go over the mechanics of constructing my own quest.
and I suppose the next best place to start is:
...write a post covering basic timers and message boxes and a couple other tricky but useful things...

since you already named that, and I am certain those "tricky but usefuly things and basic timers" would be helpful for the way my quest is starting out (yes, I have not set foot into making the quest yet. First: Write the script (not action script, but dialogue script), Second: Build any necessary towns, add NPCs, and so forth mentioned in the script. Last: Make the quest and add any last items needed to be added to specific places and/or NPCs)

so, Teacher, feel free to start whatever you see if best to be next in line.

lol...I am not enrolled in 3 classes, I am enrolled in 4:
2D animation, Coloured Photography, Charecter Design (for animation), AND....Scripting for the Construction Set.
User avatar
Cayal
 
Posts: 3398
Joined: Tue Jan 30, 2007 6:24 pm

Post » Wed May 19, 2010 4:22 am

Arright. Watch this space. I'll try to deal with at least one of those tomorrow at some point.
User avatar
Steve Fallon
 
Posts: 3503
Joined: Thu Aug 23, 2007 12:29 am

Post » Wed May 19, 2010 2:44 am

I'd love to tell you how all that time I spent today playing through the Ayleid Steps was valuable research time, but I'd be lying. Fun, though. I'm also working through a headache, so pardon any incomprehensibility.

Anyway, lesson time. I want to talk a little bit about timers and messages/message boxes. These are both pretty common elements of scripting, but can seem tricky at first if you don't know what you're looking at.

First, let's talk about the http://cs.elderscrolls.com/constwiki/index.php/MessageBox. The CS Wiki has a http://cs.elderscrolls.com/constwiki/index.php/MessageBox_Tutorial on the subject, and I recommend you go through it, because it's going to talk about this stuff in far more detail than I'm about to.

Spoiler

First off, if you're not sure what a message box is, think of them as pop-up boxes with text in them. If you've ever used an altar of the Nine and had a box come up saying "Rejoice! Through faith, your afflictions are banished!", that's a simple message box. Don't confuse those with a http://cs.elderscrolls.com/constwiki/index.php/Message, which just flashes a line of text in the top left corner of the screen.

The main difference between the two is that, unlike Messages, MessageBoxes take up the center of the screen and pause the game, forcing you to click something to continue. Messages just flash to the corner of the screen and don't pause the game, making it easier to ignore them. This can matter depending on what you're doing - if you're scripting a counter to keep track of the number of enemies left to kill in a combat, Message is probably the best choice because it won't stop the action. A MessageBox commands a lot more attention, however, and makes sense for other times, like after you've activated an important item during a quest.

The other thing about a MessageBox is that it's far more powerful. We'll get to that in a while.

The simple form of a Message or a MessageBox is relatively straightforward, and you can see it in this snippet from the AltaroftheNine script:

		if GetDayofWeek == DayofLastUse			MessageBox "You've already received your blessing this day."		else			MessageBox "Rejoice! Through faith, your afflictions are banished!"			Cast AltarNine Player			Set DayofLastUse to GetDayofWeek		endif


In game, either one of those MessageBox commands will pop up the message in front of the player and force them to click OK to continue. Messages work the same way.

MessageBoxes are far more powerful than this, though. You can also use them to offer choices, or create menus. The tutorial goes into this in a lot of detail, but I'm going to demonstrate a fairly simple MessageBox with two buttons so you can see how they work.

We'll use a script written for AFK_Weye for this, as I'm familiar with it. It's attached to an activator object. The tutorial goes into some lengthy detail about why you should do things this way, and I'll just say that this is the most common way you'll use them during quests, and leave it at that.

scriptname AFKSaintDranasTombScriptshort button ; [FN1]begin OnActivate ; We're containing the MessageBox code within an OnActivate block so that it only fires when activated. Use whatever blocktype you need here.if IsActionRef player == 1 && GetStage AFKStarWishQuest == 35 ; Limits activation only to the player at a certain quest stage.    messagebox "Do you wish to replace the item in the tomb?", "Yes", "No" ; [FN2]endifendbegin gamemode ; This is where we put the code that tells the buttons what to do when you click them. It should always look more or less like I have it, and should be in a gamemode or menumode block if applicable.set button to getbuttonpressed ; Storing the results of GetButtonPressed. You need to do this first thing.if button == 0 ; [FN3]  player.removeitem AFKItemREWARD01 1  player.removeitem AFKItemREWARD05 1  player.removeitem AFKItemREWARD10 1  player.removeitem AFKItemREWARD15 1  player.removeitem AFKItemREWARD20 1  player.removeitem AFKItemREWARD25 1  player.removeitem AFKItemREWARD30 1  setstage AFKStarWishQuest 37elseif button == 1  returnendifend


[FN1] This is a temp variable for the results of http://cs.elderscrolls.com/constwiki/index.php/GetButtonPressed

[FN2] This is how you build a MessageBox with more than one button choice. When activated, the player will see the text "Do you wish to replace the item in the tomb?" with two buttons, Yes and No.

[FN3] The "if button == 0", "if button == 1" sequence of if checks is where we do the real work of telling the buttons what to do. In this case, clicking "Yes" in the MessageBox will remove a bunch of items from the player and set a quest stage. Clicking "No" will exit the script. Keep in mind that the first button in the list ("Yes", in this case), is always button 0, the second is 1, the third would be 2, and so on.

There's a ton more you can do with a MessageBox, but this is good enough for the basics.


Now, as to timers, the CS Wiki has a pretty good http://cs.elderscrolls.com/constwiki/index.php/Scripting_Tutorial:_Creating_a_Simple_Timer with how to create one. I want to provide a bit of commentary, and a couple of examples.

Spoiler

First off, the big deal with timers is this - there's no convenient way to tell an Oblivion script to "wait 2 seconds, then do some code." You run into this all the time, for all sorts of different reasons. For example, I use several timers in AFK_Weye for sequences where the player talks to an NPC somewhere, then when conversation finishes, a timer starts for, say, 30 seconds, and at the end of the timer the NPC teleports somewhere. You'll often use timers as part of cutscenes with NPCs, such as the beginning of the vanilla game in the dungeon (which is very heavily scripted). There are lots of possibilities here.

I write my timers slightly differently than shown in the tutorial. I do things thusly:

scn AFKTimerDemoSCRIPTfloat timerfloat fQuestDelayTimeshort TimerOnbegin gamemode  if TimerOn == 1    if timer > 0      set timer to timer - getSecondsPassed    else      ; Do some stuff here      set TimerOn to 2 ; turns the timer off    endif  endifend

Then, in another script block somewhere, generally a quest stage block, you put this:

set AFKTimerDemo.timer to 5 ; for 5 secondsset AFKTimerDemo.TimerOn to 1 ; turns the timer onset AFKTimerDemo.fQuestDelayTime to 1 ; [FN1]


[FN1] - I explained http://cs.elderscrolls.com/constwiki/index.php/FQuestDelayTime a couple of lessons ago. It's a special quest variable that lets you change the interval between when gamemode and menumode blocks run (usually 5 seconds). You'll need to define it in each quest you use it in. For Object scripts it has no effect, and you don't need to do this bit.

You don't have to time things in seconds, either. You can also use various http://cs.elderscrolls.com/constwiki/index.php/Category:Time_Functions as desired, usually GameDaysPassed for tracking days. If you wanted to time by days instead, you'd write the above script a little differently, like so:

scn AFKTimerDemoSCRIPTshort startdayshort TimerOnif TimerOn == 1  if GameDaysPassed >= ( startday + 1 )    ; Do some stuff here    set TimerOn to 2  endifendifend


In your quest stage block, you need:

set AFKTimerDemo.TimerOn to 1set AFKTimerDemo.startday to GameDaysPassed


That will give you a timer that does some things one day after it's initialized.

There are various other ways to get elegant with this sort of thing. For example, here's something I wrote for AFK_Weye:

short Doonce3float fQuestDelayTimebegin gamemodeif GetStage AFKPropertyRightsQuest == 10  if Doonce3 != 1    set Doonce3 to 1    set AFKPropertyRightsQuest.fQuestDelayTime to 1    AFKNPCRef.movetomarker AFKPlaceMarker    player.movetomarker AFKPlaceMarker  else    setstage AFKPropertyRightsQuest 11  endifendifend


In the stage 10 script box, we have:

set AFKPropertyRightsQuest.fQuestDelayTime to 2


And in the stage 11 script box, we have:

set AFKPropertyRightsQuest.fQuestDelayTime to 5AFKNPCRef.evp


Here's how it works. While in dialogue with the PC, AFKNPCRef sets the stage to 10 and exits dialogue. The stage 10 script sets fQuestDelayTime to 2, because that's how long I want it to take before the gamemode kicks in. When the gamemode kicks in, because Doonce3 is still 0, it moves the NPC and the player to a marker, sets fQuestDelayTime to 1 to make the next bit happen a bit faster, and sets Doonce3 to 1 so we won't keep looping this part of the script. The next time the gamemode block runs, it's still stage 10 but Doonce3 is 1, so we skip to the part where it sets the stage to 11. This makes stage 11's scripting run, which sets the quest script's fQuestDelayTime back to normal, and makes the NPC http://cs.elderscrolls.com/constwiki/index.php/Evp, or choose which one to run, in this case an Ambush package so that he'll talk to the player.

This is, by the by, a necessary workaround for a couple of bugs regarding the http://cs.elderscrolls.com/constwiki/index.php/MoveTo function. On the one hand, moving the player during dialogue will crash the game. On the other hand, moving the player and immediately starting a conversation without a slight delay in between will cause a strange zooming effect to occur. Thus, a timer.

I also ended up doing things this way due to how various AI pack and dialogue conditions worked at the time I added the timer to the already existing quest. The point here is that there are several ways to accomplish things, and you should use the one that works for you.


There's a lot more that can be said on both topics, but this should give you enough to get started. As always, questions and audience participation welcomed.

Next up, basic quest construction tips.
User avatar
April D. F
 
Posts: 3346
Joined: Wed Mar 21, 2007 8:41 pm


Return to IV - Oblivion