4.5.9 -- Get More Monsters In Your Game By Storing In Two Banks

Bucket Mouse

Active member
You know my post "Fun With Bank Switching" from a few months ago? I gave it a heavier test and it....didn't actually work for me. I was going to replace that post with this post, but then Panzer said it DID work for HIM, so....I dunno what's going on there. I guess I'll leave it up. I do know it doesn't work for the specific thing I invented it for, storing monster data in more than one bank.

The NESMaker engine has space for 64 monsters. The dirty truth is that depending on what you tell those monsters to do and how many Action Steps you use for each, you will very probably run out of space for them if you use more than half.

Sprite data is stored in three main depositories: ObjectInfo.dat, ObjectLutTable.dat and ObjectPointers.dat. All three are stored in Bank 1C, with ObjectInfo being the largest. Bank 1C fills up fast and Bank 1D, right next to it, is completely empty. If we could move ObjectInfo there, all 64 monsters with every Action Step could theoretically be used.

I spent one unpleasant, nerve-racking week trying to solve this problem and got nowhere. Fortunately Kenneth Nietfeld, creator of the Rick Starfield demo from the recent Byte Off competition, came to my rescue. He said he had solved this issue already for his own game and he knew how to cleanly split Bank 1C into two banks. He also does not really post in the forums, so he gave me permission to share this tutorial here.

The first thing you must do is move doDrawSprites.asm, currently in Bank 1C, to the static bank (Bank 1F, the One Bank to rule them all). You do that by going to System/BankData, opening Bank1C.asm and finding this:

Code:
;; do draw sprites
doDrawSprites:
    .include SCR_HANDLE_DRAWING_SPRITES
    rts

Now open up Base 4.5/Game and find LoadAllSubroutines.asm. Bank 1F does not have a specific listing; instead you add things to the Static Bank through this script. So copy the doDrawSprites storage command from Bank 1C and paste it into LoadAllSubroutines. Then delete the original mention in Bank 1C or you'll get duplicate errors.

At the top of Bank 1C you'll notice the first two lines:

Code:
;;; ALL THE DATA FIRST
.include "ScreenData\ObjectInfo.dat"

Those lines should go in Bank 1D now. Do the same procedure for them.

Save Bank 1C, Bank 1D and LoadAllSubroutines under slightly different names, like "Bank1C-New" or "LoadAllSubroutines-Extra." When you're altering NESMaker code, it's always a good idea to keep the originals around. Also, NESMaker is going to automatically overwrite anything in the System folder, which will erase what we just did in Bank 1C anyway.

To use our newly rewritten banks, we open Script Settings and redirect Bank 1C, Bank 1D and LoadAllSubroutines to our versions. Also keep in mind: any changes you make to Script Settings can be retained in future games by making those changes into your own module. That's as easy as selecting a menu item.

The last step is to go to your Subroutines folder and open up doDrawSprites.asm. We're going to do an alteration there so it can access Bank 1D to read ObjectInfo. Scroll down to around line 216, just after

LDA (tempPointer_lo),y
STA temp16
LDA (tempPointer_hi),y
STA temp16+1

and just before

LDY #$00
LDA (temp16),y

and within that space, post:

SwitchBank #$1D

Then find the line "doneDrawingThisSprite." Right below it, post:

ReturnBank

Save doDrawSprites under a new name, link to it in Script settings, and you're done. You now have all the space you need to go nuts with monsters.
 

Subotai

Active member
Yeah, it works perfectly. (y)
With that, I'll be able to add a lot more animation and monster in my project.

Thanks Bucket Mouse.
 

vanderblade

Active member
Okay. I finally tested this out and it gives me dozens and dozens of errors at run-time related to AI behavior scripts and all the AI Routines at the bottom of LoadAllSubroutines. Did nobody else get these errors? Not sure how to proceed. Any suggestions?
 
Last edited:

Knietfeld

Member
Okay. I finally tested this out and it gives me dozens and dozens of errors at run-time related to AI behavior scripts and all the AI Routines at the bottom of LoadAllSubroutines. Did nobody else get these errors? Not sure how to proceed. Any suggestions?
Are you still working with this? If so, What kind of errors were you getting? I wonder if moving SCR_HANDLE_DRAWING_SPRITES overfilled your static bank. The AI routines and their lookup tables are the last thing in the static bank so if they didn't fit you'd get "PC out of range" . To fix that you'd have to find pieces of code in the static bank that can safely be moved to another bank.
 

