Author Topic: How to ensure your banters always run when you want them to  (Read 16830 times)

Offline Kulyok

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 6253
  • Gender: Female
  • The perfect moment is now.
Many players play romances. Or have BG1 NPC Project installed. And from time to time, it happens that you press a button, and romance music plays, but nothing happens. And sometimes, Imoen or Xan or Ajantis will misfire their scenery talk. "<CHARNAME>, wow, is this a diamond?" - "(sigh) No, Imoen, a bounty notice."

Normally it happens when the player issues a command at the same time the NPC is about to speak his piece. This means that the conditions for the banter are met, the banter is ready to trigger, but it does not, because it was interrupted. This also means that it will very probably trigger next time the banter engine looks for a next banter or lovetalk to fire, and will probably look out of place.

(Sometimes it also happens in the case of very bad scripting, but this is not the point of this tutorial).

So, what to do?

Imagine that your NPC is called "MyNPC", has a script file MyNPC.baf and a banter file BMyNPC.d. Both are really text files, and you can open and edit them with Notepad, Crimson Editor or Context Editor, whichever you prefer.

Now, imagine you want to fire a banter on entering the Graveyard District in Athkatla. And you do not want it to trigger anywhere else, which can well happen, if the player issues a command at the same time your NPC is about to speak.

The trick is FIRST to set the variable for the banter in MyNPC.baf, THEN keep triggering the banter until it runs(again, in MyNPC.baf), and FINALLY, in your BMyNPC.d, increase the variable by one, so the banter will never ever run again. It works wonders.

(Bioware does it from time to time, too. Unfortunately, not always).

So, the code. First, your script file, MyNPC.baf:

Code: [Select]
IF
InParty(Myself)
See(Player1)                         // ensures that your NPC is in party and sees Player1
AreaCheck("AR0800")           // the banter happens in the Graveyard District of Athkatla
CombatCounter(0)
!See([ENEMY])                     // the banter will not run during combat
// You could add other conditions, depending on your banter.
Global("MyGraveyardBanter","GLOBAL",0) //"0" means that the banter hasn't happened, yet
THEN
RESPONSE #100
SetGlobal("MyGraveyardBanter","GLOBAL",1) //"1" means that it is time for the banter to happen, and for the next block in the script to start working...
END

IF
InParty(Myself)
See(Player1)                         // ensures that your NPC is in party and sees Player1
Global("MyGraveyardBanter","GLOBAL",1) //"1" means that it is time for the banter to happen, so we trigger it
THEN
RESPONSE #100
Interact(Player1)                 //  calls forth a banter from BMyNPC.d
END

And now, your banter file, BMyNPC.d:

Code: [Select]
IF ~Global("MyGraveyardBanter","GLOBAL",1)~ MyGraveyardBanterStart
// the condition in the banter must match the variable in the script precisely, or we are in trouble.
SAY ~Hey, <CHARNAME>, that's a graveyard! Let us lie down and die!~
++ ~Cool idea.~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",2)~ + MyGraveyardBanter1.1
++ ~Nah, first we must meet Bodhi.~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",2)~ + MyGraveyardBanter1.2
++ ~I don't care either way.~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",2)~ + MyGraveyardBanter1.2
END
// you must ensure that your variable is increased in each and every reply, so the condition Global("MyGraveyardBanter","GLOBAL",1) is never met again, and the banter never runs again.

IF ~~ MyGraveyardBanter1.1
SAY ~Finally!~
IF ~~ EXIT
END

IF ~~ MyGraveyardBanter1.2
SAY ~Aw, you're no fun at all...~
IF ~~ EXIT
END

If your banter is more complicated: say, you want it to trigger two minutes after you enter the graveyard, there is one extra step: setting a timer. So, your code will look like this:

Script file, MyNPC.baf:

Code: [Select]
IF
InParty(Myself)
AreaCheck("AR0800")            // the timer sets if the party is in the Graveyard District of Athkatla and NPC is in party, no other conditions
Global("MyGraveyardBanter","GLOBAL",0) //"0" means that the banter hasn't happened, and the timer hasn't been set, yet
THEN
RESPONSE #100
RealSetGlobalTimer("MyGraveyardTimer","GLOBAL",120) // We set a timer for two minutes
SetGlobal("MyGraveyardBanter","GLOBAL",1) //"1" now means that the timer is set. Now we're waiting until it expires...
END

