What’s that title mean? That while you can drive nails and screw-in screws without tools, it’s a much longer and more exasperating process…
Tools for development are always needed. Besides my map and graphic editors, I have also wrote tools in Windows (in C# .NET) to handle some of the more automatable tasks. My text compression is an easy one; all I had to do recently to it was modify it to process a whole text fine line-by-line so that I could paste an entire block instead of one at a time.
But until I got VERY deep into design on transactions, I hadn’t realized that doing things by hand wasn’t going to cut it.
I’ve made one other decision; all NPC interaction is now tracked through transactions. This includes buying and selling items. As I was writing code, I realized it was just easier to have a single entry point for everything.
My original plan was to just hand-code the transactions myself, storing them in an Excel spreadsheet. I started to write out text files to describe each NPC and what they were doing. But as I did this, I realized how complicated it was getting. Plus, every small change would force me to re-do every single reference that was to an offset position. I would be reduced to gibbering insanity in a VERY short order if I had to do that.
The transaction system is a word (2-byte) language that tells the game what each NPC does. For example, the code to display a message on screen is “!MSG” followed by a list of dialogue codes. Dialogue is structured as “0-5″, which means dialogue file 0, message 5. If I have several comma-separated messages, it loads and displays all of them in a block.
Here’s an example of a text file describing a sage in the first town:
#Sage ?LOCAL_FLAG 0,Visited,NotVisited NotVisited !MSG 2-26,2-11 !LOCAL_FLAG 0=1 !BRANCH OptBuild Visited !PMSG Two,1-14,1-33 OptBuild !OPTION 0-6,Q1,1-12,Q2 ?QUEST_FLAG 1,OptBuild1,Select OptBuild1 !OPTION 0-7,Q3 Select !INPUT Select Q1 !MSG 1-13,2-20 !INPUT AnyKey !BRANCH Start Q2 !MSG 2-25,2-18,1-6 !INPUT AnyKey !BRANCH Start Q3 !MSG 3-3 !INPUT AnyKey !BRANCH Start #Sage end
The # symbol is used to indicate the start and end of a transaction block. This allows me to add commentary in-between blocks if I need to; my intent is to have one large text file containing all of a disk’s transactions for processing.
When in a block, there are two kinds of commands, actions (!) and queries (?). Queries check values and branch to labels inside the transaction based on true/false conditions. Actions do things like display messages, alter data both in memory and on disk, start a battle, etc. Any line that has neither a ? or a ! is considered a label and gets added to a list to note it’s position in the transaction array. (For now I don’t have a “comment” symbol, but I could add one if I need to.)
Let’s go over it, shall we?
So first, we check a local flag. There are eight of them in a byte, so they are referred to by position, 0-7. Every NPC in the game has a local flag store of a byte, for tracking changes and states. #0 on all of them is being treated as the “visited” flag. This indicates whether or not this NPC has been interacted with before. This lets us have an NPC actually remember that the player has interacted with them already and respond as such. The first label after the flag number is the true case, and the second is the false case.
NotVisited !MSG 2-26,2-11 !LOCAL_FLAG 0=1 !BRANCH OptBuild
If the player has not visited the Sage before, he has a few greeting messages. The visited flag is then set to 1, and we branch to the OptBuild label. After this, the player won’t see those messages again. When the player leaves the map, all the existing mob data is saved, including the new flag states, so returning later won’t restore them to blank.
Visited !PMSG Two,1-14,1-33
If you have visited him already, he has a follow-up greeting. !PMSG is actually a “plural message” command. Given that the size of the party is entirely determined by the player, I wanted a message system that could deliver multiple messages based on how big the party is. The keyword “Two” indicates there are two variations, a single and a multiple. The first message is the singular party member version, the second message is the multiple party member version. There is also a “Three” version for cases where an NPC may say “You”, “Both of you” or “All of you”.
OptBuild !OPTION 0-6,Q1,1-12,Q2 ?QUEST_FLAG 1,OptBuild1,Select OptBuild1 !OPTION 0-7,Q3
The !OPTION command specifies a number of dialogues and branches that are choices for the player to make. These are added to a local memory stack for processing later. Two are added for two questions that are always available to ask. Then we check the status of a quest flag (#1) to see if it’s true or false. If a particular quest has been completed, we add a third option.
Select !INPUT Select
The !INPUT command pauses the dialogue and waits for the player to choose one of the pre-determined options.
Q1 !MSG 1-13,2-20 !INPUT AnyKey !BRANCH Start Q2 !MSG 2-25,2-18,1-6 !INPUT AnyKey !BRANCH Start Q3 !MSG 3-3 !INPUT AnyKey !BRANCH Start
So for each option, the Sage has a different set of messages. It then waits for the player to press a key, then branches back to “Start”. This hard-coded label is always the beginning of the transaction.
And that’s the end of the transaction block. So I run my parser on it and I end up with this:
05 00 0E 04 0E 02 20 1A 20 0B 13 00 01 14 0F 00 10 0E 10 21 11 02 00 06 00 2A 10 0C 00 34 06 01 22 28 11 01 00 07 00 40 12 01 0E 02 10 0D 20 14 12 00 01 00 0E 03 20 19 20 12 10 06 12 00 01 00 0E 01 30 03 12 00 01 00
72 bytes, or 36 words, to define the entire transaction!
Transactions are all stored as 2-byte records in one large file per disk. Looking over my initial calculations of size for different things, I will end up with a couple thousand records per disk. If sizes remain at a consistent amount, I may also increase the record size and pad out some of them with zeroes for quicker load times. (I don’t think it should prove to be a particular problem; the TI disk controller loads an entire sector into VDP anyway so as long as transactions don’t cross sector lines it will remain fairly fast.)
Now I just need to write up the code to process the transactions and do all the displays… fun fun!