vanderblade

Active member
Are you still working with this? If so, What kind of errors were you getting? I wonder if moving SCR_HANDLE_DRAWING_SPRITES overfilled your static bank. The AI routines and their lookup tables are the last thing in the static bank so if they didn't fit you'd get "PC out of range" . To fix that you'd have to find pieces of code in the static bank that can safely be moved to another bank.
It's still something I'm trying to do. Thanks for this tip. When I get the time again, I'll see what can be done. Cheers.
 

vanderblade

Active member
Are you still working with this? If so, What kind of errors were you getting? I wonder if moving SCR_HANDLE_DRAWING_SPRITES overfilled your static bank. The AI routines and their lookup tables are the last thing in the static bank so if they didn't fit you'd get "PC out of range" . To fix that you'd have to find pieces of code in the static bank that can safely be moved to another bank.
After some more tests, that seems to be the issue. It's all "PC out of range" errors for my AI scripts. Any suggestions? @mouse spirit?
 

Bucket Mouse

Active member
Gotta clear room in Bank 1F. Find LoadAllSubroutines.asm and dummy out some nonessential scripts. What isn't essential depends on the game. I got rid of doLoadScreen2 and doDrawTilesDirect.

Or if you want to get a little more hands-on, you can move some routines to another bank, like Knietfeld said, but that will require some rewiring to redirect every other script that reads the script you moved.
 

Knietfeld

Member
Yep, I haven't touched the brawler base but it looks like it starts out with the static bank pretty full.

Gosh, it's a difficult thing to give tips on. I cleared space so long ago that I don't remember what I took out of the Static Bank. I don't think I got to take whole routines out, I had to look through and see if there were big enough chunks to justify switching banks and moving the code to a new place. It's a bit more advanced but if you can read the code you can look through GameEngineData\demo.txt starting at 0C000 (where the static bank starts) and see if there are any big chunks of code that can be moved. Once you find something figure out what routine it's in, then open it up and edit it (making a new version of the .asm file is the best practice, but sometimes it's hard to figure out how to insert your new version when it's not a routine that's in the Script Settings tab in NESMaker) The first I found was in Game\Initialization.asm. Everything from line 133 to 211 (even that .include line) can be moved to any bank and still function. Depending on how many user variables you've made it could be a lot of space. If you have trouble moving chunks like that we can explain it in more detail. It's the same basic concept as Bucket Mouse described at the top of this thread.

I do want to mention, though, that if it seems too complicated right now it's ok to hold off on it. Come back some time later after you understand the code a bit better and you may be surprised to find it is in the realm of possibility. I read something a few months back in a NESDEV forum that blew my mind and I just filed it away as magic that I would never understand. But recently I was dealing with some code that did a similar thing and after reading that post again the magic concepts seemed totally within my reach.
 

vanderblade

Active member
Thanks, Mouse and Knietfeld. This is all pretty daunting, but I can't fit a 2-player mode without doing it (or cutting my already small roster of enemies), so I'll keep tinkering. Cheers.

Edit: I managed to cull the drawbox and drawtext operations from the static bank, and it's now working again. I know for a fact I don't use normal text boxes, so that should be fine, but I'm not entirely sure what the drawbox stuff does (if it's just for drawing the textbox, I should be good, but I'm worried it's related to the play area parameters, too.) Testing so far works, though.
 
Last edited:

vanderblade

Active member
I have a weird issue I didn't initially notice after doing all this. Now when I have the HIDE SPRITE screenflag ticked, the game will freeze on that screen. @Bucket Mouse, any ideas? Does this happen for you? It shouldn't be related to commenting out the drawtext and drawbox operations, right?

Until I fix this issue, I guess I'll just use the workaround of an ALL BLACK player palette and avoid using HIDE SPRITE.
 

Bucket Mouse

Active member
No, I don't know why that would be happening if all you cut was the text stuff. It's unrelated to sprites.

What I do is add the cut scripts back one at a time to see which one might be causing it.
 

offparkway

Active member
@Bucket Mouse Thanks for this. I just implemented all the changes, and my game runs... but every object so far (player and enemies) all have corrupt gibberish graphics.

