Jump to content


Photo

Coding Friendships and Romances


7 replies to this topic

#1 Kaeloree

Kaeloree

    Head Molder

  • Administrator
  • 9148 posts

Posted 12 September 2008 - 03:29 PM

How to code friendships and romances!


Index

Introduction
Coding a friendship or a romance is, in actuality, simpler than most people believe. It's the writing-part which takes the longest!

In this tutorial I won't go into the creative part of writing a romance or a friendship, but the technical part. Even for the creative types, this isn't difficult--the difficult part is convincing yourself to get started. Once you've started, it's much easier than it seems to be.

Throughout this short tutorial there will be several Notes. Make sure to read these, they're rather important!


Planning the dialogues
Before jumping into the coding aspect, start by writing down the plan for the talks, and if there are any special situations that they occur in. In our example mod, there will be 8 friendship talks with 1 happening in a forest, and one happening before rest in an inn.
 

Talk 1: Boring, boring boring. I hate adventuring, don't you?
Talk 2: Stains. I just hate them. They're so hard to get out of clothes, and what with having no regular access to hot water--argh!
Talk 3: , I don't mean to alarm you but YOU HAVE A HUGE SPIDER IN YOUR HAIR. God I hate the wild.(Occurs in forest.)
Talk 4: What do you call a deer with no eyes? No eyed-deer! Ahaha, I kill myself. Literally, I have no sense of humour, sometimes I just want to...
Talk 5: There's no way I could convince you to stay in civilisation? I hate all this trekking through the wilderness.
Talk 6: Oh thank Selune for nice, warm beds and hot meals and steaming baths... (Occurs before rest in an inn.)
Talk 7: (wail) My dress! My beautiful beautiful dress! You ruined it!
Talk 8: You know, sometimes I wonder whether I'd make a good mother. Do you think you'd make a good parent? Frankly, I don't.


Placeholder dialogues
Once you have the talk topics down, we're going to put in some "dummy" talks. This is a great way to test the scripting of a romance or friendship without actually having written it. I usually test out whatever track I'm coding in this way before I've written most of the talks; it can mean a lot less hassle later when things go wrong.

Put these dummy talks into your NPC's J (interjection) file. You can put it into the B (banter) file, but I find using the J file is the better option. Keep your B file as clean and uncluttered as it can be, or things can get confusing.

 

 

Note!: I am using ## as the prefix here; do not use this! If you don't already have your own prefix go and register one over at the Blackwyrm Lair Community Prefix list.

Note!: // before any line indicates it is a comment, and it won't be parsed (read) by WeiDU. You can put anything you want in a comment and it won't affect your script. Comment everything so that your scripts and dialogues are easily understandable! For longer comments, /* they work like this, and can go over multiple lines. */

Note!: See the ##MyNPCFriendTalks variable? It needs to go up by 2 for each dialogue, starting with 1. 1, 3, 5, 7... you get the picture! This is explained later. The TimerExpired just makes sure the timer has expired--and then when we increment the number of talks, we also set the timer for the next talk.

 

 

// 1. Hate adventuring
IF ~Global("##MyNPCFriendTalks","GLOBAL",1)~ ##F1
  SAY ~Boring, boring boring. I hate adventuring, don't you?~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",2) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT
END

// 2. Stains
IF ~Global("##MyNPCFriendTalks","GLOBAL",3)~ ##F2
  SAY ~Stains. I just hate them. They're so hard to get out of clothes, and what with having no regular access to hot water--argh!~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",4) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT
END

// 3. Hate the forest
IF ~Global("##MyNPCFriendTalks","GLOBAL",5)~ ##F3
  SAY ~, I don't mean to alarm you but YOU HAVE A HUGE SPIDER IN YOUR HAIR. God I hate the wild.~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",6) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT
END

// 4. Bad jokes
IF ~Global("##MyNPCFriendTalks","GLOBAL",7)~ ##F4
  SAY ~What do you call a deer with no eyes? No eyed-deer! Ahaha, I kill myself. Literally, I have no sense of humour, sometimes I just want to...~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",8) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT 