IF
InParty(Myself)
See(Player1)                         // ensures that your NPC is in party and sees Player1
AreaCheck("AR0800")            // the banter happens in the Graveyard District of Athkatla
CombatCounter(0)
!See([ENEMY])                      // the banter will not run during combat
RealGlobalTimerExpired("MyGraveyardTimer","GLOBAL") // The timer has expired, two minutes have passed
Global("MyGraveyardBanter","GLOBAL",1)  // The timer has been set
THEN
RESPONSE #100
SetGlobal("MyGraveyardBanter","GLOBAL",2)  // The timer has now expired, it is actually time to run the banter
END

IF
InParty(Myself)
See(Player1)                         // ensures that your NPC is in party and sees Player1
Global("MyGraveyardBanter","GLOBAL",2)  // Time to trigger the banter
THEN
RESPONSE #100
Interact(Player1)                 //  calls forth a banter from BMyNPC.d
END

Banter file, BMyNPC.d:

Code: [Select]
IF ~Global("MyGraveyardBanter","GLOBAL",2)~ MyGraveyardBanterStart
// the condition in the banter must match the variable in the script precisely, or we are in trouble.
SAY ~Hey, <CHARNAME>, that's a graveyard! I've just noticed!~
++ ~Slow, aren't you?~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",3)~ + MyGraveyardBanter1.1
++ ~Really? Oh! A graveyard!~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",3)~ + MyGraveyardBanter1.2
++ ~Huh? What?~ DO ~SetGlobal("MyGraveyardBanter","GLOBAL",3)~ + MyGraveyardBanter1.2
END
// you must ensure that your variable is increased in each and every reply, so the condition Global("MyGraveyardBanter","GLOBAL",1) is never met again, and the banter never runs again.

IF ~~ MyGraveyardBanter1.1
SAY ~So much for trying to keep the conversation going, I see.~
IF ~~ EXIT
END

IF ~~ MyGraveyardBanter1.2
SAY ~Do you think the time is right to lie down and die? Ahhh... never mind.~
IF ~~ EXIT
END

And now, the most interesting part: romances and friendships. Same thing, only instead of one variable, we've got many.

Script file, MyNPC.baf:

Code: [Select]
//This block makes sure that a romance begins, if Player1 meets the requirements: here she should be a female human
IF
InParty(Myself)
Gender(Player1,FEMALE)
Race(Player1,HUMAN) //the conditions for romance match
Global("MyNPCLoveTalk","GLOBAL",0) //makes sure this block runs only once: afterwards MyNPCLoveTalk sets to 1
THEN
RESPONSE #100
RealSetGlobalTimer("MyNPCRomanceTimer","GLOBAL",3000)
SetGlobal("MyNPCRomanceActive","GLOBAL",1)
SetGlobal("MyNPCLoveTalk","GLOBAL",1)
END

IF
InParty(Myself)
See(Player1) // MyNPC is in party and sees Player1
RealGlobalTimerExpired("MyRomanceTimer","GLOBAL") // the timer has expired, so it is time for a new lovetalk
Global("MyNPCRomanceActive","GLOBAL",1) // romance is still active
!AreaType(DUNGEON) // no romancing underground
CombatCounter(0)
!See([ENEMY]) // no romancing during combat
OR(4) // let us pretend we have only four lovetalks. You could have as many as you like, of course.
Global("MyNPCLoveTalk","GLOBAL",1)
Global("MyNPCLoveTalk","GLOBAL",3)
Global("MyNPCLoveTalk","GLOBAL",5)
Global("MyNPCLoveTalk","GLOBAL",7) // "1,3,5,7" means - "between lovetalks", "2,4,6,8" means "it is time for a lovetalk"
THEN
RESPONSE #100
IncrementGlobal("MyNPCLoveTalk","GLOBAL",1) // the variable increases by one, telling that it is time for a lovetalk
END

