Jump to content


Photo

Romance Authoring tutorial


  • Please log in to reply
1 reply to this topic

#1 Seifer

Seifer

    The best Anti-Paladin weapon is a tin opener...!

  • Member
  • 4505 posts

Posted 26 February 2005 - 05:21 AM

Rastor has kindly allowed us to pinch his romance authoring tutorial. Although placed here for ease of use, its readily available for download at his foum RPGDungeon



It has been brought to my attention recently that many people with great ideas for romanceable NPC mods don't know where to begin. They try looking at already existing romanceable mods and are immediately innundated with a variety of variables and numerous dialogue files and don't know how to make any sense of it. The seeming necessity to make flirtpacks in today's mods makes this all the more challenging and baffling. If this sounds like you, read on!

Globals

Bioware's default romances are driven by a number of variables. Essentially, these are the basic ones:

"XXXXMatch","GLOBAL": This global is set to 1 if the NPC will romance the player and set to 0 if the NPC and the player won't romance.
"XXXXRomanceActive","GLOBAL": This global is set to 0 if the romance hasn't started, 1 if the NPC is talking with the player but isn't in love with him/her yet, 2 if the NPC and player are involved in an exclusive romance, and 3 if the romance has been killed for whatever reason.
"XXXXRomance","GLOBAL": This is a timer that controls when the NPC will talk next to the player. Whenever it hits zero, the game checks for the necessary prerequisites for a talk and has the NPC talk to the player if appropriate.
"LoveTalk","LOCALS": This simply controls which dialogue the NPC will say to the player whenever it is time to speak again.
There are a number of NPC specific variables present in each romance as well, but those control things like quest progression, whether the NPC and player have had sex or not, etc. Those are not explicitly covered in this tutorial. If you write your own scripts for your NPC, you can implement these things as necessary.

Flirt packs generally use similar but differently named variables to the ones described above. They do not have to, however.

Writing Talks

WeiDU is by far the best tool to use for writing dialogues. The reason for this is ensuring compatibility with other mods. Now, you may be wondering how you go about coding your lovetalk. Well, first I would strongly recommend having your entire dialogue tree (NPC statements and corresponding replies) sketched out on paper first. This will make things much easier on you.

Having your NPC start a dialogue is very similar to any other dialogue initiated by an NPC. What you'll need to do is to include a block in your NPC's override script that says something like:

IF
Global("MyNPCMatch","GLOBAL",1)
Global("MyNPCRomanceActive","GLOBAL",1)
Global("LoveTalk","LOCALS",1)
GlobalTimerExpired("MyRomance","GLOBAL")
!AreaType(DUNGEON) // None of the Bioware NPCs will banter in dungeons and I suggest that you keep this tradition
!StateCheck(Player1,STATE_SLEEPING) // How's the player going to talk while unconscious?
InParty(Myself) // Jaheira will actually fire off lovetalks if she's not in the party.  You don't want your NPC to do that, do you?
See(Player1)
!StateCheck(Myself,STATE_SLEEPING) // How's your NPC going to talk while unconscious?
THEN
RESPONSE #100
  IncrementGlobal("LoveTalk","LOCALS",1)
  Interact(Player1)
END


Then in your NPC's B dialogue file (you can also use J, but that's quite unconventional), you'll need to include the NPC's dialogue:

IF ~Global("MynpcRomanceActive","GLOBAL",1) Global("Lovetalk","LOCALS",2) Global("MynpcMatch","GLOBAL",1) GlobalTimerExpired("MyRomance","GLOBAL")~ THEN BEGIN RomanceTalk1
  SAY ~blah blah blah~ // You'll type whatever your NPC says into the dialogue
  IF ~~ THEN REPLY ~blah~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ /* You can actually set the timer to whatever you want. */ THEN GOTO NextLine
  IF ~~ THEN REPLY ~blah2~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ THEN GOTO NextLine2
END


That's the basic pattern to do lovetalks. Anything that's not a top-level text (starts the conversation) is done using basic WeiDU techniques. Simply change the variable names for conditionals as needed to get your romance coded.

Flirtpacks

While flirtpacks are by no means necessary for a romanceable NPC, many modders choose to include them most likely because every other NPC mod out there with a romance includes them. So, how do you code them? It is actually not much different than romances.

In the NPC's override script:

IF
Global("MynpcRomanceActive","GLOBAL",1)
GlobalTimerExpired("Flirt","LOCALS") // This can be a local or global variable.  It doesn't really matter.
See(Player1)
InParty(Myself)
!StateCheck(Player1,STATE_SLEEPING)
THEN
RESPONSE #100
  StartDialogue(Player1)
END


Many modders like to use a counter to measure the number of times that the player flirts with the NPC. While you are certainly able to do this, it is not really necessary. The reason that this is done is if you want to keep track of how affectionate the player is to the NPC. This could be useful if you are trying to do something such as including special dialogues for more affectionate players.

Use the same techinique as you used to code the NPC's lines for the romance dialogue, only this time put the lines into the NPC's J file. The flirtpacks and romance dialogues should go in different dialogue files.

For player initated flirts, all you need is a dialogue state in the NPC's J file (must be J file) that says IF ~IsGabber(Player1)~ THEN BEGIN FlirtSelection.

Weighting

To force the romance dialogues to fire exactly when you want them to, the romances should be weighted sequencally so that the first lovetalk gets the lowest weight, second one gets second lowest, etc. The same applies to flirts.

Often, you'll want these to be the lowest weights in the dialogue file. This will ensure that the game will check the romance talks first to attempt to find a banter to fire. If you don't do this then your NPC might skip over lovetalks occasionally or whatnot.

Now you have all the basics needed to code a simple NPC romance.


Advanced Possibilities

By now you've realized that what's been covered so far is barely anything that can (and almost always) is done with NPC romances. Flirts have random options, certain lovetalks only go off when the player rests and after the player wakes up, NPCs may have fights that spawn in at a certain stage of the romance, etc. These involve trickier scripting, so if you have no idea how to do scripts, then I would not recommend trying this sort of thing.

Sleep Scripts

All NPCs have scripts which the game checks whenever the party rests. These are set in the pdialog.2da file, but are generally XXXXXD.bcs, where XXXXX is the name of the NPC. In order to fire a script at rest, you need merely to put the necessary code to fire a lovetalk into this script. Use the script that I provided earlier as an example, changing the variables as needed.

In order to do the "Morning After" talks, include PartyRested() into the relevant block of the NPC's primary override script.

For example:

IF
Global("MynpcRomanceActive","GLOBAL",2)
Global("Lovetalk","LOCALS",19)
GlobalTimerExpired("MyRomance","GLOBAL)
PartyRested()
InParty(Myself)
See(Player1)
THEN
RESPONSE #100
  IncrementGlobal("Lovetalk","LOCALS",1)
  Interact(Player1)
END

(on rest-add to XXXXXD.bcs)

IF
Global("MynpcRomanceActive","GLOBAL",2)
Global("Lovetalk","LOCALS",17)
InParty(Myself)
See(Player1)
THEN
RESPONSE #100
  IncrementGlobal("Lovetalk","LOCALS",1)
  Interact(Player1)
END

Randomizing Flirts
To do this, you need to use the RandomNum(x,y) scripting trigger. See IESDP for more information on this trigger. You'll put this into the reply line of the Player initiated flirts, or into the GOTO section of the NPC initiated flirts.

An example for Cailean, graciously provided by Kismet:

APPEND FWCailej

//Cailean init flirts
IF ~Global("FWCaiDisableFlirts","GLOBAL",0)
See(Player1)
!StateCheck(Player1,STATE_SLEEPING)
CombatCounter(0)
Global("FWCaileanRomanceActive","GLOBAL",1)
Global("FWCaileanStartFlirt","GLOBAL",1)~ THEN BEGIN CaileanInitFlirts
SAY ~I'm going to start a flirt now.~
IF ~~ THEN DO ~IncrementGlobal("FWCaileanRandFlirt","LOCALS",1)~ GOTO CaileanInitHand
IF ~RandomNum(2,1)~ THEN DO ~IncrementGlobal("FWCaileanRandFlirt","LOCALS",1)~ GOTO CaileanInitKiss
IF ~RandomNum(2,2)~ THEN DO ~IncrementGlobal("FWCaileanRandFlirt","LOCALS",1)~ GOTO CaileanInitTickle
END

IF ~~ THEN BEGIN CaileanInitHand
SAY ~(Cailean takes a hold of your hand.)~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN CaileanInitKiss
SAY ~(Cailean gives you a kiss on the cheek.)~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN CaileanInitTickle
SAY ~(Cailean tickles your ribs.)~
IF ~~ THEN EXIT
END

//PC init flirts -- early
IF ~IsGabber(Player1)
CombatCounter(0)
GlobalGT("FWCaiInitLoveTalk","GLOBAL",6)
Global("FWCaileanQuestCompleted","GLOBAL",1)
Global("FWCaileanRomanceActive","GLOBAL",1)~ THEN BEGIN CaiFlirtBaseEarly
SAY ~(Cailean notices your approach.)~
+ ~RandomNum(4,1)~ + ~(Hold Cailean's hand.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + HandHold1
+ ~RandomNum(4,2)~ + ~(Hold Cailean's hand.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + HandHold2
+ ~RandomNum(4,3)~ + ~(Hold Cailean's hand.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + HandHold3
+ ~RandomNum(4,4)~ + ~(Hold Cailean's hand.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + HandHold1

+ ~RandomNum(4,1)~ + ~(Kiss Cailean.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + Kiss1
+ ~RandomNum(4,2)~ + ~(Kiss Cailean.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + Kiss2
+ ~RandomNum(4,3)~ + ~(Kiss Cailean.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + Kiss3
+ ~RandomNum(4,4)~ + ~(Kiss Cailean.)~ DO ~IncrementGlobal("FWCaileanFlirtCount","LOCALS",1)~ + Kiss4
++ ~Cailean, I like you very much, but please don't flirt with me anymore.~ DO ~SetGlobal("FWCaiDisableFlirts","GLOBAL",1)~ EXIT
++ ~(Say nothing.)~ EXIT
END

IF ~~ THEN BEGIN HandHold1
SAY ~Cailean holds your hand 1.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN HandHold2
SAY ~Cailean holds your hand 2.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN HandHold3
SAY ~Cailean holds your hand 3.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss1
SAY ~Cailean kisses you 1.~
= ~It's a really long kiss.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss2
SAY ~Cailean kisses you 2.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss3
SAY ~Cailean asks about the kiss~
++ ~Kiss answer 1.~ + Kiss3_1
++ ~Kiss answer 2.~ + Kiss3_2
++ ~Kiss answer 3.~ + Kiss3_3
END

IF ~~ THEN BEGIN Kiss3_1
SAY ~Cailean kisses you 3_1~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss3_2
SAY ~Cailean kisses you 3_2~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss3_3
SAY ~Cailean kisses you 3_3.~
IF ~~ THEN EXIT
END

IF ~~ THEN BEGIN Kiss4
SAY ~(Cailean kisses you 4.)~
IF ~~ THEN EXIT
END
END


Quests, Fights, and Other Miscellaneous Things
This guide is about writing romances. For doing quests and fights, script them into the NPC's override file, but such things are subject for another time.

how come you always look so damn cool in every photo I see you in?!?


Speaking of modding, I listened to IER 3 yesterday, so you can have another quote for your signature: how come you sound so damn cool, as well as look it? It's unfair. Seriously.


Still a cyberjock, still hacking the matrix, still unsure of what that means.

TeamBG member - http://www.teambg.eu

#2 Nastian

Nastian
  • Member
  • 280 posts

Posted 08 September 2005 - 12:00 PM

IF ~Global("MynpcRomanceActive","GLOBAL",1) Global("Lovetalk","LOCALS",2) Global("MynpcMatch","GLOBAL",1) GlobalTimerExpired("MyRomance","GLOBAL")~ THEN BEGIN RomanceTalk1
  SAY ~blah blah blah~ // You'll type whatever your NPC says into the dialogue
  IF ~~ THEN REPLY ~blah~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ /* You can actually set the timer to whatever you want. */ THEN GOTO NextLine
  IF ~~ THEN REPLY ~blah2~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ THEN GOTO NextLine2
END


I think that there's too much of THEN here.

Let's take a look at the TIMERS now, shall we? :)

IF
Global("MyNPCMatch","GLOBAL",1)
Global("MyNPCRomanceActive","GLOBAL",1)
Global("LoveTalk","LOCALS",1)
GlobalTimerExpired("MyRomance","GLOBAL")
!AreaType(DUNGEON) // None of the Bioware NPCs will banter in dungeons and I suggest that you keep this tradition
!StateCheck(Player1,STATE_SLEEPING) // How's the player going to talk while unconscious?
InParty(Myself) // Jaheira will actually fire off lovetalks if she's not in the party.  You don't want your NPC to do that, do you?
See(Player1)
!StateCheck(Myself,STATE_SLEEPING) // How's your NPC going to talk while unconscious?
THEN
RESPONSE #100
  IncrementGlobal("LoveTalk","LOCALS",1)
  Interact(Player1)
END


Everything looks fine, but...

IF ~Global("MynpcRomanceActive","GLOBAL",1) Global("Lovetalk","LOCALS",2) Global("MynpcMatch","GLOBAL",1) GlobalTimerExpired("MyRomance","GLOBAL")~ THEN BEGIN RomanceTalk1
  SAY ~blah blah blah~ // You'll type whatever your NPC says into the dialogue
  IF ~~ THEN REPLY ~blah~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ /* You can actually set the timer to whatever you want. */ THEN GOTO NextLine
  IF ~~ THEN REPLY ~blah2~ DO ~RealSetGlobalTimer("MyRomance","GLOBAL",30)~ THEN GOTO NextLine2
END


Edited by Nastian, 11 September 2005 - 06:30 AM.