END
  
// 5. Hate adventuring, part II; let's find an inn! 
IF ~Global("##MyNPCFriendTalks","GLOBAL",9)~ ##F5
  SAY ~There's no way I could convince you to stay in civilisation? I hate all this trekking through the wilderness.~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",10) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT
END 

// 6. Yay for inns! 
IF ~Global("##MyNPCFriendTalks","GLOBAL",11)~ ##F6 
  SAY ~Oh thank Selune for nice, warm beds and hot meals and steaming baths...~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",12) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT 
END

// 7. Ruined dress
IF ~Global("##MyNPCFriendTalks","GLOBAL",13)~ ##F7 
  SAY ~(wail) My dress! My beautiful beautiful dress! You ruined it! Last night, you spilt soup on it!~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",14) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT 
END 

// 8. Being a parent
IF ~Global("##MyNPCFriendTalks","GLOBAL",15)~ ##F8 
  SAY ~You know, sometimes I wonder whether I'd make a good mother. Do you think you'd make a good parent? Frankly, I don't.~
  IF ~~ DO ~SetGlobal("##MyNPCFriendTalks","GLOBAL",16) RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",3600)~ EXIT 
END
Scripting the dialogues
Now that we have the dummy placeholder talks in, we have to put in the scripting. Below is the basic scripting for a friendship or romance. This will go into your NPC's override script and trigger the dialogues in game.

 

 

// Dialogues
IF
    InParty(Myself)
    Global("##MyNPCFriendTalks","GLOBAL",0)
THEN
  RESPONSE #100
    RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",1200)
    SetGlobal("##MyNPCFriendTalks","GLOBAL",1)
END

This is our first section. If our NPC is in the party and they haven't spoken to the PC yet, start a timer of 1200 ticks and set the talk variable to 1.

 

 

 

 

Note!: A tick = one second. Therefore, 1200 seconds is 20 minutes of real time. Simply, to find the tick value of an amount of time, convert it to minutes and times it by 60.

 

 

IF
    InParty(Myself)                                             // If the NPC is in the party
    See(Player1)                                                // they can see the PC
    !StateCheck("MyNPC",CD_STATE_NOTVALID)                      // the NPC is valid for dialogue (ie not sleeping or stunned etc)
    !StateCheck(Player1,CD_STATE_NOTVALID)                      // the PC is valid for dialogue
    RealGlobalTimerExpired("##MyNPCFriendTalksTimer","GLOBAL")  // the timer has expired
    CombatCounter(0)                                            // the party isn't in combat
    !See([ENEMY])                                               // there are no enemies around
    OR(8)
      Global("##MyNPCFriendTalks","GLOBAL",1) // Woah! This isn't as confusing as it looks.
      Global("##MyNPCFriendTalks","GLOBAL",3) // This checks if a dialogue is ready to be triggered.
      Global("##MyNPCFriendTalks","GLOBAL",5) 
      Global("##MyNPCFriendTalks","GLOBAL",7)
      Global("##MyNPCFriendTalks","GLOBAL",9) 
      Global("##MyNPCFriendTalks","GLOBAL",11)
      Global("##MyNPCFriendTalks","GLOBAL",13) 
      Global("##MyNPCFriendTalks","GLOBAL",15)
THEN 
  RESPONSE #100 
    MoveToObject(Player1) // Move the NPC to the player 
    Dialogue(Player1) // and initiate dialogue! This will call the NPC's J file. 
END 

IF 
    InParty(Myself) // If the NPC is in the party
    See(Player1) // they can see the PC 
    RealGlobalTimerExpired("##MyNPCFriendTalksTimer","GLOBAL") // the timer has expired 
    OR(7) 
      Global("##MyNPCFriendTalks","GLOBAL",2) // And we're ready to make a dialogue ready to be triggered
      Global("##MyNPCFriendTalks","GLOBAL",4)
      Global("##MyNPCFriendTalks","GLOBAL",6)
      Global("##MyNPCFriendTalks","GLOBAL",8) 
      Global("##MyNPCFriendTalks","GLOBAL",10) 
      Global("##MyNPCFriendTalks","GLOBAL",12) 
      Global("##MyNPCFriendTalks","GLOBAL",14) 
THEN 
  RESPONSE #100
    IncrementGlobal("##MyNPCFriendTalks","GLOBAL",1) // increase the variable by one so the above block can trigger the dialogue.
END
These blocks are the meat of the script. I've commented it out, so hopefully it isn't too confusing for you.

A very simple explanation:
In the first block, it checks if the dialogue variable is odd; if it is, a dialogue is ready, and we'll trigger it by calling on the NPC's J file.
In the next block, it checks to see if the timer is expired and the variable is even; if the timer is expired, increment the variable by one so the block above it can trigger a dialogue.

But how does that trigger a dialogue? When the first block calls on the J file, the game checks if any of the conditions for talks are met. If our talk variable is odd, it means one of the talks can be triggered, so it does so.

 

 

Note!: CD_STATE_NOTVALID is not in the original game, so we have to add it in using the mod's tp2 file. Check here for a quick tutorial on how to do that, and the uses of CD_STATE_NOTVALID.

Now, that scripting is pretty straightforward. But there are two talks with specific conditions, right? How do we do those?


Dialogues with specific conditions
First, we comment out the appropriate lines from the second block in the script (which is the one that makes the dialogue able to trigger) and change the OR() value there appropriately. Looking at the plan we outlined at the beginning, we want to comment out the 3rd and 6th dialogues.

 

 

 

 

IF
    InParty(Myself)
    See(Player1)
    RealGlobalTimerExpired("##MyNPCFriendTalksTimer","GLOBAL")
    OR(5)                                                       // We've changed this to 5 (from 7), because there are now only 5 options.
      Global("##MyNPCFriendTalks","GLOBAL",2)                   // This can be confusing, but this is the 2nd dialogue. We set the first one in the very first block, remember?
      // Global("##MyNPCFriendTalks","GLOBAL",4)                // 1-3-5; the even number before five is obviously four, so this is the one we want to comment out.
      Global("##MyNPCFriendTalks","GLOBAL",6)
      Global("##MyNPCFriendTalks","GLOBAL",8) // Global("##MyNPCFriendTalks","GLOBAL",10) // And this is the sixth. 
      Global("##MyNPCFriendTalks","GLOBAL",12) 
      Global("##MyNPCFriendTalks","GLOBAL",14) 
THEN 
  RESPONSE #100 
    IncrementGlobal("##MyNPCFriendTalks","GLOBAL",1) 