IF
InParty(Myself)
See(Player1)                         // ensures that your NPC is in party and sees Player1
RealGlobalTimerExpired("MyRomanceTimer","GLOBAL") // this is actually important: this script block will only be fully checked when the timer has expired, so the performance wouldn't slow down
OR(4)
Global("MyNPCLoveTalk","GLOBAL",2)
Global("MyNPCLoveTalk","GLOBAL",4)
Global("MyNPCLoveTalk","GLOBAL",6)
Global("MyNPCLoveTalk","GLOBAL",8)
THEN
RESPONSE #100
Interact(Player1)
END

Banter file, BMyNPC.d: an example of a lovetalk's beginning

Code: [Select]
IF ~Global("MyNPCLoveTalk","GLOBAL",2)~ MyLovetalk1Starts
SAY ~Hey, <CHARNAME>, why don't we...~
++ ~NO!~ DO ~IncrementGlobal("MyNPCLoveTalk","GLOBAL",1) RealSetGlobalTimer("MyRomanceTimer","GLOBAL",3000)~ + MyLovetalk1.1
++ ~Ye-e-es?~ DO ~IncrementGlobal("MyNPCLoveTalk","GLOBAL",1) RealSetGlobalTimer("MyRomanceTimer","GLOBAL",3000)~ + MyLovetalk1.2
++ ~Oh, do go away.~ DO ~IncrementGlobal("MyNPCLoveTalk","GLOBAL",1) RealSetGlobalTimer("MyRomanceTimer","GLOBAL",3000)~ + MyLovetalk1.3
END

So, this is all. Your banters, friend- and lovetalks will now run smoothly, flawlessly, and without delay - if you check and re-check that the variables in your script file match the variables in your banter file perfectly.

Enjoy, and if you have any questions, feel free to ask.

Offline GeN1e

  • Planewalker
  • *****
  • Posts: 267
  • Gender: Male
Re: How to ensure your banters always run when you want them to
« Reply #1 on: October 27, 2008, 08:51:31 PM »
Is it the idea that whether interrupted or not, the Interact block won't set anything by itself and thus will be triggered over and over again, until successful? Now I do understand the source of certain glitches I've seen before...

Offline Kulyok

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 6253
  • Gender: Female
  • The perfect moment is now.
Re: How to ensure your banters always run when you want them to
« Reply #2 on: October 28, 2008, 01:26:51 AM »
It's best to use StartDialogueNoSet(). No, it won't cause glitches, because (see .d strings).

Offline Kaeloree

  • Planewalker
  • *****
  • Posts: 109
Re: How to ensure your banters always run when you want them to
« Reply #3 on: October 28, 2008, 02:59:21 AM »
Any particular reason for StartDialogueNoSet() rather than Dialogue() etc?

Offline Kulyok

  • Global Moderator
  • Planewalker
  • *****
  • Posts: 6253
  • Gender: Female
  • The perfect moment is now.
Re: How to ensure your banters always run when you want them to
« Reply #4 on: October 28, 2008, 03:14:59 AM »
StartDialogueNoSet() pauses the game, which is essential(an enemy will not appear around the corner, a messenger won't interrupt lovers, and so on). StartDialogueNoSet() also starts our banter immediately, while in Dialogue() NPC approaches the PC(another NPC) - since we repeat Dialogue() command in the last block, it will look as though our NPC is stuttering.

And even if we use Dialogue() only once, it will look bad - my personal experience with Amber NPC turned me off Dialogue() command for good. (The last one is my personal opinion, though; naturally, it is a matter of taste).

 

With Quick-Reply you can write a post when viewing a topic without loading a new page. You can still use bulletin board code and smileys as you would in a normal post.

Warning: this topic has not been posted in for at least 120 days.
Unless you're sure you want to reply, please consider starting a new topic.

Name: Email:
Verification:
Type the letters shown in the picture
Listen to the letters / Request another image
Type the letters shown in the picture:
What color is grass?:
What is the seventh word in this sentence?:
What is five minus two (use the full word)?: