Decoding the .tre filesHere's what I've got, which isn't much. I write ints as [NN] for brevity, eg "0x0A 0x00 0x00 0x00" = "[0A]". Anywhere you see [nn], I mean a 4-byte int.
Each NPC has a .tre file named with the NPC's entity number. So Gunther (entity 66) has his conversation tree stored in the file "66.tre".
Conversation strings are stored as a series of string pairs, which I'll call "exchanges", where the string the player can say is immediately followed by the NPC's response.
The exchange is preceded by two ints.
The first, int1, is the exchange id, which can be anything so long as it's unique within the file. Typically, it starts at 0 and increments for each exchange, but it doesn't need to: they are sometimes randomly ordered in the file, with gaps. This is typically [00] for the initial greeting, which is played only once, when you first meet the character, and [01] for the subsequent greeting that you see any other time, though I suspect that these numbers are not necessary.
The second, int2, is the "category". This is [00] for the initial-meeting greeting (which always mentions the name of the character), and [01] for the subsequent-meetings greeting. All other values are taken from int11, described below.
Then come the two "conversation" strings. Either or both can be zero length. Each conversation string is stored as int stringlength, then the ascii characters, with no terminator, like "[04]Fred". So a zero length one is [00]. In my examples, I put [LL] to mean "I'm too lazy to calculate this".
In these strings, the \n character can be used on its own within a conversation string to break the line. This is most often seen as \n\n for a blank line. The $ character will be replaced by the player name (but only at display time: it counts as a single character for the string length int, of course!)
The exchange is followed by seven ints (which I've numbered int5 to int11), then four "data" strings. Unlike the conversation strings, these have no length int, and are terminated by \r\n.
The number in int5 is a quest number to test, or zero. Then int6 stores the quest stage to test against, or zero. If both are non-zero, then the exchange will only be available if the quest is at that stage. There is no case where only one of these ints is zero.
Int7 is unknown. It is only set in the files listed below. Other than zero, no value appears in more than one file. No file contains more than two values. Most values are used once only, but 14, 15, and 27 are used up to three times. Higher file numbers contain higher int7 numbers. The numbers do not usually correspond to any exchange id in the file.
Code:
int7val : file : times found
06 : 58.tre : x 1
11 : 56.tre : x 1
12 : 56.tre : x 1
14 : 67.tre : x 2
16 : 67.tre : x 1
17 : 63.tre : x 1
20 : 83.tre : x 1
21 : 72.tre : x 1
22 : 73.tre : x 1
25 : 77.tre : x 3
27 : 80.tre : x 3
45 : 85.tre : x 1
46 : 85.tre : x 1
47 : 76.tre : x 1
49 : 86.tre : x 1
50 : 86.tre : x 1
Int8 is unknown. It is set in the same files as int7, except for 63 or 76. Values appear across multiple files, and multiple times in the same file. The numbers always correspond to an existing exchange id in the file, but this may be coincidence, as they are all low numbers.
Code:
int7val : file : times found
1:67.tre:x 2
1:72.tre:x 1
1:77.tre:x 2
1:86.tre:x 1
2:67.tre:x 1
2:80.tre:x 3
2:86.tre:x 1
5:56.tre:x 1
9:56.tre:x 1
9:58.tre:x 1
9:73.tre:x 1
9:77.tre:x 1
9:83.tre:x 1
9:85.tre:x 1
12:85.tre:x 1
Int9 and int10 are always zero.
Int11 is the next conversational category to display options for. -1 sends the player back to the initial greeting, and is only used in exchanges where the NPC string is empty. If int11 points to a category that does not exist, then the conversation terminates. However, in this case, there is always an "empty" exchange (see below) with the same exchange ID as the non-existent category ID.
The first data string, str3, is before the first \r\n, and is an optional item name, which I believe is the name of an item that needs to be in the PC inventory in order for the exchange to be offered as an option in the list. This is typically used when removing items:
Code:
[08][02][LL]"I got your thingy [Give Thingy]
[LL]"My thingy!"
[18][01][00][00][00][00][09]
Thingy\r\n
remove_item (Thingy) ; add_gold 10 ; quest 24 9\r\n
\r\n\r\n
With the rename_item command, this seems occasionally used as another way of tracking quest statuses, if another variable is needed instead of the the normal quest level mechanism.
As shown above, any script for the exchange is placed in the second data string, str3, before the next \r\n. The script is run before the NPC response is displayed, so the following exchange is displayed in a sensible order:
Code:
[02][02][LL]"Yes, I'd like to have a look at your inventory."
[LL]"Thanks for your patronage"
[00][00][00][00][00][00][03]
\r\n
init_trade (gunther)\r\n
\r\n\r\n
The next two data strings, str5 and str6, are always empty. Possibly one is for a script that runs after the NPC's text displays?
Since the only required part of the exchange is the incrementing id, you can and do see 'empty' exchanges like this. One of these exists for every conversational category that terminates a conversation, with its category id set to 0 and its exchange id set to the category id. I don't know why:
Code:
[03] - int1=id (ids of [00] are initial greetings, so won't be blank.)
[00] - int2=no category
[00] - int3+str1=zero length PC conversation string
[00] - int4+str2=zero length NPC conversation string
[00] - int5=no quest to check
[00] - int6=no quest stage to check
[00] - int7=unknown
[00] - int8=unknown, reference to another int1?
[00] - int9=unknown, always [00]
[00] - int10=unknown, always [00]
[00] - int11=no subsequent category
\r\n - str3=no item needed
\r\n - str4=no script to run
\r\n\r\n - str5+str6=empty data strings
So here's a full conversation tree:
Code:
Initial greeting: A man waves. "Hi, I'm Joe."
Subsequent greeting: Joe looks up and waves.
Then displays bottom level of this conversation tree.
"Train me!" / (training happens) "Thanks."
- "[Back]" / ""
"Trade with me!" / (trading happens) "Thanks."
- "[Back]" / ""
"Tell me about yourself." / "I'm boring."
- "Nevermind, then." / ""
- "Tell me anyway." / "I'm REALLY boring."
- "Nevermind, then." / ""
- "No really, tell me." / "Once upon a time...."
- "That was gripping!" / ""
(if not already told to get a fish)
"Give me a job!" / "OK, get me a fish."
- "OK, brb" / ""
- end convo.
- "I've got one on me." / "Great."
- "[Back]" / ""
(if told to get one, and has one)
"I've got you a fish." / "Great."
- "[Back]" / ""
"I have to go."
- end convo.
That becomes:
Code:
[00][00]
[LL=00] - empty PC string for greetings.
[LL]A man waves. "Hi, I'm Joe."
[00][00][00][00][00][00][02] - display cat 2.
\r\nquest 63 0\r\n\r\n\r\n - initial greeting zeroes the quest, just in case.
[01][01]
[LL=00]
[LL]Joe looks up and waves.
[00][00][00][00][00][00][02] - display cat 2.
\r\n\r\n\r\n\r\n
[02][02]
[LL]"Train me!"
[LL]"Thanks."[00][00][00][00][00][00][03]
\r\nteach_skill (Joe) 24\r\n\r\n\r\n
[03][03]
[LL][BACK]
[LL=00] - empty NPC string, because we're going back to the greeting.
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[04][02]
[LL]"Trade with me!"
[LL]"Thanks."[00][00][00][00][00][00][04]
\r\ninit_trade joe\r\n\r\n\r\n
[05][04]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[06][02]
[LL]"Tell me about yourself."
[LL]"I'm boring."
[00][00][00][00][00][00][05]
\r\n\r\n\r\n\r\n
[07][05]
[LL]"Nevermind, then."
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[08][05]
[LL]"Tell me anyway."
[LL]"I'm REALLY boring."
[00][00][00][00][00][00][06]
\r\n\r\n\r\n\r\n
[09][06]
[LL]"Nevermind, then."
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[0a][06]
[LL]"No really, tell me."
[LL]"Once upon a time...."
[00][00][00][00][00][00][07]
\r\n\r\n\r\n\r\n
[0b][07]
[LL]"That was gripping!"
[LL]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[0c][02]
[LL]"Give me a job!"
[LL]"OK, get me a fish."
[00][00][00][00][00][00][08]
\r\nquest 63 1 ; give_item (Fishing Rod)\r\n\r\n\r\n
[0d][08]
[LL]"OK, brb"
[LL]
[00][00][00][00][00][00][0e]
\r\n\r\n\r\n\r\n
[0e][00] - empty "end conversation" entry.
[LL]
[LL]
[00][00][00][00][00][00][00]
\r\n\r\n\r\n\r\n
[0f][08]
[LL]"I've got one on me."
[LL]"Great."
[00][00][00][00][00][00][09]
Fish\r\nquest 63 9 ; add_gold 10 ; remove_item (Fish)\r\n\r\n\r\n
[10][09]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[11][02]
[LL]"I've got you a fish."
[LL]"Great."
[3f][01][00][00][00][00][0a] - [3f][01] means check quest 61 is stage 1
Fish\r\nquest 63 9 ; add_gold 10 ; remove_item (Fish)\r\n\r\n\r\n
[12][0a]
[LL][BACK]
[LL=00]
[00][00][00][00][00][00][-1]
\r\n\r\n\r\n\r\n
[13][02]
[LL]"I have to go."
[LL]
[00][00][00][00][00][00][14]
\r\n\r\n\r\n\r\n
[14][00] - empty "end conversation" entry.
[LL]
[LL]
[00][00][00][00][00][00][00]
\r\n\r\n\r\n\r\n
Now, I'm sure you can see scope for immediate optimisation there - all four "[Back]" blocks can be combined into one by giving them the same category ID, for a start. And those empty exchanges, same thing.
Except, this is never done. I don't know why: I assume the software they used to generate these files didn't consider the possibility of reusing exchanges for multiple responses.
But what that means is that there's plenty of space to fit in another conversation option or two into most conversation files.
OK, think I got all the procrastinatory marrow out of this bone that I can be bothered to get. Dropping it now.
[Edit: filled in the two unknown ints, so now we know how the trees work.]