END
So, now we need to add some blocks which will trigger these dialogues if the appropriate conditions are met. (You can remove the commented variables if you would like--they're no longer important.)


Triggering a dialogue in a specific location type
The first dialogue with specific conditions was #3: the party has to be in a forest area for it to occur. Now, this one is fairly easy. All we need to do is copy+paste the variable-incrementing block, change a few things around and add a new condition.

 

 

 

 

IF
    InParty(Myself)
    See(Player1)
    RealGlobalTimerExpired("##MyNPCFriendTalksTimer","GLOBAL")
    AreaType(FOREST)                                            // Now that was easy, wasn't it?
    Global("##MyNPCFriendTalks","GLOBAL",4)                     // We don't need the big OR() section, just the check specific to the dialogue we want to trigger.
THEN
  RESPONSE #100
    SetGlobal("##MyNPCFriendTalks","GLOBAL",5)                  // increase the variable by one so the triggering block can trigger the dialogue.
END

Easy, hey? Here's a quick list of the various area types you can specify, from AreaType.IDS.

 

 

  • OUTDOOR
  • DAYNIGHT
  • WEATHER
  • CITY
  • FOREST
  • DUNGEON
  • EXTENDEDNIGHT
  • CANRESTOTHER

The next one isn't quite so easy.
 

Note!: There are plenty of other conditions you can use; take a look at the IESDP for a complete list of script triggers.


Triggering dialogues before rest
When you want something to happen before rest, it needs to go into the NPC's dream script. All NPC dream scripts are checked right before resting. An NPCs dream script is the same as their override script, with a "D" on the end. So, if the override script is ##NPC the dream script would be ##NPCD.

 

 

Note!: A filename must be 8 letters or less. If your NPC's override script is 8 characters, you have to remove the last letter in order to add the D. Hence, ####NPCS would become ####NPCD.

So the scripting block that triggers the sixth dialogue must be in our NPC's dream script.



Triggering a dialogue in a specific area (or areas)
Dialogue 6 also only occurs in an inn. This is pretty simple, so don't panic!

Similarly to the scripting block for the 3rd dialogue, we just copy+paste the variable-incrementing block and change a few things around.

 

 

 

 

IF
    InParty(Myself)
    See(Player1)
    RealGlobalTimerExpired("##MyNPCFriendTalksTimer","GLOBAL")
    OR(11)
        AreaCheck("AR0021")                                     // City Gates - Crooked Crane 1st floor
        AreaCheck("AR0313")                                     // Docks - Sea's Bounty 1st floor
        AreaCheck("AR0406")                                     // Slums - Copper Coronet
        AreaCheck("AR0509")                                     // Bridge - Five Flagons 1st floor
        AreaCheck("AR0513")                                     // Bridge - Calbor's Inn 1st floor
        AreaCheck("AR0522")                                     // Bridge - Five Flagons 1st floor (stronghold)
        AreaCheck("AR0704")                                     // Waukeen's Promenade - Mithrest Inn
        AreaCheck("AR0709")                                     // Waukeen's Promenade - Den of the Seven Vales
        AreaCheck("AR1105")                                     // Umar Hills - Imnesvale Inn
        AreaCheck("AR1602")                                     // Brynnlaw - Brynnlaw Inn
        AreaCheck("AR2010")                                     // Trademeet - Vytori's Pub
    Global("##MyNPCFriendTalks","GLOBAL",10)
THEN
  RESPONSE #100
    SetGlobal("##MyNPCFriendTalks","GLOBAL",11)                 // increase the variable by one so the triggering block can trigger the dialogue.
    MoveToObject(Player1)                                       // Move the NPC to the player
    Dialogue(Player1)                                           // and initiate dialogue! This will call the NPC's J file.
END

You'll notice that instead of relying on the NPC's main script to trigger the dialogue, I've done it in the same block. This is because the game doesn't check the NPC's override script before resting, only the dream script, so it wouldn't trigger at the right time.

There are quite a few inns in the game, as you can see. Feel free to copy+paste this section instead of finding them all yourself.


Addendum: Friendship and Romance checks
But your NPC won't romance or befriend just any PC, will they? Perhaps they only like druidic elves, or tall paladins. In order to do this, we need to make some changes to the first block which initiates the dialogue track.

First up, we need to decide under what conditions our NPC will befriend the PC. Let's say that our NPC only wants to talk to non-evil members of the shorter races.

 

 

 

 

// Dialogues
IF
    InParty(Myself)
    Global("##MyNPCFriendCheck","GLOBAL",0)                     // To ensure this block doesn't run more than once
    Global("##MyNPCFriendship","GLOBAL",0)                      // The variable which determines whether the NPC has decided to befriend the PC; a very useful variable!
    Global("##MyNPCFriendTalks","GLOBAL",0)
    OR(3)                                                       // If the PC is one of the wee races
      Race(Player1,GNOME)
      Race(Player1,DWARF)
      Race(Player1,HALFLING)
    !Alignment(Player1,MASK_EVIL)                               // and isn't evil (! means "is not")
THEN
  RESPONSE #100
    RealSetGlobalTimer("##MyNPCFriendTalksTimer","GLOBAL",1200) // First up, set the timer
    SetGlobal("##MyNPCFriendTalks","GLOBAL",1)                  // set the dialogue variable to 1
    SetGlobal("##MyNPCFriendCheck","GLOBAL",1)                  // Set the checking variable to 1
    SetGlobal("##MyNPFriendship","GLOBAL",1)
END

Now the friendship dialogues will only begin if the PC is one of the small folk and isn't evil. This block will keep running until the requirements are met--this is useful if you have a changing variable such as Reputation. If the requirements are never met, so be it.

See? This really isn't so hard after all!



Recap
Let's recap briefly. This is what you should have so far, in the appropriate files.

Attached File  mynpcj.d   2.92K   292 downloads
Attached File  mynpc.baf   2.04K   305 downloads
Attached File  mynpcd.baf   646bytes   256 downloads


Conclusion
And that there is the very basics of coding a friendship or a romance! There are many other things you can do, but that's the core of it. Even for complex NPCs you often don't need scripting much more complex than this; all it involves is extra scripting blocks to trigger dialogues under certain conditions. Simple, hey?

Of course, you'll need to replace the dummy placeholder dialogues with real dialogues, but apart from that, you've got it done. Now you just need to test it in the game--and if it doesn't work and you're stumped as to why, the friendly folk at the IE Modding Help forum would be happy to lend you a hand.

I hope this tutorial has been helpful! Feel free to make suggestions or ask any questions if anything is unclear.

Happy modding!



#2 Kulyok

Kulyok
  • Member
  • 2398 posts

Posted 12 September 2008 - 10:07 PM

Cool stuff. You read minds, don't you? I coded Xan exactly this way: with plans, dummy dialogues and everything. (Only, I told myself: "Gee, these scripts are scary. I'd better finish writing actual talks first." Works either way, though.)

#3 Bardess

Bardess

    the Eternal Noob

  • Member
  • 483 posts

Posted 13 September 2008 - 03:57 AM

Ooh, neat! ^.^ *bookmarks page*
And I really needed that list of inn areas... :whistling:
"Farewell, Charname, and may Irenicus get you!"

Mazzy to Edwin:
"When science finally locates the center of the planes, I'm sure you'll be taken aback to find that you're not it."

- Author of lil one-day Moddie fox. -

#4 Kaeloree

Kaeloree

    Head Molder

  • Administrator
  • 9148 posts

Posted 13 September 2008 - 04:39 PM

Cool stuff. You read minds, don't you? I coded Xan exactly this way: with plans, dummy dialogues and everything. (Only, I told myself: "Gee, these scripts are scary. I'd better finish writing actual talks first." Works either way, though.)

Heh! I guess it's just a good way of doing things. The latest mod I've written/coded I didn't actually plan, it was more of an organic process... very interesting to write, not to say the least. I think the planning method is certainly easier, though!

In any case--thank you, and Bardess, I hope it is helpful! :)

#5 Bardess

Bardess

    the Eternal Noob

  • Member
  • 483 posts

Posted 13 September 2008 - 04:50 PM

The rest-at-inn talk works! It's alive! :woot:
"Farewell, Charname, and may Irenicus get you!"

Mazzy to Edwin:
"When science finally locates the center of the planes, I'm sure you'll be taken aback to find that you're not it."

- Author of lil one-day Moddie fox. -

#6 Kaeloree

Kaeloree

    Head Molder

  • Administrator
  • 9148 posts

Posted 31 October 2008 - 10:59 PM

Updated with a fix to ensure things work correctly. Thanks to witya for pointing it out!

#7 -kimmuryiel-

-kimmuryiel-
  • Guest

Posted 21 August 2013 - 03:20 PM

erm, is there a tutorial if I want my NPC to romance another NPC if the PC doesn't romance either one?



#8 Kaeloree

Kaeloree

    Head Molder

  • Administrator
  • 9148 posts

Posted 21 August 2013 - 03:45 PM

Basically, you'd just make a series of NPC banters. :)





Reply to this topic