A little advance warning.. this page is kind of rambling and is updated at seemingly random times. It will remain that way until I'm done working on Retail Clerk '90.
What's the purpose of all this?
Back in 2019 I released a Sega Genesis game demo called Retail Clerk '89. It wasn't my first attempt, or even tenth, to create a game demo. All my previous attempts failed because I was too caught-up trying to build a reusable framework first. In a textbook sense that may be a good thing to do. In my reality it prevented me from ever finishing anything.
With Retail Clerk '89 I took a different approach - just start hacking away at a demo until it barely works. It wasn't pretty but I finished something this time. It's not exactly what I wanted to do. My starting idea was something like Tombs & Treasure where the mall hallways were the overworld and entering stores changed to that Shadowgate-like perspective. I'm not doing that with Retail Clerk '90 either but perhaps in whatever comes next. Anyway, by the time I was done I had the basics in place: walking around, talking to people, managing items, playing music, and text paging.
I knew I wasn't going to stop at one demo. I wanted to keep building on this until I had something more closely resembling my original plan. The first step is untangling the mess I made into modular parts. That's really the goal of Retail Clerk '90, take what I have and make it reusable.
This page is documenting my attempts at:
Along the way it's possible that someone reading this might learn something helpful.
Retail Clerk "Engine"
I've been doing professional software development for over 20 years and hobby development for even longer. Despite that I have no idea what the difference is between a framework, engine, library, and random assortment of related code. Yes, I know the technical definitions for each of these. Projects seem to choose one of these as a label with no regards to the definition.
I'm going with the Retail Clerk "Engine" - although I won't claim it meets the formal definition of a game engine today (or maybe even tomorrow).
Retail Clerk '89 had a main loop, it worked but was disorganized. It checked for things in an illogical order that resulted in a lot of jumping around. I wasn't close to having framerate issues because of it. It was just difficult to understand the flow, even for the odd person who wrote it.
In Retail Clerk '90 it is better organized into a flow I can explain. At the top is all the system and game initialization code. Obvious enough place for it.
Then we enter the main loop. At each pass through the loop we wait for a VBlank to occur (60 times a second). During that window of time controllers are read and sprite z-order is sorted. That second part is still a little hacky. Sprite z-ordering is difficult on the Genesis, maybe I'm not smart enough to understand it. I kept having issues where sprites were not overlapping correctly. Resorting the sprite list at every VBlank is overkill but it sure works. It's the only thing that has worked reliably for me.
After returning to the main loop we check for all the game states where the player is not playing the game. There are checks for the title screen, load screen, and information screen. If the game is in any of these states there's a branch to process the corresponding screen.
If all those checks fail then the player is actually playing the game. It had to happen eventually. Then there are checks for other states in order of importance. Checking for pause/unpause is first since the game can be paused at any time. They there's a check for whether an action is queued. Actions include things like talking to an NPC or changing rooms. If there are no actions queued the next check is for a scripted event. Think of this like checking for whether a cut-scene is running. After that is a check for whether the player is on a status (menu) screen. If all of those fail then the player is in the overworld doing stuff.
The last two check are for whether the player did something like open the menu, interact with an object, or move.
This general flow likely won't change much as I evolve this "engine". If I ever add battles (unlikely) there would be changes for sure.
The game itself is centered around something I called a "scene" for lack of a better term. A scene is the area the player is currently in. The definition includes everything required to draw the scene and the objects the player can interact with.
This idea was originally meant to represent the first-person Shadowgate-like rooms I mentioned before. I'm happy that ultimately this structure worked for everything. I really mean "everything".. everything in Retail Clerk '90 is a scene. Each store is a scene, the title screen is a scene, anything else is also a scene. It's a structure that is very reusable and makes creating new screens & places simple.
Let's breakdown what a scene object contains...
The first two fields are address pointers to the tiles and palettes used to draw the scene. The font and dialog tiles are loaded by default in every scene so they are not included in the definition. The Scenery and Text sections contain the instructions for what to draw. Each Scenery entry has an address pointer to a pattern which I admit is a confusing name since the Genesis VDP also has patterns. In this case it's the order to draw tiles. Text entries don't have a pattern since the text itself describes where formatting like line breaks occur. Text entries just have a pointer to the text, a draw location, and which palette to use.
The Objects list contains the location of things in the scene the player can interact with. It does not include draw instructions which can be confusing. Anything that needs to be drawn goes in the Scenery block.
Collision data is another address pointer to a list of 1s & 0s that are used to determine where the player can/can't walk.
The Exists list is simply four values that indicate what scene to load when the player leaves the current scene.
The NPC list is similar to the Objects list. This defines where NPCs will be drawn if any are in the scene. Which NPCs are in the scene is not defined here. The game maintains a list of NPC to Scene mappings allowing NPCs to change locations.
The last entry is a pointer to which music to play in the scene. I originally wanted different music in each store. That clearly did not pan out. That will probably wait until I try making a 16-bit CD game. Even then, expect a lot of bad public domain tracks.
This is not a bad place to transition to the build tooling. This section isn't quite complete yet, it's missing the memory map for example, but let's move on anyway. The build tooling is mostly (not exclusively) focused on things that go into a scene.
Build Tooling
In one of my many failed game development projects I started with a UI to build various objects but didn't have a great plan for how the game would actually work. This time around a UI, if I even decide to create one, will be the last piece.
The not-secret long-term goal of all this is to generate a game for multiple consoles. This means keeping all the external assets like images, palette definitions, and scene definitions in some convenient file format. At the moment this is a combination of png, json, and csv files.
Then I'm planning a set of tools to convert all these things into platform-specific code. The Genesis is first since that's what I already have working. Next will likely be another 68000-based system out of laziness. After I burn a couple months learning a new assembly language it will be something else.
You can see the working version of it in the RetailClerk90 repo. I'll maybe get around to documenting how that current assortment of code actually works.
In terms of what is being auto-generated today, I think looking at the ROM map is the best place to start.
The first block is not code - it's all the various constants used throughout the game. These are currently generated from csv files.
The header is more or less also a series of constants. I have that defined in the build script. This part will have to be reworked to support other consoles.
The next block is console-specific code - if I was smarter I'd separate it into CPU-specific code with wrappers for console-specific things. After that is the action script which is like 90% of the game logic. I would really like to abstract that out an intermediate language to make it more portable. This by far the biggest piece of work ahead of me in terms of building a reusable game engine.
The sound driver is last in the code block for kind of a silly reason. If if goes before the action script then it bumps some functions calls into a range where long branches are needed.
Outside of the console initialization data everything in the data block is generated. More to come on how each of these work.
Lookup tables are exactly what they sound like. I need to automated their generation next.
The section called "resources" is easy to confuse with the data block. Everything in the data block should be the same across versions. Things in resources are volatile based on the version. Text may vary based on language. Music files definitely would vary based on platform. Tiles may be the same in some cases but I expect not always. Looking at this now, palettes should probably go here too. I guess when I thought of the resources section I based it off how dlls are structured.
Let's go through an example build file section by section to see how everything is generated today. The latest version is here probably here.
{
"basePath":".",
"backupPath":"../../Backup/RetailClerk90/2021",
[...]
This is the most simple part, it's defining the working paths.
[...]
"memoryMap":{
"sourceFile":"design/constants/MemoryMap.csv",
"destinationFile":"src/const_MemoryMap.X68",
"baseAddress":"FFFF0000"
},
[...]
This section generates the memory map from a .csv file. The .cvs file looks like:
;-------------------------------------------------------------------------------
; debug registers
;-------------------------------------------------------------------------------
MEM_DEBUG_1,2,general debug register
MEM_DEBUG_2,2,general debug register
;-------------------------------------------------------------------------------
; table to sort the draw order of sprites
;-------------------------------------------------------------------------------
MEM_SPRITE_SORT_TABLE_SORTED,2,0000=sorted
MEM_SPRITE_SORT_TABLE_ID0,2,sort table ID 0
MEM_SPRITE_SORT_TABLE_VALUE0,2,sort table value 0
[...]
Thing that start with ';' are comments - if I thought ahead I would have used the more standard '//'. I'm sure I'll get around to that eventually. Each row then has a constant name, size (number of bytes), and comment. It generates code that looks like:
;-------------------------------------------------------------------------------
; debug registers
;-------------------------------------------------------------------------------
MEM_DEBUG_1=$FFFF0000 ; general debug register
MEM_DEBUG_2=$FFFF0002 ; general debug register
;-------------------------------------------------------------------------------
; table to sort the draw order of sprites
;-------------------------------------------------------------------------------
MEM_SPRITE_SORT_TABLE_SORTED=$FFFF0004 ; 0000=sorted
MEM_SPRITE_SORT_TABLE_ID0=$FFFF0006 ; sort table ID 0
The end the size values are used to determine the start address of each thing in the memory map.
The next section is similar, it is used to build constants:
[...]
"constants":{
"fileMap":{
"design/constants/Characters.csv":"src/const_Characters.X68",
[...]
"design/constants/RetailClerk90.csv":"src/const_RetailClerk90.X68"
},
"includeFilePath":"src/inc_Constants.X68"
[...]
It's very simple, just convert key-value pairs in a csv to the code equivalent.
The next section has a little more heavy lifting:
[...]
"collision":{
"collisionMap":{
"design/collision/BasementCafe.png":"src/collision-maps/BasementCafeCollision.X68",
[...]
"design/collision/WWTV.png":"src/collision-maps/WWTVCollision.X68"
},
"includeFilePath":"src/inc_CollisionMaps.X68"
[...]
My general approach for collision detection hasn't changed much since I wrote this thing on collision detection. Generating the collision data is new though.
For lack of a better idea I'm generating the collision data based on images. Pictured here is the collision map for the first scene in Retail Clerk '90. It's just a little version of the scene minus the scenery. The work to convert the image to collision data is here.
I found this was an easy way to generate the collision data. A smarter version of me would have it in the scene definition. The objects being drawn are defined there and it wouldn't be too difficult to flag objects as being solid and figuring out the collision data based on that. Yeah, that's a good idea. I should add that to my mental backlog.
Next up are the palettes:
[...]
"palettes":{
"paletteMap":[
{
"name":"PaletteBlack",
"sourceFilePath":"design/img/swatches/black.png",
"destinationFilePath":"src/palettes/Black.X68",
"exclude":"false"
},
[...]
{
"name":"PaletteWWHall",
"sourceFilePath":"design/img/swatches/wwhall.png",
"destinationFilePath":"src/palettes/WWHall.X68"
}
],
"includeFilePath":"src/inc_Palettes.X68"
[...]
The palette and tile generation mostly works like what I described here.
The general idea is I take an image and map the first 16 colors in it to their nearest Sega Genesis equivalent. Assuming I ever support another console this is code I'll need to extend a little.
The tiles are next:
[...]
"tiles":{
"tilesets":[
{
"name":"TransparentTile",
"palette":"PalettePeople",
"sourceFilePath":"design/img/scene-tiles/transparent.png",
"destinationFilePath":"src/tiles/scene-tiles/transparent-tile.X68",
"allowDuplicateTiles":"false"
},
[...]
{
"name":"MenuMap",
"palette":"PaletteMenu00",
"sourceFilePath":"design/img/scene-tiles/map.png",
"destinationFilePath":"src/tiles/scene-tiles/menu-map.X68",
"patternFilePath":"src/patterns/menu-map.X68"
}
],
"tileIncludeFilePath":"src/inc_Tiles.X68",
"patternIncludeFilePath":"src/inc_PatternsGenerated.X68"
[...]
This is similar to the article I linked to a few lines up. A tileset is an image sized to some multiple of 8x8. Each pixel in the tileset is mapped to the nearest color in the assigned palette. A pattern may or may not be created for each tileset. It really depends whether I'll be later drawing the tileset in a generic pattern (like 32x32 for example) or if that tileset has a unique shape (like some store-specific scenery).
Building sprites is very similar to building tiles:
[...]
"sprites":{
"sprites":[
{
"name":"Eryn",
"sourceFilePath":"design/img/sprite-tiles/pc-eryn.png",
"destinationFilePath":"src/tiles/sprite-tiles/pc-eryn.X68"
},
[...]
{
"name":"Victor",
"sourceFilePath":"design/img/sprite-tiles/npc-victor.png",
"destinationFilePath":"src/tiles/sprite-tiles/npc-victor.X68"
}
],
"palette":"PalettePeople",
"includeFilePath":"src/inc_SpriteTiles.X68",
"characterDefinitionFilePath":"src/data_CharacterDefinitions.X68",
"constantDefinitionPath":"src/const_CharacterIDs.X68",
"nameLookupTableFilePath":"src/text/table_CharacterNames.X68",
"nameFilePath":"src/text/en-us/CharacterNames.X68",
"baseId":"2000"
[...]
There are a couple differences. Like one palette is used to create all sprites. This also builds constants for each character ID and a text file with their names.
The most complicated part is building the scenes. It looks simple at a glance:
[...]
"scenes":{
"scenePaths":[
"/design/scene-json/legal.json",
[...]
"/design/scene-json/basementoffice.json"
],
"includeFilePath":"src/inc_Scenes.X68"
[...]
That's because all the real stuff is buried in the scene definitions. Let's dig into one:
[...]
{
"name":"SceneDenimCountry",
"id":"SCENE_ID_DENIMCOUNTRY",
"destinationFilePath":"src/scenes/DenimCountry.X68",
[...]
So far it's nice and simple.
Now we get to importing tiles. Each tileset name needs to be included in the prior tilesets section. These are loaded into memory so we can later build the scenery entries. This might make sense in a little bit.
[...]
"tilesetNames":[
"TransparentTile",
[...]
"DenimCountryTableLow"
],
[...]
The next part of the scene definition is the palette names. I suppose these don't need to exist in the palettes section but the ROM won't compile later on if they don't.
[...]
"paletteNames":[
"PaletteDenimCountry00",
"PaletteDenimCountry01",
"PaletteDenimCountry02",
"PalettePeople"
],
[...]
Scenery is where a few things come together. Let's start with the definition:
[...]
"scenery":[
{
"patternName":"PatternWoodFloorV",
"comment":"background",
"tilesetIndex":1,
"tilesetOffset":0,
"highPriority":false,
"paletteNumber":0,
"repeat":13,
"layer":"VDP_VRAM_WRITE_B",
"row":"00000000",
"column":"00000000"
},
[...]
{
"patternName":"PatternDenimCountryTableLow",
"tilesetIndex":6,
"tilesetOffset":0,
"highPriority":false,
"paletteNumber":2,
"repeat":0,
"layer":"VDP_VRAM_WRITE_A",
"row":"07000000",
"column":"00080000"
}
],
[...]
Again there's some Genesis-specific stuff in here I need to clean-up later. Layer, row, and column all need to be re-worked to something generic before trying to support a 2nd console.
This json ultimately leads to code that looks like:
[...]
dc.l PatternDenimCountryTableLow
; %pccvhnnnnnnnnnnn
dc.w %0100000000110100 ; vdp pattern
dc.w $0000 ; repeat=0
dc.l VDP_VRAM_WRITE_A+$00080000+$07000000 ; initial drawing location
[...]
The next section in the scene definition is for the location of virtual objects.
[...]
"objects":[
{
"id":"OBJ_SCENE_DC_MERCH",
"x":160,
"y":232,
"width":71,
"height":40
},
[...]
The mechanics of working with objects hasn't changed much since I wrote this.
Next up is a pointer to the collision data:
[...]
"collisionDataName":"DenimCountryCollisionStart",
[...]
Then there's a section to define the exits in the order of south, north, west, east:
[...]
"exitIds":[
"$FFFF",
"SCENE_ID_EWHALL",
"SCENE_ID_SOUTH_CENTER",
"$FFFF"
],
[...]
The next part defines where NPCs would be located in the scene:
[...]
"npcLocations":[
{
"x":192,
"y":144,
"direction":"DIRECTION_DOWN",
"movementFrequency":65535,
"movementPatternName":"NullMovement"
},
[...]
}
[...]
The last part of the scene definition is the pointer to the background music:
[...]
"bgmName":"BGM_Mall"
}
Alright, let's move back to the main build file. This next part is very console-specific and will eventually be reworked:
[...]
"header":{
"filePath":"src/init_Header.X68",
"copyright":"\u0027(C)HUJO \u0027",
"cartName":"\u0027Retail Clerk 90 \u0027",
"romStart":"$00000000",
"romEnd":"RomEnd",
"ramStartEnd":"$FFFF0000,$FFFFFFFF",
"sramType":"\u0027RA\u0027,$F8,$20",
"sramStart":"SRAM_START",
"sramEnd":"SRAM_END",
"comment":"\u0027https://HuguesJohnson.com/ \u0027"
},
[...]
Almost at the end now... here are the commands to build the ROM.
[...]
"assembly":[
{
"assemblerPath":"src/",
"arguments":"vasmm68k_mot -o ../build/RetailClerk90.bin -Fbin -spaces -D_DEBUG_\u003d0 -D_ATGAMES_HACKS_\u003d0 RetailClerk90.X68"
},
[...]
],
[...]
The last step is packaging the ROM and any associated text files into an archive.
[...]
"packageParameters":[
{
"includeFilePaths":[
"/build/RetailClerk90.bin",
"CREDITS",
"LICENSE",
"README.md"
],
"packagePath":"/build/RetailClerk90.zip"
},
[...]
}
]
}
Congratulations on making it this far. I'm not sure what will be added next right now.
Update #1: Text Generation
2021 is flying by, I'm not sure if my goal of finishing another demo this year is still feasible. During a recent heatwave I decided to tackle text generation.
I really created a mess for myself in terms of how strings where handled in Retail Clerk '89. Here's an example:
align 2
DialogTextDaniScene00Day00Flag0:
dc.w OBJ_NPC_DANI
; "1234567890123456789012"
dc.b "It's 10 minutes past",LF
dc.b "closing time already.^",FF
dc.b "Get that guy out of",LF
dc.b "here so we can leave.",ETX
align 2
DialogTextDaniScene00Day00Flag1:
dc.w OBJ_NPC_DANI
; "1234567890123456789012"
dc.b "Maybe you should",LF
dc.b "actually talk to him.^",FF
dc.b "You know, like,",LF
dc.b "do your job.",ETX
This is dialog from the first scene in the game. There's a lot of formatting baked into these strings. Let's look at each part in a little detail:
dc.w OBJ_NPC_DANI
That is something I added for dialogs with titles. I now realize I never wrote an article about how I managed that. In Retail Clerk '89 there are two styles of dialogs - plain and titled. The titled ones are used when speaking with a character or interacting with an object. That first word is used to lookup the string to display in the title rather than repeat it dozens of times.
align 2
These exist to deal with byte layout problems (for lack of a better description) that occur from the previous part.
; "1234567890123456789012"
This is just a comment I added to make sure I didn't exceed the maximum width of the dialog since this is all being written by hand.
dc.b "Maybe you should",LF
dc.b "actually talk to him.^",FF
Dialogs have two lines. The LF character means, as you'd expect "line feed" which tells the text drawing routine to move to the next line. FF obviously then means "form feed" which tells the dialog processing routine to wait until the player hits a button to load the next block of text. The ^ character is manually added and needs to be in the last position to avoid looking goofy.
dc.b "You know, like,",LF
dc.b "do your job.",ETX
The difference here is the ETX which means "end of text". That tells the dialog processor to close the dialog after the player hits a button.
The real problem is all these things are done manually. It made me start to dread writing the text for Retail Clerk '90. What would be more convenient is being able to write text like this that is converted automatically.
{
"name":"CharacterInteractTextPlayer1",
"description":"Default character dialog for player 1 (daytime events)",
"lineLength":22,
"formLines":1,
"defaultTerminator":"ETX",
"defaultLineFeed":"LF",
"defaultFormFeed":"FF",
"lines":{
"DialogTextNPC00Day00":{
"dialogTitle":"OBJ_NPC_NAME",
"text":"Here's some really long rambling block of text that I'd like some code to automatically convert to the appropriate format.",
},
[...]
In this idea we have a block of related text with similar properties. This particular example is for NPC dialog. There are settings that apply to all strings in the set like line length, number of lines in the dialog, and how to terminate lines. The text then is just one line that the build tooling can convert. The code to do all this isn't very pretty but I'll improve it over time. The latest version is here.
For the previous example, it produces code like this:
align 2
DialogTextNPC00Day00:
dc.w OBJ_NPC_NAME
dc.b "Here's some really",LF
dc.b "long rambling block ^",FF
dc.b "of text that I'd like",LF
dc.b "some code to ^",FF
dc.b "automatically convert",LF
dc.b "to the appropriate ^",FF
dc.b "format.",ETX
It's still ugly to look at but there is no manual formatting involved now. The code also generates handy lookup tables like:.
;-------------------------------------------------------------------------------
; CharacterInteractTextPlayer1
; Default character dialog for player 1 (daytime events)
;-------------------------------------------------------------------------------
CharacterInteractTextPlayer1TableStart:
dc.l DialogTextNPC00Day00
dc.l DialogTextNPC00Day01
[...]
So here's where all the auto-generation stuff is at now. Fixing the last couple lookup tables should be easy. Then again if it was I'd be done already.
The action script will be the most complicated thing. Well, I wrote an action script editor for a game I don't have the source code too so this should be easier than that at least.
Music generation... yeah, that's going to be bad times because the XM->Genesis conversion tool I'm using is pretty hard-wired to Windows.
Update #2: Action Table & Pattern Generation
First off, all this fancy auto-generation code is now in Github.
In the Retail Clerk engine (a name I still don't like) there is a table to map player actions to their consequences. That's a weird way to word it. Let me try it this way.. there's a lookup table that maps (player action + the scene the action occurred in + the day in the story) to whatever code should run for that combination.
In Retail Clerk '89 one of the actions was "exit scene". This was used for things like launching dialogs when the player tried to exit a scene (as the name implies) if there was some story event tied to it. Like a "hey wait, before you go here's some stuff" sort of thing. For Retail Clerk '90 I added an "enter scene" action which would have made a lot of dumb things I did in Retail Clerk '89 better. Most of the time these actions just run the default scene transition code. So building the action table needs to account for default actions.
What I went with was only including non-default actions in the script used to build the action table. This streamlines the script a bit into something like:
"actionTable":{
"dayCount":1,
"sceneCount":4,
"entries":[
{
"day":0,
"scene":"SCENE_ID_ROOM00",
"action":"ExitScene",
"label":"Room00Exit"
},
[...]
{
"day":0,
"scene":"SCENE_ID_ROOM03",
"action":"Respond",
"label":"Room03Respond"
}
],
"actions":[
"Interact",
"Respond",
"ExitScene",
"EnterScene"
],
"defaultLabels":[
"ActionScriptDefaultInteract",
"ActionScriptNullEvent",
"DefaultExitScene",
"DefaultEnterScene"
],
"filePath":"src/table_Actions.X68"
},
So here we have:
Running that through the code generation tool produces assembly code that looks like:
;---------------------------------------------------------------------------
; action table constants
;---------------------------------------------------------------------------
SCENE_COUNT=$0004 ; total number of scenes
ACTION_COUNT=$0004 ; total number of actions
DAY_COUNT=$0001 ; total number of days
ACTION_COUNT_X4=(ACTION_COUNT*4)
SCENE_COUNT_X_ACTION_COUNT_X4=(SCENE_COUNT*ACTION_COUNT_X4)
DAY_COUNT_X4=(DAY_COUNT*4)
ActionTableStart:
;---------------------------------------------------------------------------
; day 0
;---------------------------------------------------------------------------
; scene 0
dc.l ActionScriptDefaultInteract ; Interact
dc.l ActionScriptNullEvent ; Respond
dc.l Room00Exit ; ExitScene
dc.l DefaultEnterScene ; EnterScene
[...]
; scene 3
dc.l Room03Interact ; Interact
dc.l ActionScriptNullEvent ; Respond
dc.l DefaultExitScene ; ExitScene
dc.l DefaultEnterScene ; EnterScene
ActionTableEnd:
Now for the thing that would have saved me many hours when building Retail Clerk '89. I feel very not smart for not building this before. I wrote code that could take an image, break it into a set of unique 8x8 tiles, and write a pattern to draw the image. That was a time saver of course. A better time saver would have separated the unique tileset from the image.
Like, here's a set of unique tiles. I use these to create dialog frames and probably other stuff:
Now here's the image I want to create based on these tiles:
The build instruction is pretty simple:
"patterns":{
"patterns":[
{
"name":"DialogTitledFull",
"tilesetName":"DialogFrame",
"paletteName":"PalettePeople",
"sourceFilePath":"design/img/font-dialog-tiles/dialog-titled-full.png",
"destinationFilePath":"src/patterns/dialog-titled-full.X68"
},
[...]
],
"patternIncludeFilePath":"src/inc_PatternsGenerated.X68"
},
The code to produce the resulting pattern is somewhere in here. The assembly code it produces goes like:
PatternDialogTitledFull:
dc.w $5 ; 6 rows
dc.w $17 ; 24 columns
dc.w $0
dc.w $1
dc.w $5
[...]
dc.w $8
So that was fun and will save me a bunch of time later. I might even post a more detailed example of how to use this later.
I'm about out of things I want to build code generation tools for. There are still three things on my list, let's break them down...
The Action Script is a big thing I'm putting off. This is the game script, with all the if-then type of logic. You know the table we just saw, this is the stuff that table points to. This would likely entail creating an intermediate language (or using an existing one) that could be converted to console-specific code. I'm likely to punt on this until I get serious about building for a second console.
Menu Locations is just a lookup table. I'll get to that when I decide on a final menu layout.
Music is still a big TBD for me. I either need to port the toolset to not-Windows or try something different or just keep doing this manually.
Update #3: Re-skinning
It's been almost two years since I posed an update to this project. The explanation is simple, I got bored with it and instead created a demo called Speedrun Tower. I have another idea I'm pretty serious about starting so I think it's best to focus on completing this one first. For Speedrun Tower I used some licensed tilesets that look way better than my original art. Over the past couple of months I've been re-skinning Retail Clerk '90 to use the same tileset for all the scenery. I decided to keep the sprites as-is since I was happy with them. I think it's a nice difference - [Map with my bad art skills] vs [Map using a licensed tileset].
I am going to try to crank through the story next and then move on to something else.