Scripting

Guides for development on Amia.
Post Reply
User avatar
Maverick00053
Posts: 675
Joined: Fri Jun 05, 2020 10:01 pm

Re: Scripting

Post by Maverick00053 »

Creating a Script in Toolset and Variables

We are going to be starting with the absolute fundamentals of programming with NWScript.

First we are going to right click the Scripts section on the far left of your toolset as shown below. Then click new.

Image

You will have the following pop up. This is your script compiler.

Image

Let us start with the basics. What is a void main{} ? The best way to think about this is it is your container for your code. The compiler will read everything between this script and will launch any code inside of the void main function first. This is significant later when developing other additional functions for scripting but for now just remember that everything you write will go inside of the { and }.

In programming you have different variables that store different types of data. These are used to conduct math, store names, and more. I am going to keep it simple and stick with the most used variables in NWN. There are many, many more and they can be found in the link below if you are curious.

NWScript Variables

Now looking at the image below lets go over int, string, float, and object. You can follow along inside your own script. Once you fill yours in, hit the save icon at the top next to the scroll icon in the image. This will compile your script and if everything is OKAY you will see the 0 Errors, compiled successfully message at the bottom.

Image

When "declaring" or assigning a value to a variable you must first write the type of variable. It will turn blue in the compiler when written out as shown. Then you follow up with the name. The name can be ANYTHING you wish. For the sake of staying consistent I usually start the name of the variable with an n for int, s for string, f for float, or o for object. You do not have to follow this formula but it helps me keep track of variables later on.

Programming nomenclature I was taught says to follow this when naming stuff: lowercase word or letter followed by an uppercase word or letter. For example: thisExample OR numberFive. You do not have to follow this but if you see this in my scripts or others that is the reasoning.

int or integers store numbers as shown in the example. They do not store decimal points.

string store letters or words as shown. You must encircle strings with quotes. You can have numbers stored as strings, but they will not function if you try to do math or inject them into formulas because they register as letters/words. There are ways to change them back and forth, but we will go over that later.

float are stored numbers with decimal points. There are advantages to storing decimal points and advantages to not. A lot of equations will round up or down automatically if you use an int over a float which will simplify the end result.

Lastly we have object. Objects are the creatures, players, placeables, and items we have in the game. Objects can be a wide range of different classifications and there are functions to tell them apart that we will go over later. Objects are significant in that we need to know who, or what to assign the effects of a script to. Objects are also very powerful in that we can store int and strings on them for later use. This is how we track things on player characters over time.

All NWscript lines of code must end in a semi colon. This tells the compiler that the code is finished and where to end. If you do not end your code with the semi colon then it will error out. You DO NOT need to end your loops, and if else statements in them though. We will go over those in detail later.
User avatar
Maverick00053
Posts: 675
Joined: Fri Jun 05, 2020 10:01 pm

Re: Scripting

Post by Maverick00053 »

Logical Expressions, If Else Statements, Loops, and Switches

We are going to cover some fundamental tools in programming and NWScript to control, direct, and process information to actually get stuff done.

We are going to be covering If Else Statements, For Loops, While Loops, and Switch Case Statements. Links to alternate descriptions on NWN Lexicon are below. NWN Lexicon is an extremely powerful resource for referencing tools, functions, and more.

Links

If Else Statements
For Loops
While Loops
Switch Statements


Logical Expressions and Programming

There is a number of logical expressions in programming that are used to compare data and return a TRUE or FALSE statement.

In NWScript, and many other programming languages, it is important to remember the following expressions.

When you are checking if one value is equal to another value you will use ==. For example, if you have int n which is 10 and int i which is 9. You write n == i it will compare the two and return FALSE. If int n was also 9 it would return TRUE.

When you are checking if one value is less than or greater than another value you will use < or > . For example, if you have int n which is 10 and int i which is 9. You write n > i it will compare the two and return TRUE. If int n was 8 it would return FALSE. You can also use the expressions >= or <= which will check if one value is less/greater than or EQUAL to the other value. For example, if int n was 10 and int i was 10 then the statement n >= i would be TRUE.

Finally, you have && and ||. && translates into AND and || translates into OR. These expressions are usually used when comparing multiple other variables together in their own checks. For example,

Lets say we have int n = 5, int e = 6, int z = 2, and int y = 5.

Using && for the following: (n > e) && (z<y) would be FALSE. The the n > e is FALSE and the z < y is TRUE, but when using && both statements have to be TRUE for the entire thing to be TRUE. So because n > e is FALSE then the entire thing would return FALSE.

Using || for the following: (n > e) || (z<y) would be TRUE. Like above the the n > e is FALSE and the z < y is TRUE, but now when using || either statements could be TRUE for the entire thing to be TRUE. So because z < y is TRUE then the entire thing would return TRUE.

Both && and || are powerful for times when all conditions or only some conditions need to be met to produce a desired result. You will see some of these demonstrated in further examples and guides.

