Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - Zerker

Pages: [1] 2 3
Map Gab / Re: NES Classic Edition - full map set
« on: January 01, 2017, 09:58:47 am »
Note that the Excitebike maps are missing half the courses. Excitebike has a rather weird way of laying out the courses: each course has both a preliminary/challenge race and the main race variant, which have different layouts. The manual explains this a bit better:

FYI: All NES classic edition manuals can be found here:

Maps Of The Month / Re: 2013/04: Xargon (PC) - Zerker
« on: April 05, 2013, 03:16:11 pm »
If you really want to finish your collection once and for all, you might wish to map the Demo level, the one where he fights black ants and enters three samples of the trilogy. For the time of this award congratulations Zerker, you've truly earned it.

I actually did map those as well, but they weren't posted to VGMaps since they aren't really very map-like. You can see them on my web site:

You can also get the scripts there if you wanted them.

Mapping Tips/Guides / Re: how big can a map be?
« on: April 03, 2013, 04:25:50 pm »
I would say just go for it :). My Ultima 7 maps were 24576 x 24576. Using the "save file as" should work fine for anyone even if the browser can't display the full image.

Maps Of The Month / Re: 2013/04: Xargon (PC) - Zerker
« on: April 03, 2013, 04:23:53 pm »
Thanks for the map of the Month, Jon!

As for the overworld maps, I'm quite happy if you just put DarkWolf's maps up instead. They were quite well done, and I don't need to have the "complete set" :).

Map Gab / Re: Rise of the Triad Maps?
« on: December 22, 2012, 07:31:33 pm »
Space is cheap. JPEG compression artefacts can ruin any sprite based map, so JPEG is usually avoided unless absolutely necessary.

See for more info.

Mapping Tips/Guides / Re: PC Game Hacking and Mapping Tutorial: Xargon
« on: December 10, 2012, 03:41:55 pm »
Hey guys. For the next couple days, I'm just going to be adding better comments and interface documentation to the code, as well as writing usage instructions. I won't be posting a daily log of this process, but I will post when everything is complete and on my web site.

If anyone has any questions, they are of course quite welcome.

EDIT: That actually didn't take long. It's released on my web site now.

Mapping Tips/Guides / Re: PC Game Hacking and Mapping Tutorial: Xargon
« on: December 09, 2012, 08:59:31 am »
Hello again. Today we finish Episode 3 of Xargon, and thus the entire game. The first thing I need to do is play the last few levels with an eye to what is missing in the maps, and take screenshots of each so I don't forget. Since I can't save between levels, I'm going to just finish the game in one go then go back and update the maps.

And I finished the game. Now, Stage 14 is the castle stage, and it actually only has one missing sprite: another toggle platform. Let me add it now. Since it has a shadow when it appears (even on the right side), I'm going to create it as a 48 x 32 image and also include the shadow:
Code: [Select]
4: graphics.semitransparent(
   graphics.compositeimage((48, 32), [(0, 0, 11, 1),
   (16, 0, 11, 1), (0, 16, 11, 2), (16, 16, 11, 2),
   (32, 0, 11, 19), (32, 16, 11, 19)]), 128),

