How to code friendships and romances!
Index
- Introduction
- Planning the dialogues
- Placeholder dialogues
- Scripting the dialogues
- Dialogues with specific conditions
- Triggering a dialogue in a specific location type
- Triggering a dialogue before rest
- Triggering a dialogue in a specific area (or areas)
- Addendum: Friendship and Romance checks
- Recap
- Conclusion
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 ENDScripting 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. ENDThese 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) ENDSo, 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.
mynpcj.d 2.92K
878 downloads
mynpc.baf 2.04K
868 downloads
mynpcd.baf 646bytes
798 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!