w7n's Advanced SMB Engine: Level data format and utility (Plan)

Started by w7n, March 19, 2017, 02:09:46 AM

Previous topic - Next topic


Currently the engine is only meant for this hack, but I might release a patch for the original SMB that implements the engine.

The level data format is still in the planning stage, but some of it has already been implemented in code. It has alpha-stage functionality in the project currently.
The following are adapted from my own notes. Feel free to fill in the missing blanks.

The level editor is being programmed in Delphi. I'm not really familiar with the IDE though, so progress on this level editor is rather slow. If you're familiar with Delphi then help is welcome.

C1 System architecture

1.1  I/O format of the editor
Editor-specific level data files can be in any format, but the editor should be able to output either hex files or compilable asm source.
The editor should output these files:
> Level data files
> Background files
> Table file for background of upr & lwr areas in H+V levels
Outputing to a compilable asm source is the recommended solution for level data, while outputing to a hex file is the recommended solution for backgrounds.
A line of an object in the asm source code looks like:
  HOR 0,3,8,14,QuestionFlower
The HOR is a pre-defined macro.

1.2  Hierarchy

First note that a 'metatile' is a 16x16px tile. The SMB system process almost everything about level loading in metatiles.

-- Area data --

               [decides w/table]         
(World number) ----------------- (Map bank) (Maximum size 8191 bytes)

Map bank file: Map**.65s (** denotes a number)

The structure of a map bank:

64 area data pointers (areas are labeled $00 to $3F) ------
64 enemy data pointers -------------------------           |
                                                 |          |
|                                               |
V                                               |
(Data of area $00)                              |
Header part                                     |
Objects part, with a specific termination byte  |
Enemy objects part and 'area entry' data part, with a specific termination byte

-- Background data --

         [Find ID of lower and upper area w/table]
                     /                  |            [BG is filled with a single metatile]
                    / Y                 |             /
                   /                    |            /  Y
(BG setting) -- [H+V level?]         (BG ID) -- [ID < 4?]
                   \  N                 |            \  N
                    \                   |             \
                 [Setting = ID] --------             [Look for the (ID-4)th background stored in the BG banks]

Background bank file: BGBank*.bin (* denotes a number, for example BGBank0.bin)

The structure of a BG bank is simple: One background is composed of 1 page, stores 256 metatile bytes. Thus a BG bank stores 32 backgrounds.

    0 1 2 3 4 ... F  (y)   +--------------> y
0   * * * * *     *        |
1   * * * * *     *        |
...                        |
F   * * * * *     *        V  x

The only thing needs attention is that xpos and ypos are not the same as aligned in a hex editor, instead they are swapped.

-- Metatile data --

There are a total of 256 metatiles available for use. Since the CHR banks are not fixed though, the metatile graphics are determined by several bytes in the level header: One is the area style byte (determines CHRBank4 and CHRBank5), another is the area BG byte.
To actually calculate the graphics isn't easy work perhaps, so the metatile GFX setting can be an editor-exclusive setting that is never exported.
Earlier, I've made a .BMP exporter to export metatile data.

1.3  How the map is preprocessed in the ROM hack

1.3.1  Basics

Firstly: a horizontal level takes up at most 16 pages, while a H+V level takes up at most 8 pages horizontally and 2 pages vertically.
For the map data, the lower part of an H+V level is stored separately from the upper part, and its ID is the AreaID of the upper part plus 1. And the lower part has no header.
For example, if you're loading area $0B which is an H+V level:


Area0C: ;The corresponding lower part of area $0B

This doesn't apply to enemies: Enemies are all stored in Area0BEnemy, there is a flag to indicate whether the enemy object is in the upper or the lower area.

The y-length of a horizontal level is 13 metatiles, while for H+V levels it's 28 metatiles. Usually it's 28 = 16(upper) + 12(lower), the exception is the terrain.

1.3.2  The metatile map

We can say that there are 3 'layers' of the metatile map and they are written in the following order:

#1: Background.
Backgrounds are saved separately in the files, as explained above. The header includes a BG setting, and BG settings can be changed midway during the level.

For horizontal levels -- BG setting = BG ID. Only the top 13 lines of metatiles are actually used.
For vertical levels -- Use the H+V BG table to find out the BG ID for the upper and lower areas. The upper area takes all 16 lines, while the lower area only takes the top 12 lines.

