<html> <head> <style> html { background-color: #000207; color: white; font-family: sans-serif; } table, th, td { border: 1px solid white; border-collapse: collapse; } span.NoLineBreak { white-space: nowrap; } abbr{cursor: help;} </style> </head> <body> <center><h1>Memory-based Conditional map 16 blocks</h1></center> <h2>Introduction</h2> <p>This ASM resource allows you to have more than 128 bit flags in your entire game. This is useful for exploration-based hacks should you have blocks that shouldn't respawn when the player refreshes the level. An example is locked doors and keys, especially involving backtracking. Another use, which is minor, is SA-1 hacks, since blocks are processed during SA-1, they do not have access to WRAM (RAM address in banks <kbd>$7E</kbd> and <kbd>$7F</kbd>), thus the blocks themselves cannot use <kbd>$7FC060</kbd> directly. Instead have blocks to use banks <kbd>$40</kbd> or <kbd>$41</kbd>, then uberasm tool to transfer that to <kbd>$7FC060</kbd> making it possible for custom blocks to use CM16 blocks on a SA-1 hack.</p> <p>Note that this isn't really a <b>true</b> conditional map 16 expansion, this is because this ASM resource relies on Lunar Magic's conditional map16 blocks (<kbd>$7FC060</kbd> to <kbd>$7FC06F</kbd>), therefore, you can use up to 128 flags <u>per level</u> (using within a group of 128 bits in “<kbd>!Freeram_MemoryFlag</kbd>”). This ASM resource just creates a new RAM area defined as <kbd>!Freeram_MemoryFlag</kbd> and “divide” them into groups of 16 bytes (I call them “Group-128”, 16 bytes = 128 bits). Depending on what level you are in, during level load, all 16 bytes within a specified group (set by the table in <kbd>UberasmTool/Library/MBCM16WriteGroup128To7FC060.asm</kbd> under the sublabel <kbd>.OneHundredTwentyEightFlagGroupList</kbd>) will copy itself into <kbd>$7FC060</kbd>-<kbd>$7FC06F</kbd> to make the blocks spawn, not spawn anymore, or remain changed when the player leaves and return.</p> <p>Keep in mind, you are not forced to use a different group-128 for every level, you can reuse the same group-128 in multiple levels should the level uses less than 128 flags. Just be careful not to have a separate block in another level be inadvertently set the same LM flag number (which also means the same flag number for <kbd>!Freeram_MemoryFlag</kbd>) as the other level of the same group (example: Two levels, 105 and 106, both use group 0, one block is a key set to 0, the other level also have a key set to 0, picking up one key will affect the spawning of the other key, which you should avoid).</p> <p><kbd>!Freeram_MemoryFlag</kbd> is <kbd>NumberOfGroups*$10</kbd> bytes long. <kbd>NumberOfGroups</kbd> is how many group-128s you wanted, ranging from 1-16. Therefore, the absolute limit on how many bit flags you can have in the entire game is 2048. I don't think any hack would ever need this many flags, even if all the playable levels on an average hack uses this mechanic.</p> <p>This RAM acts as a universal global memory, and shouldn't be reset on level load and overworld (it must be “untouched” RAM initialized by the SRAM/BWRAM patch when the user starts the game).</p> <p>The blocks included in this ASM resources is a key (collectible and not carried like a key sprite), locked gates* and doors*. How this works using this resource is that unlike the hack <i>Mushroom Kingdom - Under Crimson Skies</i>, this ASM pack do not use duplicate map16 blocks each having their own flag to write, instead when the player triggers the block, the block checks what level, if its on layer 1 or 2, and its location within the level. If all 3 of these attributes are found on a specific index number, will use that index number to determine what flag number to set/reset a bit in <kbd>!Freeram_MemoryFlag</kbd>, otherwise it will do nothing as a failsafe.</p> <ul><li><p>(*I termed “gates” and “doors” as two separate things: “gates” refer to solid objects that acts as a barrier to prevent the player from accessing other parts of the level the player is currently in, while “doors” is an exit-enabled object that teleports the player to another level. This terminology is to prevent confusion since both in real-life terms, they control access to another point.)</p></li></ul> <h2>What you need</h2> <ul> <li><a href="https://www.smwcentral.net/?p=section&a=details&id=21211">Lunar Magic</a></li> <li><a href="https://www.smwcentral.net/?p=section&a=details&id=19047">Gopher Popcorn Stew</a> (block inserter)</li> <li><a href="https://www.smwcentral.net/?p=section&a=details&id=19982">UberASM Tool</a></li> <li><a href="https://www.smwcentral.net/?p=section&a=details&id=18531">SRAM/BWRAM plus patch</a></li> </ul> <h2>Recommended</h2> <li>Status bar patches (Vanilla SMW's HUD is limited) <ul> <li><a href="https://www.smwcentral.net/?p=section&a=details&id=19247">Super status bar</a>. By default, the defines in this package assumes you are using this patch </li> </ul> </li> <h2>Useful stuff provided here</h2> <ul> <li>I made a <a href="Readme_files/JS_MemoryCalculator.html" id="MemoryCalculator">Javascript tool</a> that displays the memory statistics, what flag number is in what byte (or what RAM address its located), and what flag gets written to what flag in <kbd>$7FC060</kbd>, good for debugging among other things.</li> <li>For debugging and dealing with bitwise values, I've provided a fast and easy <a href="Readme_files/JS_HexBinary.html" id="HexBinary">simple converter</a> between hex and binary.</li> </ul> <h2>Before we begin...</h2> <p>This is the type of ASM resource that you may have to come back, edit stuff, and then re-insert periodically as you develop your hack, since this works in tandem of you creating levels. Instructions below marked with “<sup><font color="yellow">[Return later]</font></sup>” indicates you to perform these steps when re-inserting in your hack.</p> <p>I've included a sample level (for level 105) demonstrating the key blocks, along with sample ASM tables (what flags the blocks should be using and what group-128 to use in level 105).</p> <p>If you are using this on SA-1, LM version 3.10 (September 24, 2019) and before it, they have a bug <a href="https://smwc.me/1522106">I've reported</a> that causes blocks to fail to spawn (or spawn when it shouldn't (never experienced this)) even when <kbd>$7FC060-$7FC06F</kbd> bits are set (or clear) on certain emulators. This is due to the fact that level load code is processed under SA-1, so WRAM (<kbd>$7Exxxx</kbd> and <kbd>$7Fxxxx</kbd>) access <abbr title="Certain emulators, like ZMZ would allow SA-1 to access WRAM as they “read” the instructions, which the real SNES (and accurate emulators, like BSNES+) doesn't do. ">will not work</abbr>. So don't use this ASM resource on an SA-1 hack using LMv3.10 (or any previous LM versions), as blocks may appear (or not) incorrectly can occur on certain emulators.<br><br> EDIT: This is now fixed on version 3.11 (released on 2020/02/09)</p> <h2>Insertion</h2> <ol> <li> <p>Freerams and tables:</p> <ol> <li> <p>Because the blocks check their locations and level numbers, you must open these files:</p> <ul> <li><p><kbd>UberasmTool/Library/MBCM16WriteGroup128To7FC060.asm</kbd>, this determines what group-128 to use for each level using the MBCM16.</p></li> <li><p><kbd>UberasmTool/Library/MBCM16DisplayKeyCounter</kbd>, this displays the key counter on the heads-up display (HUD) of how many keys of a particular level.</p></li> <li><p><kbd>CustomBlocks/routines/SearchBlockFlagIndex.asm</kbd>, blocks' routine to find out what flag they are.</p></li> <li><p><kbd>CustomBlocks/routines/GetWhatKeyCounter</kbd>, blocks' routine to find what key to use.</p></li> </ul> <p>These contains a list of level numbers, block location within level and group numbers (times <kbd>$10</kbd>), and what key counter for the level to use. Comments should fully explained how each table correspond. Even better is that the JS tool (linked previously) also print out placeholder template tables (with comments pointing of each item and their flag numbers/Level ID) for you to copy.</p> <li><p>Open <kbd>FlagMemoryDefines/Defines.asm</kbd> and edit <kbd>!Freeram_MemoryFlag</kbd> if you find RAM conflicts with another ASM resource. I highly recommend make note as (including before and after) you insert any patches (including third party) so it is easier to keep track of. There's also other settings you can play around with. Also, this assumes you are using the super status bar patch, if you are not using that and/or using other patches, adjust the defines under “<kbd>HUD stuff</kbd>”.</p></li> </li> </ol> </li> <li> Using SRAM/BWRAM plus patch. <ol> <li> <p>Open either SRAM or BWRAM plus depending on if your hack is SA-1 or not. Now again, using the JS tool, copy the given ASM tables from an obviously named section and paste them accordingly. Remember that the order in table and defaults must match (the first <kbd>dl $xxxxxx : dw $yyyy</kbd> goes to the first group of bytes in “<kbd>defaults</kbd>”, second on second, and so on). Remember that this is the default value they would be when you start a new game.</p> <p>Keep in mind about LM's CM16:</p> <ul> <li> <p>With “Always show objects” unchecked, if the flag is 0, it uses tile <kbd>$0025</kbd>, otherwise use the tile you've just placed down. If you want something to not exist but later in the game to appear forever, then you should default the flag to 0 and SET the bit on an event the player does something to make it appear. To make it disappear and stays gone forever, you should default the flag to 1 and CLEAR the flag on an event the player does something to make it gone forever (such as when the player picks up the key).</p> </li> <li> <p>With “Always show objects” checked, if the flag is 0, it uses the tile you've placed down, otherwise it will use <kbd>Tile_Youve_Placed_Down+$0100</kbd> (example: if you use tile <kbd>$0425</kbd>, it will use that tile when the flag is 0, otherwise it will use <kbd>$0<u>5</u>25</kbd>, which is the next map16 page keeping its low byte).</p> <p>Seems only useful if you don't want to use tile <kbd>$0025</kbd>. To keep things organized when using both settings (“Always show objects” checked for some blocks and unchecked for others), I would recommend having all the default values in this table be set (<kbd>%11111111</kbd>) and always clear them when the player triggers something, and have the intended “inital” block state on the second of the two map16 pages (<kbd>$0525</kbd> (inital) → <kbd>$0425</kbd> (block you place)).</p> </li> </ul> <p>Thus, you are not restricted to using all eight ones or all eight zeroes in the bytes.</p> </li> </ol> </li> <li> <p>Uberasm tool insertion</p> <ol> <li> <p><sup><font color="yellow">[Return later]</font></sup>In any level that are using the flags, create/edit a .asm file (like <kbd>Level105.asm</kbd>), in that file, paste this code: <table><tr><td><pre> incsrc "../FlagMemoryDefines/Defines.asm" load: ;Transfer group-128 to $7FC060 JSL MBCM16WriteGroup128To7FC060_LoadFlagTableToCM16 ;[...] RTL main: ;Display key counter on the HUD (during the level at play) LDY #$00 ;>$xx is what key counter to use, as an index from !Freeram_KeyCounter. JSL MBCM16DisplayKeyCounter_DisplayHud ;[...] RTL init: ;Display key counter on the HUD (during screen fading into the level) LDY #$00 ;>$xx is what key counter to use, as an index from !Freeram_KeyCounter. JSL MBCM16DisplayKeyCounter_DisplayHud ;[...] RTL </pre></td></tr></table> What will happen is when the level loads, <kbd>$7FC060</kbd> is now written from <kbd>!Freeram_MemoryFlag+(<Group_Number>*$10)</kbd> to <kbd>!Freeram_MemoryFlag+((<Group_Number>*$10)+$0F)</kbd>. Group number is found in <kbd>.OneHundredTwentyEightFlagGroupList</kbd> in <kbd>UberasmTool/Library/MBCM16WriteGroup128To7FC060.asm</kbd>. This also displays the key counter (or write blank tile <kbd>$FC</kbd> if you don't have any) on the status bar/HUD.</p> </li> <li> <sup><font color="yellow">[Return later]</font></sup>Specify what group-128 in <kbd>MBCM16WriteGroup128To7FC060.asm</kbd> should your level will use, Then run uberasm tool to apply the changes. </li> </ol> </li> <li>Block insertion</li> <ol> <li> <p>Copy the list of blocks in <kbd>Blocks/list.txt</kbd> and paste it in GPS's list file. You may need to edit their map16 number (or ID number).</p> </li> <li> <p>Copy and paste the block and routine ASM files, their folders should have obvious names to know where to put.</p> <ul> <li> <p><sup><font color="yellow">[Return later]</font></sup> Make sure you edit the tables in <kbd>GetWhatKeyCounter.asm</kbd> and <kbd>SearchBlockFlagIndex.asm</kbd> to give it the level number, the layer it is on, its block location and what key counter the blocks should read on a given level number. For block location, If dealing with multiple blocks in a group, such as the locked gates and doors, refer <a href="#TopLeftGroup" id="BlockInsertTables">here</a> that it uses their top-leftmost block.</p> <p>You can set it to write on ANY flag number here if you want other blocks to disappear/appear/change in the other level, regardless of the group number the current level is set to, unlike the limitation to a range of 128 bits. Just avoid using the <kbd>%SearchBlockFlagIndex()</kbd> and <kbd>%GetWhatKeyCounter()</kbd> routines as they lookup the current level.</p> </li> </ul> </li> <li> <p>Save and run GPS to insert into your game.</p> </li> </ol> <li>LM and the CM16 flag number</li> <ol> <li> Graphics <ol> <li> <p>Create the Exgraphics and Graphics folder by pressing “Quick Extract GFX from ROM” and “Quick Extract ExGFX from ROM” (red and blue mushroom) if you haven't done so already.</p> </li> <li> <p>Then paste these files, they should have obvious names, however, for <kbd>GFX28.bin</kbd> (status bar tiles), you can either replace the original with that in the “Graphics” folder, or rename it <kbd>ExGFXYY</kbd> where <kbd>YY</kbd> is the hexadecimal number of what slot you want to insert to (and to use it in a level, it must be on LG1). Now insert the graphics by pressing “Quick Insert GFX and ExGFX to ROM, reload graphics” (yellow mushroom with a red left arrow).</p> </li> </ol> </li> <li> Map16 tiles <ol> <li> <p>Insert the <kbd>.map16</kbd> file included in this package (located at <kbd>Blocks/Map16.map16</kbd>) using Lunar Magic's map16 editor (the button showing a yellow ? block with the red left arrow). By default it should be inserted in page 5-6.</p> </li> </ol> </li> <li> Placing blocks in the level <ol> <li> <p><sup><font color="yellow">[Return later]</font></sup><a href="#BlockInsertTables" id="TopLeftGroup">^</a>, Every time you place blocks in the level, you have to assign them to their respective flags, this involves the frequent editing of <kbd>SearchBlockFlagIndex.asm</kbd>. While finding the level numbers and what layer they're on is easy, the block coordinate is displayed LM's status bar on the bottom-left corner of what block your mouse cursor is hovering:<br> <img src="Readme_files/LM_BlockCoords.png"><br> Note that on and after <kbd>Version 3.03 April 1, 2019</kbd>, the coordinates are displayed in hexadecimal numbers, before this, they were decimal.<br><br> Use the X and Y coordinate for the table <kbd>?GetFlagNumberC800IndexStart:</kbd>. If involving multi-blocks, such as gates that are more than 1 block wide and/or tall, you would take the top-leftmost block's coordinate of the multi-blocks group (shown in red squares):<br> <a href="Readme_files/TopLeftAssignedBlock.png" target="_blank"><img src="Readme_files/TopLeftAssignedBlock.png" width="800"></a></p> <p>When dealing with doors (exit enabled objects that take you to another level), if you have the define setting <kbd>!TopPart</kbd> set to <kbd>!False</kbd>, (a regular-sized 16x32 door setting that allows any powerup instead of just small mario) the block would use the block position <i>above</i> it (the custom block itself will act as the lower part of the 16x32 door and the top part is the selected coordinate), unless the lower half of the door is at <kbd>Y=$0000</kbd>, (the selected coordinate will use <kbd>Y=$0000</kbd> instead of <kbd>Y=$FFFF</kbd>):</p> <img src="Readme_files/DoorSelectedCoordinate.png"> <p>Small and a normal-sized doors included, you don't have to create a copy and edit the settings to make a small door.</p> </li> <li> <p><sup><font color="yellow">[Return later]</font></sup>Also every time you place these memorized blocks, is that you must select them (all within the group if multi-blocks like the locked gates), then give them their conditional map16 flags by <kbd>Menubar</kbd> → <kbd>Edit</kbd> → <kbd>Conditional Direct Map16...</kbd>, otherwise they'll keep reappearing every level load.</p> <p>What LMCM16 flag number you should put down is the flag number of <kbd>!Freeram_MemoryFlag</kbd>, MOD 128. For example:<br> <table> <tr><th>Flag number of <kbd>!Freeram_MemoryFlag</kbd></th><th>LM's flag number</th></tr> <tr><td>$000 (Group 0)</td><td>$00</td></tr> <tr><td>$001 (Group 0)</td><td>$01</td></tr> <tr><td>$002 (Group 0)</td><td>$02</td></tr> <tr><td>...</td><td>...</td></tr> <tr><td>$07E (Group 0)</td><td>$7E</td></tr> <tr><td>$07F (Group 0)</td><td>$7F</td></tr> <tr><td>$080 (Group 1)</td><td>$00</td></tr> <tr><td>$081 (Group 1)</td><td>$01</td></tr> <tr><td>$082 (Group 1)</td><td>$02</td></tr> <tr><td>...</td><td>...</td></tr> <tr><td>$0FE (Group 1)</td><td>$7E</td></tr> <tr><td>$0FF (Group 1)</td><td>$7F</td></tr> <tr><td>$100 (Group 2)</td><td>$00</td></tr> <tr><td>$101 (Group 2)</td><td>$01</td></tr> <tr><td>$102 (Group 2)</td><td>$02</td></tr> <tr><td>...</td><td>...</td></tr> </table><br> Also remember that up to 128 different flag numbers can be used per level, this means, for example if you end up with one level using 128 flag numbers (<kbd>$00-$7F</kbd>), you'll need to use another level to use additional flags.</p> <p>Remember, when the level loads, what group-128 to use to write to $7FC060 is specified in <kbd>UberasmTool/Library/MBCM16WriteGroup128To7FC060.asm</kbd>. With the previously mentioned paragraph, when you choose any group-128, you are bounded to a range of what flag numbers of <kbd>!Freeram_MemoryFlag</kbd> to use:<br> [<kbd>LowestFlagNumber = GroupNumber * 128</kbd>] to [<kbd>HighestFlagNumber = (GroupNumber * 128) + 127</kbd>]<br> For example, if level <kbd>$105</kbd> uses group <kbd>$0</kbd>, only flags <kbd>$0-$7F</kbd> are available in that level, if level <kbd>$1CB</kbd> uses group <kbd>$1</kbd>, only flags <kbd>$80-$FF</kbd> are available.</p> </li> </ol> </li> </ol> </ol> <h2>Notes and Troubleshooting</h2> <ul> <li> If you're unsure if you had set up the flag and level tables correctly I have some protips: <ul> <li> <p>Use the block <kbd>Template_PermaDisappear.asm</kbd>, place it in the level, and touch it. If it doesn't disappear in a puff of smoke, then that block location isn't assigned to a flag number. You can use other blocks, they will simply do nothing if it doesn't find its location associated to a flag number.</p> </li> <li> <p>If the block do function but keeps respawning on level load, then that means there is something wrong with the following:</p> <ul> <li> <p><kbd>MBCM16WriteGroup128To7FC060.asm</kbd> (uberasm tool library) isn't set up correctly, set to use the wrong level number, wrong group-128, wrong layer, and/or wrong block C800 positions.</p> </li> <li> <p>You must've set the block to the wrong flag number that is assigned.</p> </li> </ul> <p>Otherwise if another block in the same level disappears, then you've set the block to the same flag number. This is correct for multi-block gates bigger than a 1x1 block so that the whole door remains gone when unlocked and level refreshes, however if 2+ separate things in the same level uses the same $7FC060 flag number, then both are affected, which any of them have been set to the wrong flag.</p> </li> </ul> </li> <li> <p>You may be asking the question, why does the game lag if the player tries to activate these blocks when they aren't assigned to the list? This is because each frame and each of the player's collision point touches the block, it has to search a long list of flag numbers (and fail to find the matching entry), which tanks the game's performance (a loop with huge number of iterations). The longer the list, the harder the game will slow down. However, if your entire hack does not contain any non-listed MBCM16 blocks (which your hack <u>should</u> have all of them be listed), you shouldn't worry, since the blocks execute for only 1 frame for a listed MBCM16 block the moment the block disappears.</p> <ul> <li> Just like most loops in vanilla SMW, it checks the last item, going backwards on the index towards the first, this also includes the uberasm tool dealing what group-128 the current level should use. Thus if the block you interact is at the lower indexes (close to index/flag 0), it would take longer than the higher indexes to find the matching item. </li> </ul> </li> <li> <p>When the player enters an exit-enabled locked door (or, in general, teleport to another level), the key counter on the HUD (heads-up display) will not update until the level the player is heading to is loaded. This is because during level transition, the HUD no longer update the tiles every frame.</p> </li> <li> <p>You don't have to have all flags to be permanently cleared/set, if you want some levels to only reset them when the player goes to the overworld map via dying, START+SELECT, and beating the level. Just have an ASM file created into uberasm tool's gamemode folder to be called on the <kbd>list.txt</kbd> as gamemode <kbd>0D</kbd> (overworld fade-in) or <kbd>0E</kbd> (overworld). I would prefer having this under <kbd>init:</kbd> because it is not necessary to “reset” them every frame on the map (reset in quotes, because, as in to restore their original values, not always have them all zero on all flags): <table><tr><td><pre> init: ;This will put the flags back to their set position by writing all $FFs: LDX.b #<NumberOfBytes>-1 - LDA #$FF ;>Value to write on each byte. $00 = %00000000, $FF = %11111111. STA <StartingRAMAddress> TXA SEC SBC #$01 TAX BCS - ;>Why am I not using DEX : BPL -? Because then you're limited to indexes $00-$7F. </pre></td></tr></table><br> If you happen to have a byte that contains flags that shouldn't be reverted while other to be reverted: <table><tr><td><pre> ;Example: %RRRR----, where R means a bit must be reverted, while - retains its value. ;Clear bits LDA $xxxxxx AND.b #%00001111 ;>AND returns 0s for any matching bit pairs have at least one 0 on either, so AND is used to clear bits. STA $xxxxxx ;>$xxxxxx is now %0000xxxx. ;Set bits LDA $xxxxxx AND.b #%11110000 ;>ORA returns 0s if both matching bit pairs are 0, therefore, ORA is used to set bits. STA $xxxxxx ;>$xxxxxx is now %1111xxxx. </pre></td></tr></table><br> And don't forget, if you are resetting the locked gates and locked doors, you MUST also reset the key counters too, otherwise the locked gates and doors will respawn and the key counter is retained with them also respawning in the level: <table><tr><td><pre> LDA #$00 STA !Freeram_KeyCounter STA !Freeram_KeyCounter+1 ;[...]</pre></td></tr></table> </p> </li> </ul> <h2>Credits</h2> <ul> <li><a href="https://stackoverflow.com/a/8083480/11030779">Stack Overflow on the font for inputs</a>.</li> <li><a href="https://www.smwcentral.net/?p=profile&id=22951">MarioFanGamer</a> for the <a href="https://www.smwcentral.net/?p=section&a=details&id=21130">door blocks.</a></li> </ul>
GhbSmwc/SMW_MemoryBasedConditionalMap16
A flag table system that other games use for pickup items (prevent respawning), unlocked doors (don't relock when level reloads), etc.
AssemblyAGPL-3.0