However, there appears to be a hidden rapid fire over one of the flame spitters, which as far as I can tell, is not there. This looks like the same issue as the ice cream cone thing. I suspect the "mapping" based on the variant field has priority over the subtype when this behaves as an ordinary hidden item. Since this is the only alternate case I have come across, I will simply add it as a special entry in the sprite DB:
Code: [Select]
for i in range(2):
    self.addsprite(73, i, variablesprite({
        0 : graphics.records[30].images[19],
        1 : graphics.records[30].images[19],
        2 : graphics.records[30].images[19],
        3 : graphics.compositeimage((32, 32), [(0, 0, 59, 1),
           (16, 0, 59, 4), (8, 12, 59, 1)]),
        4 : graphics.semitransparent(
            graphics.records[37].images[i], 128),

Stage 15 only has one new type of bouncing ball, after which it is complete:

The boss & ending stage, Stage 32, has one new sprite for a slug spawner, so we'll clump a couple slugs together for this (also, we need to account for the fact that slugs aren't in Episode 2):

Code: [Select]
if epnum != 2:
    slugspawner = graphics.compositeimage((32, 14),
            [(2, 0, 62, 2), (-3, 0, 62, 0)])
    slugspawner = graphics.records[30].images[19]


5 : slugspawner,

Incidentally, Xargon was already identified from the Episode 1 ending. However, the ending picture doesn't show up at all!

Let's look into this further; Going through the entire set of sprites for this map, I can't see anything that looks like it would trigger the ending image. Everything is fairly well identified. The last column in the unidentified header region has the number 143, which is unique. I can only theorize that this must be some special function to draw the ending image. Therefore, we will just have to do it the hard way.

First thing, I'm going to take advantage of my composite sprite code to make up a fake sprite that does not appear in the game to contain the full image. Looking through the graphics output, it appears to be stored in record 57 as 100 16 x 16 pixel chunks (i.e. 10 x 10). Let's put this together:

Code: [Select]
# Fake sprite for the ending scene (which does not appear to have a sprite OR use Tiles):
tilelist = []
for x in range(10):
    for y in range(10):
        tilelist.append( (x*16, y*16, 57, x + 10*y) )
self.addsprite(1000, 0, sprite(graphics.compositeimage((160, 160), tilelist)))

Then we need to go into GIMP and find out the exact upper-left corner. With that determined, we can add this fake sprite to the end of the sprite list for Episode 3, Stage 32:

Code: [Select]
# Fake Sprite for Episode 3 Ending:
if == 'BOARD_32' and mapdata.epnum == 3:
    mapdata.sprites.append(objrecord( (1000, 48, 240, 0, 0, 160, 160,
        0, 0, 0, 0, 0, 0, 0, 0) ))

That worked:

And that's it. The complete game is mapped. I have posted the Episode 3 maps on my web site and submitted them to VGMaps. The only thing remaining (for me) is to do a bit more code cleanup and documentation before I release the tool "officially". is available if you want to see the complete tool "as-is".

Mapping Tips/Guides / Day 25
« on: December 08, 2012, 07:31:09 am »
Good Morning. Time for a bit more Episode 3. Stage 9 sees the return of those cloak guys from the last level of Episode 1, be it with a different number:

Stage 10 was fully mapped, but it also proved the value of the doorway identifiers when I was playing it:

Stage 11 has a few new sprites: some snake-like creature, a new pickup gem, a new variant of the bouncing balls, high-jump boots in a treasure box, and a new invisible platform. And a different palette. Nothing we haven't already done.

Stage 13 is the demo stage, and just needs a new palette to finish it off:

Stage 12 just needed us to select the correct palette and...

Augh! No! Wrong! Guess the palette isn't *quite* identical to the earlier stage. Let's use a screenshot from this stage directly:


Next up is Xargon's Castle, i.e. the last set of levels. I will do that tomorrow. However, I can at least fill in the correct sprites for the map image:

Some assembly required? Let's see if we can't get the alignment on some of these sprites fixed up. After a few attempts, and some careful examination of a screenshot, here's what I came up with:

Code: [Select]
# Xargon's castle:
if epnum == 3:
    self.addsprite(88, 7,  sprite(graphics.records[47].images[25], yoffs=6, xoffs=4))
    self.addsprite(88, 8,  sprite(graphics.records[47].images[26], yoffs=6, xoffs=10))
    self.addsprite(88, 9,  sprite(graphics.records[47].images[27]))
    self.addsprite(88, 10, sprite(graphics.records[47].images[28], xoffs=4))
    self.addsprite(88, 11, sprite(graphics.records[47].images[29], xoffs=10))
    self.addsprite(88, 12, sprite(graphics.records[47].images[30])) is available.

Mapping Tips/Guides / Day 24
« on: December 07, 2012, 05:38:51 pm »
Good evening. There's not a whole lot left, so this is pretty much turning into a daily progress report until I finish. Almost all of the interesting stuff is already done.

Today, I start with Stage 4, which has one new sprite in the ceiling. It appears to be a ceiling switch of some sort:

Also, I died far too many times in that damn lava pit.

And I'm out of order again. Stage 6 appears to have some timers for periodically triggered events, so I will draw them by adding a prefix to their label number and make them otherwise invisible:

Code: [Select]
# Timers:
for i in [30, 40, 50, 60]:
    self.addsprite(73, i, sprite(graphics.records[30].images[19],
        labelpref="Timer ", labeloffs = (-4, 4)) )

Stages 5, 7 and 8 are fully identified. I just needed to find the right palette, and voila: is available.

Mapping Tips/Guides / Day 23
« on: December 06, 2012, 05:08:31 pm »
Episode 2 is now posted on my web site, and submitted to VGMaps. I also submitted the correction to Episode 1.

Episode 3 time. The first task we need to do is fix THIS error when generating the Episode 3 maps:
Code: [Select]
Generating Map 'BOARD_01'
Traceback (most recent call last):
  File "", line 139, in <module>
    mapper = xargonmapper(xargonimages, tiledata, themap)
  File "", line 66, in __init__
    sprites = spritedb(graphics, mapdata.epnum)
  File "/data/Projects/Xargon/", line 247, in __init__
    self.addsprite(56, 0, sprite(graphics.records[46].images[2]))
IndexError: list index out of range

Looks to me like that slime creature is an Episode 2 exclusive. So let's change that:

Code: [Select]
if epnum == 2:
    # Goo Monster
    self.addsprite(56, 0, sprite(graphics.records[46].images[2]))

Then it looks like we just need to add keys 1 and 2 for the bouncing ball trap. I will use debug images for now until we ID them:

Code: [Select]
# Bouncing Balls:
for i in range(2):
    self.addsprite(46, i, variablesprite({
        0 : graphics.records[51].images[4],
        1 : graphics.debugimage(46, 'T1', 32, 16),
        2 : graphics.debugimage(46, 'T2', 32, 16),
        3 : graphics.records[51].images[7]},
        field='info', hidelabel=True))

After that, it looks like the first three stages are all identified, excluding their palettes. Let's add a few more entries to our palette list and finish those up:

And that's all for today. is available.

Mapping Tips/Guides / Day 22
« on: December 05, 2012, 04:47:54 pm »
Another day, more maps. Today, I play Stage 12 only to discover it's already fully mapped:

Same with stage 13:

And 14:

And 15 (once I selected the correct palette):

See what I mean about things becoming easier? Now, there's one new sprite in the reactor level (stage 32), and that's for the larger reactor:

However, there's also misaligned text again for the ending sequence, so I'm going to need to do the same trick as with the Episode 1 Story scene:

Code: [Select]
# String adjust for Episode 2 Ending:
if == 'BOARD_32' and mapdata.epnum == 2:
    blank = mapdata.stringlookup[-1]

    del mapdata.stringlookup[-1]
    mapdata.stringlookup.insert(8, blank)

And with that, Episode 2 is complete! I'll post it on my site tomorrow. is available.

Mapping Tips/Guides / Day 21
« on: December 04, 2012, 06:03:30 pm »
Hello folks. Time for more maps. Things are getting easier and easier as we go along. Soon everything will be done for us before we even start.

But not stage 7. It has ONE identified sprite, which is just the Eagle character again. And a new palette.

Stage 9 has a few new things. We've got another illusionary wall, more spikey trap guys, a blue key treasure box, and a variant of the bouncy ball trap. The latter I need to switch over to using varients so I can update this accordingly.

Code: [Select]
# Bouncing Balls:
for i in range(2):
    self.addsprite(46, i, variablesprite({
        0 : graphics.records[51].images[4],
        3 : graphics.records[51].images[7]},
        field='info', hidelabel=True))

Oh, and I may as well fill in the Green Key treasure box, since all four keys appear to be in order.

I'm playing stages out-of-order again. Stage 11 has an alternate palette and one new ID for the same spike spear we've already seen. I also realize that I screwed up the shrimp monster, which also appears in Episode 1. I've re-uploaded the fixed copy to my site and get it corrected here after I finish Episode 2 (to make sure we didn't miss anything else).

And Stage 10 just has a different palette, an epic disk in a treasure box and ANOTHER version of the eagle sprite. I think I'm just going to assume all his variants are the same and populate via a loop:

Code: [Select]
# Silvertongue
for i in range (25):
    self.addsprite(23, i, sprite(graphics.records[45].images[1])) is available.

Mapping Tips/Guides / Re: PC Game Hacking and Mapping Tutorial: Xargon
« on: December 03, 2012, 05:21:25 pm »
Good evening. From here on our things will likely be pretty straightforward. Stage 4 just has one new sprite to identify, which appears to be a simple bat enemy. Yes, another small hard-to-see enemy. See if you can find them in the image below:

Stage 5 has one new enemy (some sort of goopy creature) and a new palette:

Stage 6 has a new trap and a new treasure box type (containing a red key no less).

The next two stages I did out of order. Stage 8 just has one new treasure box (yellow key):

And Stage 7 I will get to tomorrow. is available.

Mapping Tips/Guides / Day 19
« on: December 02, 2012, 07:58:05 am »
Hello again. Today is the day we make the mapper script work with Episodes 2 and 3. The first thing I noticed when firing up the other two episodes is that each one has a different colour palette:

And I suspect the Graphics file will have slightly different contents. Before doing anything else, I'm going to run my Graphics script on the other GRAPHICS files with the episode 1 palette. I should be able to compare the results to see what's different.

And Beyond Compare tells me there's very little that's actually different. A few missing sprites (notably the story scenes) and a few extra. Nothing that should totally break things, but the spritedb will need to be populated based on the episode number. We can't have it populating sprites that don't exist.

Also, there's the nature of the alternate palettes per episode. So let's get the files upgraded to detect episode number from the file extension. To do this, we add this line to
Code: [Select]
self.epnum = int(tempext[-1])
and to

Code: [Select]
self.epnum = int(filename[-1])
if self.epnum == 2:
    self.activepal = 6
elif self.epnum == 3:
    self.activepal = 7
    self.activepal = 0
And for debug:
Code: [Select]'Episode{}Images'.format(xargonimages.epnum))'Episode{}OriginalImages'.format(xargonimages.epnum), masked=False)