The BG processing routine processes BG by column, and when it finishes column 15 it goes back to column 0.
Changing the BG setting midway does not reset the column of the BG renderer, instead it renders the changed BG from the next column.

Note that after BG rendition, metatile $00 will be treated as 'transparent' and doesn't overwrite the BG. Hidden blocks don't overwrite the BG ingame, but in the map editor showing them is better.

#2: Terrain.
Terrains are controlled by terrain bit objects, and the header also has two bytes for them.

For horizontal levels -- 13 lines
For vertical levels -- Upper = 15 lines, Lower = 13 lines

Terrain graphics have a Metroid-like approach: Although the existence of terrain blocks is only controlled by a bit on/bit off, the actual terrain block is determined by its surrounding blocks.
The terrain block metatiles are defined as such in the source code:

;4 6

TerNothing = $D0 ;This means a solitary terrain metatile
Ter2 = $D1
Ter4 = $D2
Ter6 = $D3
Ter8 = $D4
Ter24 = $D5
Ter24_1 = $D6
Ter26 = $D7
Ter26_3 = $D8
Ter28 = $D9
Ter46 = $DA
Ter48 = $DB
Ter48_7 = $DC
Ter68 = $DD
Ter68_9 = $DE
Ter246 = $DF
Ter246_1 = $E0
Ter246_3 = $E1
Ter246_13 = $E2
Ter248 = $E3
Ter248_1 = $E4
Ter248_7 = $E5
Ter248_17 = $E6
Ter268 = $E7
Ter268_3 = $E8
Ter268_7 = $E9
Ter268_37 = $EA
Ter468 = $EB
Ter468_7 = $EC
Ter468_9 = $ED
Ter468_79 = $EE
Ter2468 = $EF
Ter2468_1 = $F0
Ter2468_3 = $F1
Ter2468_7 = $F2
Ter2468_9 = $F3
Ter2468_13 = $F4
Ter2468_17 = $F5
Ter2468_19 = $F6
Ter2468_37 = $F7
Ter2468_39 = $F8
Ter2468_79 = $F9
Ter2468_137 = $FA
Ter2468_139 = $FB
Ter2468_179 = $FC
Ter2468_379 = $FD
Ter2468_1379 = $FE

#3: Area objects.
Area objects should always be sorted by their x positions, and the latter objects would overwrite metatiles created by former objects.
Objects on the same column can be manually sorted, but (if on the same column) objects in the lower area always come later than objects in the upper area and will overwrite their metatiles.

For horizontal levels -- 13 lines for normal objects, last 3 lines for settings
For vertical levels -- Upper = 16 lines for normal objects, Lower = 12 lines for normal objects and last 4 lines for settings

Note that if an object in the upper area is long enough, it will find itself occupying space in both upper and lower areas.

1.3.3  Enemies

Enemy data are separated from area objects, and they are much easier to document.
Enemy data also stores 'area entry'(where a pipe should lead to) data and other settings.

There are several sets of enemies, but only one set can be used at a time. Exactly which set to use is set(overwritten) by an enemy object.

1.4  Area object structure

Once again:
For horizontal levels -- 13 lines for normal objects, last 3 lines for settings
For vertical levels -- Upper = 16 lines for normal objects, Lower = 12 lines for normal objects and last 4 lines for settings

The termination byte for area data is #$FE, while the page skip byte for area data is #$FD. Otherwise, the first byte of an area object is always in this structure:


X = X column, Y = Y column

Normal objects:


B always denotes the object length. The actual object length is B+1.

A = Type of object.
A = 00: Special object
A = 01: Vertical block (starting from x,y and goes downward)
A = 10: Horizontal object (starting from x,y and goes rightward)
A = 11: Objects of infinite vertical length (starting from x,y and goes downward infinitely, then copied horizontally)

If A != 00, then B denotes the object length (B denotes the horizontal length if A = 11) and C is the metatile ID.

If A = 00, then C is the routine ID. For example C=0 means a vertical pipe.



If X=15 this is the page skip object and there is no second byte. The object will be treated as if it carries a page flag by default.
Otherwise, the Ath routine will be called. Usually used to conditionally skip or load the next object, for example, if the player has Lens of Truth then skip / load the next object.