If Else Statement

The first tool we are going to examine is the if else statement. This lets you take a variable, or even a string, and do tests to see if it matches expected results, and if it does complete a specific set of code.

In the example below I introduce a new function. The Random() function will roll a random dice between 0 and the number you put into the () minus 1. So in our example below nTest will be between 0 to 9. The majority of programming languages start counting at 0 and not 1, this is something important to remember. An if else statement works simply by checking if the equation inside of the () is TRUE or FALSE. The structure of an if else must always start with an if() because this is the first test the compiler will check.

You can have a stand alone if statement, an if statement followed by an else, or an if statement followed by an else if and no else at the end. If is the first statement checked, and if it is TRUE then the compiler will complete the code underneath that statement and skip the rest. If the first statement is FALSE it will move onto the next statement underneath it, in this case the else if. This will repeat till either the compiler runs through all the statements or one of the ifs or else ifs comes back TRUE. If you have an else statement at the end this is a "catch all" statement, it is not required but can be useful to always guarantee a result.

Image

Let's go through an example. In the above image if nTest rolls a 5, then what will nFinal be? It would be 40.

If nTest rolls a 1, then what will nFinal be? It would be 20.

We will be moving onto our next tool.

For Loops

For Loops are a powerful tool that lets you loop through a set of code as many times as you designate. This lets you save space without having to repeat the same line of code over and over. It is also a loop you can set up to adjust based on different factors. For example, if you need to reward a group of players for a successful quest completion you can use the number of people in that party, in the same area, to run a for loop to reward each person individually. This for loop would adjust based on party size and always run the exact amount of times you needed it to.

The first thing you must do for a for loop, as shown before, is declare an integer but don't assign it a value. In this case we are using i.

Within the for loop you will declare the value of i, in this case 0, and then have a semi colon. The next statement is the conditional statement the loop will check each time it runs to make sure it can continue. Then the final thing in the () is i++ which is shorthand for i = i + 1, in other words it will increase by 1 every time the loop runs.

To start the for loop will check if the starting value of int i, which is 0, is less than 10. This is of course TRUE. It will then run the code within the for loop which will make nFinal = 1. Then it will increment the value of int i up one, so it will be equal to 1 now. The for loop then starts over, in this case just going straight to the i<10 statement to see if it is TRUE or FALSE. For the second run through int i is equal to 1 which is less than 10, which is still TRUE. nFinal will be set to itself plus 1 which means it is now 2, and then int i will be increased by 1 as well being set to 2 now. This process will REPEAT till the i<10 statement returns FALSE and immediate stopping.

Image

So what will the final value of nFinal be in the above example? The answer is nFinal = 10. The for loop will run 10 times before stopping.

You can set the value of i within the for loop to whatever you wish. You can also make the middle statement be i<=10, i=10, or whatever variation you wish. Finally you can increment i up or down by any value that you desire as well.

While Loops

While Loops are similar to For Loops in that they can loop through a select amount of code for as long as required. The big difference is that a While Loop is infinite until it is turned off by the condition set inside of the while loop becoming false.

While Loops are much simpler to set up than For Loops as shown below. As shown below, WHILE nTest==1 is TRUE the loop will continue to run. These loops are used extensively in NWN when stripping spell effects from players or creatures. The while loop will keep finding and removing spell effects on the player till there is no longer a valid spell effect to find and then stops.

While Loops also have the potential to cause crashes if they are allowed to run indefinitely, so please be aware. If you look back at the example you will see that we have an if statement embedded inside this while loop. This if statement changes the value of nTest after the while loop has run a total of 11 times.

Image

While Loops are useful when you do not have a way of knowing exactly how many times you might need to run a loop but have a condition that can be met that will turn the loop off once you are finished. Later on in future guides you will see While Loops being used to not only spell removal of effects but also for counting party sizes in local areas for certain bonuses.

Switch Case Statements

Switch Case Statements are like if statements in that if conditions are meet you can specify a specific result. Switch Case Statements can make your code cleaner, easier to read, and more organized if you have a lot of possible different results for a single value. Switch's can't manage complete equations for comparison like if elses but if you are checking if an integer you received is equal to a certain number they are perfect for it. Amia's Job System utilizes massive Switch Statements to organize and make editing new recipes fast and easy for any new Dev.

As you can see below we are generating a random integer called nTest using the Random() function. A switch simply contains the value you are testing and inside the { } you will have “case” a number and colon at the end. If the number that results from nTest matches the case number it will run the code in that section. If nTest is equal to 5 then nFinal will be set to 60. You must include a break; after you finish, this is the compiler's way of knowing where a case check ends and begins.

What happens if nTest rolls a 9? Since there is no case 9 it will fall into the default section, which is a catch all for all results that do not match any of the cases. That means nFinal will be equal to 100

default: is not required for switch statements and is optional.

Image