Let's update to tie this together. I'll also clean up the Episode 1 palette choice, since more stages use the "DARK" palette than otherwise:
Code: [Select]
if self.epnum == 2:
    # Episode 2
elif self.epnum == 3:
    # Episode 3
    # Episode 1
    if in ['BOARD_01', 'BOARD_02', 'BOARD_04']:
    elif == 'DEMO3':
    elif == 'BOARD_05':
    elif in ['BOARD_08', 'BOARD_33']:

sprites = spritedb(graphics, mapdata.epnum)

And save to a subfolder:

Code: [Select]
def save(self):
    epfolder = 'Episode{}'.format(self.epnum)
    createpath(epfolder), + '.png'))

And go by my sprite comparison to see which sprites are not in Episode 2, which looks like 56, 57 and 62. 62 I expect is in Episode 3, though.

Code: [Select]
# Skull Slug!
if epnum != 2:
    self.addsprite(75, 0, variablesprite({
        -1 : graphics.records[62].images[2],
        0 : graphics.records[62].images[0],
        1 : graphics.records[62].images[5],
        2 : graphics.records[62].images[3]
        }, hidelabel=True ))


# Story Scenes:
if epnum == 1:
    for subtype in range(24):
        self.addsprite(85, subtype, sprite(graphics.records[56].images[subtype]))
        self.addsprite(86, subtype, sprite(graphics.records[57].images[subtype]))