Mostly just repeated tiles (like 6 copies of the side of my player's head). And if I let the player idle, the tiles change every 4 or 5 seconds, even though there is no idle animation. Any ideas?
 
Last edited:

Bucket Mouse

Active member
It's likely pulling graphics data from the wrong places -- it has the wrong coordinates. Try the steps again...you must've put something in the wrong place.
 

offparkway

Active member
Ohhhh. I forgot I was using the platformer version of doDrawSprites. I edited the default one instead. Now it looks like we're in business! Thanks much.
 

vanderblade

Active member
ooops, spoke too soon. Just like @vanderblade, my screens that hide sprites crash. Getting around it by using an all black sprite palette.
I'm just about to use this on my latest run and gun game since I hit the wall after just 14 monsters! Well, if you count the game objects, it's more like 30. The fun part is trying to find where to hide your ALL BLACK player during busy interstitial screens.
 

vanderblade

Active member
Okay. I have a weird question. So I'm using a lot of unique/bespoke code to manually draw monters to help with slowdown. When I use this method to bankswitch to 1D, I get all kinds of errors if I don't take out a lot of my unique sprite draw code from the default script.

As an example, this is what I'm doing at the top of the script to check for monsters and manually draw them if possible. How do I go about incorporating this system into this method without massive errors? This works fine and I even have several more monsters in the full version of the script below before I hit the "limit" and get an error. Again, for the record, this is just the default drawSprites_Platformer script with a bunch of CMP checks upfront to manually draw monsters (and it has the 1D bankswitch stuff noted in this thread).

Is there a way to use Bank 1D to do these unique draw commands so I can avoid the error? Every time I try to change the place of the bank switch command, I get errors or the game freezes.

Code:
    LDA gameHandler
    AND #%01000000
    BEQ doDrawThisSprite
    JMP doneDrawingThisSprite
doDrawThisSprite:
   
;; This routine will work together with the luts created by the old object animation tool.
;; Due to that, it must be included in the same bank with luts.

    ;; We will use tempA to track the x value of a tile.
    ;; We will use tempB to track the y value of a tile.
    ;; X is the index of the object.
    ;; Y is the index of the object type.
   
    ;; ObjectSize,y is the size in tiles for this version of drawing.
    ;; 00xxxyyy
    ;; where x = the width in tiles (1-7)
    ;; where y = the height in tiles (1-7)
    ;; for both, zero is an impossible value.
   
    ;; Object_frame,x keeps track of actions and animations.
    ;; 00xxxyyy
    ;; where x = animation frame
    ;; where y = action frame

    ;LDY Object_type,x ;; for lut lookups.
   
    LDA Object_x_hi,x
    SEC
    SBC camX ;; is using scrolling
    STA tempA
    LDA Object_y_hi,x
    ;sec
    ;SBC camY
    STA tempB
   
    LDY Object_type,x
   
    LDA Object_type,x
    CMP #$01
    BEQ +isPlayerProjectile   ;;;;START OF NEW STUFF
        JMP +isNotNormalProjectile
   
    +isPlayerProjectile
        ;;; DRAW PLAYER PROJECTILE
        GetActionStep temp
        CMP #$01
        BEQ +drawBuzzSaw
            JMP +BuzzSawReverseCheck
           
        +drawBuzzSaw  
        DrawSprite tempA, tempB, #$49, #%00000000
        JMP doneDrawingThisSprite
   
        +BuzzSawReverseCheck
        CMP #$02
        BEQ +drawBuzzSawReverse
            JMP +PlasmaCheck
           
        +drawBuzzSawReverse
        DrawSprite tempA, tempB, #$49, #%00000000
        JMP doneDrawingThisSprite
       
        +PlasmaCheck
        CMP #$03
        BEQ +drawPlasma
        BCS +drawPlasma
            JMP +drawNormalProjectile
           
        +drawPlasma
        DrawSprite tempA, tempB, #$56, #%00000011
        JMP doneDrawingThisSprite
   
        +drawNormalProjectile
        DrawSprite tempA, tempB, #$56, #%00000000
        JMP doneDrawingThisSprite
       
    +isNotNormalProjectile
   
    ;;;and then it has the default stuff below this. In my actual script, I have WAY more
    unique monster draw code. But I get errors if I have this material here after this bankswitching
    method. Any idea how to resolve or where to appropriately move this stuff?
 
Last edited:

Atarath

Member
I got this to work in 4.1.5! It required removing a table, Object_total_sprites from ObjectInfo and placing back in bank 1C, and creating a different dat file. Great work!
 
Top Bottom