XY = #$FE means end of data. Otherwise:
If A<>0, this is a setting object with B as a coefficient. List:
A=1: X autoscrolling. If B=0 then revert to normal scrolling.
A=2: Y autoscrolling. B=coefficient: 0=cancel, 8=stopped, 1=upwards fastest, 15=downwards fastest
A=3: Y scroll position autoscrolls to B*16.
(Others undefined for now)

If A=0: Setting object without coefficient. Current list:
0: Hard mode
1: Smart enemies
2: Enemies won't be removed if out of screen boundaries
3: Certain enemies run in normal code
4: Certain enemies' trajectory are set with preloaded code chunk 1
5: ...code chunk 2
6: Certain enemies fire a bullet aimed at the player every 4 seconds
9: Stop enemy frenzy
10: Checkpoint - start at bottom of upper area
11: Checkpoint - start at bottom of lower area
12: Scroll stop (x only, same as below)
13: Scroll stop (end of level)
14: Scroll stop (Megaman cutscene-ish)
15: Scroll stop (until all enemies onscreen defeated)



A: Setting type.

IF A==00:
B: Palette type
C: BG type

IF A==01:
B and C: Terrain bits. If H+V level, then it stands for the terrain bits of the lower part.

IF A==10:
B: Lower 4 bits used, this means 'When player reaches a position that's the same as the object's x position and lower than B*16 px, trigger cutscene C'. If the high bit of B is 1 though, the Y judgement is ignored.

IF A==11: Setting object whose type depends on B.
B=0: Change BG (of the SMB's own mechanics, for example castle walls).
B=1: Preload chunk 1. C=routine number
B=2: Preload chunk 2.
B=3: Set the type of the next undefined powerup. The highest bit of C means upgraded status.
(Others undefined)

Y==12 of lower part of H+V level:

A: Terrain bits.




TT: Type. 00: Normal, 01: Appears on the left edge of screen, 10: Trigger, when the player touches its bounding box it disappears and triggers an event, for example 8 projectiles shoot towards the player. The third byte stands for trigger ID. 11: NPC with dialogue, this kind of enemy uses another format.
BBBB: Enemy type. Determines PRG and CHR bank used for the enemy. If B=0, then do not change PRG and CHR bank settings.
R: Random enemy.
E: Enemy ID. If R=1, E stands for random enemy setting.

IF T == 11 (NPC with dialogue):
E: Enemy ID
C: Cutscene ID

Y==14: Pipe entry data.
A: Entry area number
E: Entry type, 0=none 1=leftward 2=rightward 3=downward 4=upward 5=vine upward 6=vine downward 7= (undefined)
B: Entry page
x,y: X and Y coordinates. Actual X and Y coordinates are: xxxx xxx0 and yyyy 0000.
p: If p==1, then player appears at the lower half of the level
W: World number
Unlike the original SMB, this object doesn't work like 'when it's loaded then change pipe entry settings' but rather 'all entries right of this object use this setting'.

Y==15: If XXXX YYYY = #$FF then it's the end of data byte, else:
A=0: If B=0 then it's a blank object. Otherwise, B is the routine number. (Also may be used to conditionally skip objects) If the highest bit of B is 1, then it's not executed in real-time but rather in level data preloading.
A=1: Change BGM.

Level header:
> TTTT AAPP   T: Time setting (0=do not set), A: Area type, P: Initial Y position
> TTTT TTTT CSLT TTTT  C: Ceiling judgement (0: Ceiling hollow; 1: Ceiling has same attributes as the highest metatile of the same x position), S: Safe zone, L: Landform(terrain) GFX type, T: Terrain bits
> LSSS SSSS  L: L+R level, S: GFX settings type
> One byte for BGM
> One byte for starting BG
> VPPP PPPP  P: Palette, V: H+V level
> One byte for local map ID
> One byte which determines the level's position on the local map. YYYX XXXX
> TTBB BBBB   T: Time flowing speed setting, B: Player to background collision setting (used for moving background)
> Two bytes depending on the area type:
--If H+V level: The starting terrain bits of the lower area.
--If not H+V level: The two routine IDs for preloaded data.
> Finally a null-terminated string for the name of the level.


Have you considered using the disassembly to port to a new system?