Then we run it and find out what breaks...

On the whole, it works. There are a few monster sprites with unmatched direction indices. Let me just fill those in now, run it again to confirm, then run it on Episode 3. Episode 3 has a couple more indices to fill in, including new hidden platform types. I will add those as debug images until I can see them in-game.

Code: [Select]
# Variant of Compound and semi-transparent for hidden platform(s)
self.addsprite(11, 0, variablesprite({
    2: graphics.debugimage(11, 'T2', 32, 16),
    4: graphics.debugimage(11, 'T4', 32, 16),
    6: graphics.semitransparent(
       graphics.compositeimage((32, 16), [(0, 0, 25, 14),
       (16, 0, 25, 15)]), 128),
    7: graphics.semitransparent(
       graphics.compositeimage((32, 16), [(0, 0, 51, 10),
       (16, 0, 51, 11)]), 128) }

Same goes for the new spawner varient (sprite 73).

Well, that's all done. Now we just need to identify new sprites to get the Episode 2 maps up to code. But first, I'm going to make one fix to the Episode 1 maps based on what I've seen of Episodes 2 and 3: Trigger Number of -1. This doesn't ever appear to be linked to anything and doesn't have any specific use for a map. So I will exclude it and only draw positive numbers:
Code: [Select]
if > 0 and < 90 and not self.hidelabel:

With that, a new palette, and a couple new sprites, Episode 2, Stage 1 is complete:

Oh, btw, the world map was complete before we even started (yay for reuse), but DarkWolf has already submitted the map to the site.

Stage 2 was almost done for me, and only needed a single new treasure box type. That said, one of the doorway labels is obscured by a spider, so I'm going to add doorways and triggers to the "text" list so they are drawn on top.
Code: [Select]
self.text = [obj for obj in self.objs if obj.sprtype in [6, 7, 12, 61, 62] ]
self.sprites = [obj for obj in self.objs if obj.sprtype not in [6, 7, 12, 61, 62] ]

Since switches moved to the "text" list, we also need to adjust our post-processing routines:

Code: [Select]
# First loop: find all door info values
for objrec in mapdata.sprites:
    if objrec.sprtype == 9:

# Second loop: Erase switches that align with doors and move doubled up sprites.
for objrec in mapdata.text:
    if objrec.sprtype == 12:
        while (objrec.x + objrec.y*128*16) in switchlocations:
            objrec.y += 8
        switchlocations.append(objrec.x + objrec.y*128*16)
        if in doorinfos:
   = 0

And here's stage 2, illustrating the tricky asymmetric doorways :).

Stage 3 introduces two new enemies: evil purple bunnies and mini dinosaur things. Let's add them both to the sprite DB and continue. I'm also going to pick the jumping animation for the bunny because they are SMALL otherwise.

Code: [Select]
# Mini Dino
if epnum != 1:
    self.addsprite(58, 0, variablesprite({
        -1 : graphics.records[56].images[5],
        0 : graphics.records[56].images[4],
        1 : graphics.records[56].images[1],
        2 : graphics.records[56].images[0],
        } ))
# Evil Bunny
if epnum != 1:
    self.addsprite(70, 0, variablesprite({
        0 : graphics.records[63].images[4],
        2 : graphics.records[63].images[1],
        } ))

Stage 3 is ALMOST done now. I realize there's a minor glitch, that didn't manifest in Episode 1:

We need to make sure to use the tile mask data, and draw the map with the proper background colour (index 250 in the palette). This should be fairly simple. First, the initial map image should start from this colour, then we need to paste in tiles using their mask.

Code: [Select]
self.mappicture ="RGB", (128*16, 64*16), graphics.getcolour(250) )


tileimg = tiledata.gettile(graphics, tileval)
self.mappicture.paste(tileimg, (x*16, y*16), tileimg )

Better now:

I could keep going, but I think that's enough for today. is available.

Mapping Tips/Guides / Day 18 (Episode 1 complete)
« on: December 01, 2012, 12:45:56 pm »
Hello folks. It's cleanup day. Here are the outstanding tasks I have written down, in the order I'm going to tackle them:
  • The player start and one mountain look misaligned on the map and should be adjusted.
  • Re-add switch indicators, with filtering to remove other uses of this field.
  • Add correct monster facing.
  • Fix the string ordering on the STORY scene.

We'll see if we can get these all done today, or if it will spill over into tomorrow.

Task 1 is easy, we just add an offset of 4 to each sprite:
Code: [Select]
# Map Images that need alignment:
for (sprtype, subtype, recnum, imagenum) in [
        (5, 0, 47, 8), # Map Player
        (88, 4, 47, 22), (88, 5, 47, 23)]:
    self.addsprite(sprtype, subtype, sprite(
        graphics.records[recnum].images[imagenum], xoffs=4))

For task 2, I'm going to start by enabling the existing code, and adding the sprite ID to each string for easy debugging (and filter list population). I'm also going to use the smaller font for this.

Code: [Select]
if != 0:
    self.drawlabel(mappicture, (objrec.x -8, objrec.y -8),
        "{} ({}:{})".format(, objrec.sprtype, objrec.subtype))

With that done, I need a mechanism for filtering our numbers on some sprites. I'm going to add some optional parameters to the sprite __init__ methods. Then, I move the label code into the sprite code, and adjust accordingly. It also looks like some of the high numbers (>=90) are used for form changes, so I will hide those automatically. I'll also add the ability to specify the label offset, for entries (like the trigger pickups) where the switch is the primary purpose.

Code: [Select]
class sprite(object):
    def __init__(self, image, xoffs=0, yoffs=0, hidelabel=False,
            labelpref='', labeloffs=(-8, -8)):
        self.image = image
        self.xoffs = xoffs
        self.yoffs = yoffs
        self.hidelabel = hidelabel
        self.labelpref = labelpref
        self.labeloffs = labeloffs

    def draw(self, mappicture, objrec, mapdata):
        # When pasting masked images, need to specify the mask for the paste.
        # RGBA images can be used as their own masks.
        mappicture.paste(self.image, (objrec.x +self.xoffs,
            objrec.y +self.yoffs), self.image)

        if != 0 and < 90 and not self.hidelabel:
            text = "{}{} ({}:{})".format(self.labelpref,,
                objrec.sprtype, objrec.subtype)

            # Draw the text 5 times to create an outline
            # (4 x black then 1 x white)
            pen = ImageDraw.Draw(mappicture)
            for offset, colour in [( (-1,-1), (0,0,0) ),
                    ( (-1,1), (0,0,0) ),
                    ( (1,-1), (0,0,0) ),
                    ( (1,1), (0,0,0) ),
                    ( (0,0), (255,255,255) )]:
                pen.text( (objrec.x +self.xoffs +offset[0] +self.labeloffs[0],
                    objrec.y +self.yoffs +offset[1] +self.labeloffs[0]),
                    text, font=markupfont, fill=colour)

Then I just need to actually use hidelabel and labelpref members for some good. Hidelabel is just to remove the clutter of useless information, but labelpref is to ADD info. Specifically, to give the label numbers more context. I'm going to use this for the doorways first:

Code: [Select]
self.addsprite(61, 0, sprite(graphics.records[30].images[19])) # Out Door
self.addsprite(62, 0, sprite(graphics.records[30].images[19], labelpref='To ')) # In Door

And the main ones to hide:

Code: [Select]
for sprtype in [17, 63]:
    for subtype in range(-1, 11):
        self.addsprite(sprtype, subtype, sprite(graphics.records[30].images[19],

Now I'll just go ahead and hide anything else that looks like it needs it.

And done. However, there are two cases that need to be fixed, and I think they will both require pre-processing. Case 1 is that all locked doors appear to have switch triggers. We don't want to display these, because the use of locked doors is fairly obvious. In order to remove this ONE use of triggers, we will need to first find all locked doors, then erase the info value for any triggers that match.

The second update is very minor, but on stage 5, it appears that two triggers are on the same tile. The pre-processing will simply need to move one down (or up) about 8 pixels.

Code: [Select]
def preprocessmap(self, mapdata):
    switchlocations = []
    doorinfos = []

    # First loop: find all door info values and move doubled up sprites.
    for objrec in mapdata.sprites:
        if objrec.sprtype == 12:
            while (objrec.x + objrec.y*128*16) in switchlocations:
                objrec.y += 8
            switchlocations.append(objrec.x + objrec.y*128*16)

        if objrec.sprtype == 9:

    # Second loop: Erase switches that align with doors
    for objrec in mapdata.sprites:
        if objrec.sprtype == 12 and in doorinfos:
   = 0

Not too hard. Now I just take out the debug sprite ID, and the labels are good to go.

My convention is to add "To" for a Doorway transition, "TR" for a pickup trigger, and "SW" for a toggle switch. Let me know if anything is unclear or you have any suggestions. I thought of adding "W" for the toggle walls, but I think they should be okay as-is.

Monster facing is next. Let's start with Stage 3, which obviously has monsters facing both directions. If we filter by monster 55, we get:
Code: [Select]
55  880     400 2   0   32  48  0   0   0   0   0   0   0   0   0
55  560     304 2   0   32  48  0   0   0   0   0   0   0   0   0
55  1440    336 2   0   32  48  0   0   0   0   0   0   0   0   0
55  400     944 -2  0   32  48  0   0   0   0   0   0   0   0   0
55  1904    960 3   0   32  48  0   0   0   0   0   0   0   0   0
55  160     864 3   0   32  48  0   0   0   0   0   0   0   0   0
55  560     864 -1  0   32  48  0   0   0   0   0   0   0   0   0
55  1232    400 -4  0   32  48  0   0   0   0   0   0   0   0   0
55  1712    928 -1  0   32  48  0   0   0   0   0   0   0   0   0
55  304     336 1   0   32  48  0   0   0   0   0   0   0   0   0

So, I don't see a left-right, but I do see a positive-negative correlation. The actual number probably specifies the exact initial sprite, which we will likely have a very hard time guessing. I'm going to assume positive is left, and negative is right. I'm also going to use a different frame of animation for each ID just for the hell of it.

Code: [Select]
# Monsters:
# Brute
self.addsprite(55, 0, variablesprite({
    -4 : graphics.records[61].images[4],
    -3 : graphics.records[61].images[5],
    -2 : graphics.records[61].images[6],
    -1 : graphics.records[61].images[7],
    0 : graphics.records[61].images[8],
    1 : graphics.records[61].images[9],
    2 : graphics.records[61].images[10],
    3 : graphics.records[61].images[11],
    4 : graphics.records[61].images[12],
    } ))

But it's not correct. Compare the below screenshot to the STORY screen:

Well, STORY is easy, because the sprites don't move, so I'll pick exact matches for them. Then I'll go play through Stage 3 again and see if I can figure out the initial facing of each monster.

Unfortunately, that's rather difficult to do. Here's the best I was able to come up with:
Code: [Select]
self.addsprite(55, 0, variablesprite({
    -4 : graphics.records[61].images[12],
    -3 : graphics.records[61].images[11],
    -2 : graphics.records[61].images[10],
    -1 : graphics.records[61].images[9],
    0 : graphics.records[61].images[8],
    1 : graphics.records[61].images[0],
    2 : graphics.records[61].images[1],
    3 : graphics.records[61].images[2],
    4 : graphics.records[61].images[3]
    } ))

Let's go ahead and do some other monsters. I'll leave the centipede as-is, because the only known instances all face in the same direction. Most other monsters appear to just use 0 and 2.

That's done. Still not 100% convinced, but it adds variety, and it's better than we had before.

Now strings. We're going to need more information for this, so let's add another output to the debug mode for

Code: [Select]
# Standalone debug string list:
with open( + '_strings.csv', 'wb') as csvfile:
    writer = csv.writer(csvfile)
    for stringnum, lookupval in enumerate(self.stringlookup):
        writer.writerow([stringnum, lookupval, self.strings[stringnum]])

With that listing in place, I will try to identify all the discrepancies by comparing screenshots against my map output. For each discrepancy, to the right of the string that "actually appears", I will put the string that "should be there", as well as the index in the string array for that string. Without a better idea, I think I'm just going to have to correct the alignment discrepancy in the lookup array.

With that in place, I can put together a simple set of corrections to fix the alignment. Most of them are shifted by 4, so we only need to take into account the entries OTHER than the ones that shift by 4. Generally, this just means moving a few of the page number entries to different places in the list.
Code: [Select]
# String adjust for STORY map:
if == 'STORY':
    page3to5 = mapdata.stringlookup[117:120]
    page6 = mapdata.stringlookup[82]
    page7 = mapdata.stringlookup[81]
    page8 = mapdata.stringlookup[84]
    page9 = mapdata.stringlookup[83]
    page10 = mapdata.stringlookup[116]

    del mapdata.stringlookup[116:120]
    del mapdata.stringlookup[81:85]

    mapdata.stringlookup[81:81] = page3to5 + [page6, page7, page8, page9, page10]

I confused myself a little bit figuring out exactly which value I was moving (original string ID vs string position in array). All is good now, however. This means that Episode 1 maps are complete! is available. Episode 1 maps are also on my website, and submitted to VGMaps. Tomorrow I will start Episode 2.

Pages: [1] 2 3