We will demonstrate further application and actual use in game later for all of these.
User avatar
Maverick00053
Posts: 675
Joined: Fri Jun 05, 2020 10:01 pm

Re: Scripting

Post by Maverick00053 »

Functions, Constants, NWN Wiki + NWN Lexicon Utilization, and Our First Spell Script!

In the script compiler/editor on the far right you have Functions / Variables / Constants / Templates. This is shown below. This is a powerful resource you can utilize to search Functions or Constants that the base game offers.

Image

List of Functions

Functions are a sequence of program instructions that performs a specific task, packaged as a unit. This means you can segregate code that you use often into its own package that can be called at any time. Base NWN has some of these integrated into the toolset that are virtual for finding players, targets, or other objects to then complete a set of code on.

Image

List of Constants

Constants are just that constant. They are variables, usually integers and strings, that are preset by bioware or yourself that can be referenced easily for important stuff like spells/effects/abilities/etc. For example you are calling a Function that sets an AC effect on a target you can then go into the constants and use AC_NATURAL_BONUS for natural AC or AC_DODGE_BONUS for dodge AC.

Image

Below you can see an object oPC is set to OBJECT_SELF. Object self refers to the caller of the spell, feat, or ability. If you are casting a spell as a player character you are the OBJECT_SELF in the code. This can change if you pass or launch a new script onto a new object or player. We will go over examples in the future.

Image

When activating something like a widget you have to use the function called GetItemActivator() and it will pull the creature or player that is using the widget. The reason you wouldn't use OBJECT_SELF in this case is because OBJECT_SELF would pull the widget not the PC. The widget is the one actually casting the ability, not the player, in this instance.

Image

When casting a spell you would use the function GetSpellTargetObject() to assign an object variable to your target. OBJECT_SELF would be the caster. We will demonstrate this further in an example spell script at the end.

Image

In special circumstances, when having a place-able you want the player to interact with and have something happen you would make a script and inside that place-able's events/scripts section, in properties, you would put that script in the OnUsed Section.

When grabbing the PC who interacted with the PLC you would use GetLastUsedBy() as shown below. The OBJECT_SELF in this case would be the PLC.

Image

NWN Wiki + NWN Lexicon Utilization

In the Neverwinter Nights Development Resources/Links post you can find links to NWN Wiki and the NWN Lexicon. NWN Lexicon is a critical resource for finding, using, and understanding Functions that Bioware and now Beamdog has introduced into the toolset. If you aren't ever sure what a function does then look it up in the lexicon.

The NWN Wiki is powerful also because it can be a quick reference to search for scripts. Look at Acid Splash on NWN Wiki, at the very bottom you can find that the script associated with this spell is X0_S0_AcidSplash.nss

The other way to find what spells or feats are associated with what scripts is to have access to the hak files which are called .2das. How to search base game 2das and work with them will be within the hak guides.

Our First Spell!

This is going to be a simple attack spell. Let's break down what is happening in this script.

Our caster in this instant is the OBJECT_SELF. As you can see we set the OBJECT_SELF to the object oPC.

Our target in this case can be obtained with GetSpellTargetObject() function. Now we have both the caster as an object and then the target. We can now get things done.

Something to note about spells, when created by you or Bioware they can be made to function a number of ways via haks. You can have them only being able to be used on single targets, or targets + locations. In this case our spell can only be used on single targets, but if it could also be used on locations we would have used another function to grab that location variable. Fireball for instance uses the function GetSpellTargetLocation().

We are generating a damage dice for this spell so we are keeping it simple. We are getting an integer called nRandom by using the function Random(), we covered this function in an earlier guide. The reason we are adding a +1 at the end is so our value is between 1 and 6.

Now we are covering something brand new. There is a new variable called effect in play. This effect variable is specifically used in NWScript for effects of all kinds such as damage, visual effects, and other changes we can enact in game.

First we get an effect variable called eVFX and using the EffectVisualEffect() function to grab a visual effect for our spell. That list of visual effects can be found in both the NWN Lexicon and the Constants table on the right.

Now we are getting an effect variable called eDamage. We are using our Random damage integer and classifying it as acid damage using our DAMAGE_TYPE_ACID constant which can be found on the table on the right.

Lastly we are using an effect variable called eLink alongside a new function called EffectLinkEffect(). This function combines effects into a single effect. This is important for simplifying tracking effects and also when removing effects later on. It is easier to remove one combined effect than four or five separate ones.

Finally we use the base game function called ApplyEffectToObject(). This function is the conclusion of our script and we tie in our effect eLink our object oTarget and then we declare what kind of effect it is. There are three different kinds: DURATION_TYPE_INSTANT, DURATION_TYPE_TEMPORARY, and DURATION_TYPE_PERMANENT. Instant is used for immediate effects like damage spells, temporary is used for buffs or other effects with durations, and permanent is for effects that are, well, permanent, with no duration.

Image
Post Reply