Requests for Sample Toolings

For toolset tutorials as well as question and answers.
Locked
Zelknolf
Chosen of Forumamus, God of Forums
Posts: 6139
Joined: Tue Jul 05, 2005 7:04 pm

Requests for Sample Toolings

Post by Zelknolf »

If there's thing that you'd like to see covered with the sort of detail as the other examples, feel free to post here. I can't promise that I'll get to them all, or that the explanation of how to make it happen will make sense to everyone. I'll also say that I'll pointedly not do the specific application that you want (e.g. "I want to tool an assault quest for the orcs on this island!" will probably be responded to with a quest about gnolls in a cave or something-- the goal is to get other people to make things, and you can't much do that if I do everything for you) but I do know that seeing a concrete example of a thing being made can help people learn how to make the thing.

Requests so far...
-- an area transition created from placeables instead of triggers or doors
-- a secret door that is actually invisible to PCs until they make a search check
-- an NPC who prefers to fight with subdual damage
-- Using a lever to open a (presumably plot-locked?) door

Done...
-- A static quest arc requiring new creatures and a new area and containing multiple challenges, a sub-quest, and a repeatable aftermath quest.
Last edited by Zelknolf on Wed Jul 09, 2014 7:06 pm, edited 2 times in total.
Rumple C
Bard
Posts: 3561
Joined: Thu Jul 22, 2004 9:38 pm
Location: The ceiling.

Re: Requests for Sample Toolings

Post by Rumple C »

Pull a lever, open a door type deal.
12.August.2015: Never forget.
Zelknolf
Chosen of Forumamus, God of Forums
Posts: 6139
Joined: Tue Jul 05, 2005 7:04 pm

Re: Requests for Sample Toolings

Post by Zelknolf »

So all of the things on this list require scripting. I can write it; someone else can write it. Though if I write it we get a pretty-lame learning opportunity. "Use this and leave me alone."


So I have to give you simpler scripts to build up the knowledge you need to write the stuff you want. And I happen to have written a good one for explanation-- I have an NPC who I want to provide long-term care to PCs who walk up to her with, say, gut wounds (but maybe they're low-level PCs or whatever, and can't afford potions to recover, but also would like to heal more than 1 hit point when they rest).

So firstly, gotta set the mental model-- a script will do exactly what you tell it to, and nothing else, in exactly the order that you specify. Sections of the script might not be aware of one another (watch this -- it explains recursion and stack frames like you're five), but they'll still run in the order they're called.

In order to do anything in a given order, we have to know where we're starting. Which is your entry point. Which is why every script has a line like this in it:

Code: Select all

void main()
It sometimes doesn't look exactly like that, but we can cover those things as they become necessary. For this, void main.

Next, we have to say where stuff starts and stops. Precisely, we're going to define a scope -- compilers will often use this term (if a variable is "out of scope" that means that you declared it somewhere else, and can't use it where you're trying to), so I'm going to use it to. A scope is defined by brackets, and what it says to the program is "All of the stuff inside of these brackets is to be treated as a command." -- we'll use them inside of a few different contexts, but for right here.

Code: Select all

void main()
{
}
"When this script is run, do the stuff inside of these brackets." -- which, right now, is nothing.

But if you scroll up, I wanted to do long term healing from an NPC in a conversation. So now we get into functions-- functions are named things that you can tell a script to do. If you have script assist on, you have a list of them on the right side of the toolset, and a search box on top to look for them, and you can click on one for details. So, for instance,

Code: Select all

// Sends szMessage to all the Dungeon Masters currently on the server.
void SendMessageToAllDMs(string szMessage);
Means that there's a command named SendMessageToAllDMs which you can give stuff to (szMessage) and have it tell that stuff to every DM currently logged in.

Code: Select all

void main()
{
    SendMessageToAllDMs("u up?");
}
"When this script is run, send a message to all DMs on the server, asking them if they're up."
... but we don't need pervert servers.

Next comes variable declarations. Do do this, we need keywords -- whenever you use a keyword, that means that you're creating a new variable named the next thing that you write. In NWScript, your options are float, int, object (actually uint), string, effect, itemproperty, location, and vector. So if we were to combine that with the function "GetPCSpeaker", we can grow our script to this:

Code: Select all

void main()
{
	object oPC = GetPCSpeaker();
}
"When this script is run, declare an object named oPC, and set its value to be whatever comes back when you run GetPCSpeaker()"

This, though, isn't very useful-- the scope ends on the very next line, so we can never use oPC. What I really want to do is roll a skill check, right? We need to know if the NPC is good enough to bandage up our gut-shot PC. For this, we need libraries. Libraries are collections of functions that are not core to the operation of the language. You get a lot of useful things just by creating a script, but we specifically want ALFA's skill stuff. So we use the include statements to include a library into the available functions in our script.

Code: Select all

#include "acr_skills_i"

void main()
{
	object oPC = GetPCSpeaker();
}

And then we're going to construct something called a conditional out of that. To write the simple one like the one we're working on here, we're going to use an if statement. When you write an if statement, you put other stuff inside of the parenthesis, and all of the stuff inside of the parenthesis is going to evaluate until it's a single number. If that number is 0, then the next command (or next scope) will not run. If that number is anything but 0, then the next command is run.

For example...

Code: Select all

void main()
{
    if(1)
    {
        SendMessageToAllDMs("u up?");
    }
}
This script always makes booty calls.

Code: Select all

void main()
{
    if(0)
    {
        SendMessageToAllDMs("u up?");
    }
}
This script never makes booty calls.

What we want, though, is in the acr_skills_i library, specifically ACR_SkillCheck

Code: Select all

//!! Official skill check function for ALFA, takes into account skill synergy and whatever circumstance
//!!   modifiers it can; returns 0 (FALSE) or 1 (TRUE).  
//!! If no DC is provided, the function will return the roll result.
int ACR_SkillCheck(int nSkillID, object oPC, int nDC=0, int bShowRoll=FALSE, int bBroadcast=FALSE, int nModifier=0);
So we can put this into the conditional-- we know it will evaluate to 0 if the skill check fails, and it will evaluate to 1 if the skill check succeeds.

Code: Select all

void main()
{
	object oPC = GetPCSpeaker();
	
	if(ACR_SkillCheck(SKILL_HEAL, OBJECT_SELF, 15, TRUE, TRUE, 0))
	{
		SendMessageToAllDMs("u up?");
	}
}
This script only makes a booty call if the creature that called it passes a DC 15 heal check.

Now I used something called constants in there. Those all-caps words I gave to the script are defined elsewhere-- either in my libraries or just by the language. They're similar to variables (like oPC) except that you can't change them. SKILL_HEAL will always refer to the heal skill, but if there's any time when a function asks you for the integer that refers to a skill, you can say SKILL_HEAL instead of having to remember or look up that it's 4 (the way you have to do in the conversation editor).

In theory, I could write that script as...

Code: Select all

void main()
{
	object oPC = GetPCSpeaker();
	
	if(ACR_SkillCheck(4, 214748364, 15, 1, 1, 0))
	{
		SendMessageToAllDMs("u up?");
	}
}
... but that's really hard to read. (also it might break, based on who's logged in; we'll talk about OBJECT_SELF when we talk about action queues, which we must do to talk about the door lever).


At this point, the last thing we need to do is put something useful inside of the conditional. The function we want is inside of acr_resting_i -- so we have a second library to include. And the function is

Code: Select all

//! This function manages (toggles) the long term care status on demand
//!  - oPC: player to whom long term care applies
//!  - bStatus: boolean status - TRUE or FALSE
//!  - Returns: nothing
void ACR_SetLongTermCare(object oPC, int bStatus);
So I'm going to put the finished bit inside of a spoiler box. If you're keen on learning scripting, do the mental exercise for real reals. How do we make it so that if the heal check passes, the PC gets the benefit of long-term care?
Spoiler:

Code: Select all

#include "acr_resting_i"
#include "acr_skills_i"

void main()
{
	object oPC = GetPCSpeaker();
	
	if(ACR_SkillCheck(4, 214748364, 15, 1, 1, 0))
	{
		ACR_SetLongTermCare(oPC, TRUE);
	}
}
Locked