;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Free Overworld patch ;; ;; by TheBiob ;; ;; ;; ;; Description: ;; ;; This patch allows the player to move in a Super Mario 3D World like overworld ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Note: ;; ;; This patch does NOT work if the overworld wasn't saved before (The game will crash) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; if read1($00FFD5) == $23 sa1rom !base1 = $3000 !base2 = $6000 !base3 = $000000 !7EC800 = $40C800 !7ED000 = $40D000 else lorom !base1 = $0000 !base2 = $0000 !base3 = $800000 !7EC800 = $7EC800 !7ED000 = $7ED000 endif !SpclFlag = $1F2B|!base2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Special Flags: ; 1 byte that is used to tell if the player is able to swim/climb. ; Format: xxxxCcWw. ; C = Player 2 (Luigi) can't climb ; c = Player 1 (Mario) can't climb ; W = Player 2 (Luigi) can't swim ; w = Player 1 (Mario) can't swim ; x = Unused. Setting these won't affect the ability of any player ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; !SpclYoshiFlag = $1F2C|!base2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Special Yoshi flags: ; 1 byte that is used to tell if yoshi is able to swim/climb. ; Note: The player ability does not matter if yoshi can do it. ; Example: If the player has the "can't swim" bit set but is riding a yoshi which doesn't have it set the player will still be able to swim. ; Format YBRGybrg ; Y = Yellow yoshi can't climb ; B = Blue yoshi can't climb ; R = Red yoshi can't climb ; G = Green yoshi can't climb ; y = Yellow yoshi can't swim ; b = Blue yoshi can't swim ; r = Red yoshi can't swim ; g = Green yoshi can't swim ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; !Tile = $140B|!base2 ; Temporary storage of current layer 2 tile (2 bytes) org $03BB20 WriteLevelName: ; > Set "WriteLevelName" label so we can jsl to that rather than $03BB20 ; Note: This address is empty in the original game LM writes a routine there as soon as the ow is saved so don't use this patch without the overworld saved at least once as it'll most likely cras org $04920F JMP $9831 ; This originally jumped to the hijacked routine below. Now it skips this routine and jumps to where the code below ended org $04925A ; JSR GetPos ; > Get position in $00-$02 and layer 1 index into $04. Code starts there and jumps here afterwards. STZ $75 REP #$30 LDX $04 ; \ Load index for layer 1 tiles LDA !7EC800,x ; | Load layer 1 tile number the player is standing on STA $13C1 ; / LDA !7ED000,x ; \ Get level number to go to... AND #$00FF ; | CMP $13BF|!base2 ; | ...and check if it changed BEQ DontUpdateText ; / Don't update level name when the level didn't change STA $13BF|!base2 INC $75 ; > Set "update level names" flag DontUpdateText: SEP #$30 autoclean JSL HandleMovement INC $88 ; > Update animation timer (In levels this is used as a timer for the pipe animation but w/e) LDA $75 BEQ NoNewText REP #$30 LDA $13BF|!base2; \ get level number and update level name text ;ASL ; | Old routine. Uncomment this and comment the JSL WriteLevelName out to use the old method of writing the level names. (LM Hijacks this so I don't recommend doing so but if you want to for some reason there you go) ;TAX ; | ;LDA.W $A0FC,X ; | ;STA $00 ; | ;JSR $9D07 ; / JSL WriteLevelName ; > Run LM's update level name routine SEP #$30 NoNewText: JMP $9120 ; > Jump to code that checks if a level should be entered org $048FA0 ;GetPos: REP #$20 ; 16 bit A ; Accum (16 bit) LDX.W $0DD6|!base2 ; Get current character * 4 LDA.W $1F17|!base2,X ; Get character's X coordinate LSR ; \ LSR ; |Divide X coordinate by 16 LSR ; | LSR ; / STA $00 ; \ Store in $00 and $1F1F,x STA.W $1F1F|!base2,X ; / LDA.W $1F19|!base2,X ; Get character's Y coordinate LSR ; \ LSR ; |Divide Y coordinate by 16 LSR ; | LSR ; / STA $02 ; \ Store in $02 and $1F21,x STA.W $1F21|!base2,X ; / TXA ; \ LSR ; |Divide (current character * 4) by 4 LSR ; | TAX ; / JSR $9885 ; > Calculate index to for layer 1 tile JMP $925A ; > Jump to routine above org $048586 dl $048FA0 ; dl GetPos ; > Change pointer to start at the get position routine org $048F87 STZ $0DD5|!base2 ; \ Set $0DD5 to 0 (Don't auto-walk) LDA $0DB2|!base2 ; | Check if game is in 2-player mode BEQ + ; | LDA #$06 ; | Set action to Fade between players STA $13D9|!base2 ; | RTS ; / Skip auto-walk routine + INC $13D9|!base2 ; \ If not in 2-player mode don't switch between players RTS ; / org $04896E LDA $88 ; \ This originally was the normal frame-counter. Changed to AND #$0018 ; / freecode ; These are used to determine what spcl flag bits do what. Don't change them unless you know what you're doing. WaterBits: dw $0001,$0002 LadderBits: dw $0004,$0008 YoshiClimb: dw $0000,$FFFF,$0080,$0040,$0020,$0010 YoshiWater: dw $0000,$FFFF,$0008,$0004,$0002,$0001 ; Tiles with specific interaction: ; Format: dw $xxxx,$yyyy ; xxxx = First tile number, yyyy = Last tile number + 1 ; Example: dw $0000,$000F ; This would make tiles 0000 through 000E act the same way ; Solid tiles: Tiles that the player can't walk on. SolidTiles: dw $0000,$0003 dw $0005,$0011 dw $0012,$0017 dw $0019,$0021 dw $002C,$0035 dw $0039,$0059 dw $0060,$006A dw $0085,$008A dw $0095,$009A dw $00A6,$00AA dw $00B6,$00B8 dw $00BD,$00C0 .end ; Ladder tiles: If the player touches these tiles he will be able to climb up/down on the ladder but not move left/right. Acts solid if the player's or Yoshi's can't climb bit is set LadderTiles: dw $003E,$003F dw $0091,$0095 dw $00A0,$00A4 dw $008C,$008E dw $00B9,$00BA .end ; Water tiles: All tiles that when walked on will make the player use the swimming animation. Act solid when the player's or Yoshi's can't swim bit is set WaterTiles: dw $002B,$002C dw $003F,$0040 dw $0075,$0078 dw $00AA,$00AC .end ; Tiles not listed in any of these will not affect the player's movement/graphics. Unless you code them to I guess. ; This routine handles the player's movement including collision, updating player graphics etc. ; Basically everything the patch does. HandleMovement: PHB : PHK : PLB ; Change data bank LDA $13BF|!base2 BEQ .disableLevelEnter LDA $13C1|!base2 ; \ Check if the layer 1 tile is revealed CMP #$6E ; | These are (or should be) all tiles that are not revealed yet BCC .dontDisableLevel ; | Change this if you need/want to CMP #$77 ; | BCC .dontUpdateText ; | CMP #$78 ; | BEQ .dontUpdateText ; | CMP #$7A ; | BEQ .dontUpdateText ; | CMP #$7B ; | BEQ .dontUpdateText ; | CMP #$7D ; | BEQ .dontUpdateText ; | CMP #$7F ; | BNE .dontDisableLevel ; / .dontUpdateText STZ $75 ; > reset update text flag .disableLevelEnter LDA #$F0 ; \ Disable any button presses that could enter a level if the destination is level 00 or the layer 1 tile isn't revealed yet TRB $16 ; | TRB $17 ; | TRB $18 ; / .dontDisableLevel LDA $0DB3|!base2 ASL STA $0F JSR GetBlocked ; Update $77 LDA $15 ; \ Load pressed buttons AND #$0F ; | Remove the non directional inputs BEQ .noButton ; | Skip if no button is pressed ASL : TAX ; | Multiply by 2 and put it into x REP #$20 ; | LDY $0F ; | STZ $0A ; | > Reset temporary "moved in direction" flag JSR.w (MovementPntr,x) ; / Jump to the routine that handles the direction the player pressed SEP #$20 LDA $0A ; \ If player didn't move in this frame then reset his animation timer BNE .button ; / .noButton LDA $77 ; \ If player is swimming don't reset animation AND #$40 ; | BNE .button ; / LDA #$15 STA $88 .button LDY $0F LDA $77 ; \ Check if player is swimming and update player image if necessary AND #$80 ; | BNE .ladder ; | LDA $77 ; | AND #$40 ; | BEQ .nowater ; | LDA $1F13|!base2,y ; | CMP #$07 ; | BCS .nowater ; | CLC : ADC #$08 ; | STA $1F13|!base2,y ; / .nowater PLB ; Restore data bank and return RTL .ladder LDA #$16 STA $1F13|!base2,y LDA $15 AND #$0F BNE + LDA #$FE STA $88 + PLB ; Restore data bank RTL MovementPntr: ; UDLR dw DoNothing ; 0000 No buttons dw right ; 0001 Right button dw left ; 0010 Left button dw DoNothing ; 0011 Left and Right button (cancels) dw down ; 0100 Down button dw downRight ; 0101 Down and right button dw downLeft ; 0110 Down and left button dw down ; 0111 Down, left and right (L&R cancels) dw up ; 1000 Up button dw upRight ; 1001 Up and right button dw upLeft ; 1010 Up and left button dw up ; 1011 Up, left, and right (L&R cancels) dw DoNothing ; 1100 Up and down (cancels) dw right ; 1101 Up, down and right (U&D cancels) dw left ; 1110 Up, down and left (U&D cancels) dw DoNothing ; 1111 Up, down, left and right (cancels) right: LDX $0DD6|!base2 LDA $77 ; \ Check if player is climbing or blocked in the direction AND #$0081 ; | BNE .return ; / INC $1F17|!base2,x ; > Move the player INC $0A ; > Set temporary "player moved" flag .return LDA #$0006 ; \ Change player image STA $1F13|!base2,y ; / RTS left: LDX $0DD6|!base2 LDA $77 ; \ Check if player is climbing or blocked in the direction AND #$0082 ; | BNE .return ; / DEC $1F17|!base2,x ; > Move the player INC $0A ; > Set temporary "player moved" flag .return LDA #$0004 ; \ Change player image STA $1F13|!base2,y ; / RTS down: LDX $0DD6|!base2 LDA $77 ; \ Check if player is blocked in the direction AND #$0004 ; | BNE .return ; / INC $1F19|!base2,x ; > Move the player INC $0A ; > Set temporary "player moved" flag .return LDA #$0002 ; \ Change player image STA $1F13|!base2,y ; / RTS up: LDX $0DD6|!base2 LDA $77 ; \ Check if player is blocked in the direction AND #$0008 ; | BNE .return ; / DEC $1F19|!base2,x ; > Move the player INC $0A ; > Set temporary "player moved" flag .return LDA #$0000 ; \ Change player image STA $1F13|!base2,y ; / RTS downRight: JSR down JMP right downLeft: JSR down JMP left upRight: JSR up JMP right upLeft: JSR up JMP left DoNothing: RTS ; If you want to make custom sprites interact with ow layer 2 feel free to call this routine. ; See GetBlockedState below for info on what the routine needs print "Global GetBlockedState routine: $", pc GlobGetBlockedState: PHB : PHK : PLB ; Change data bank JSR GetBlockedState PLB ; Restore data bank RTL ; Return GetBlocked: LDY $0DB3|!base2 ; > Load current player (0 = Mario, 1 = Luigi) LDA $1F11|!base2,y ; \ STA $08 ; | Set up position values LDY $0DD6|!base2 ; | Load current player (0 = Mario, 4 = Luigi) REP #$20 ; | STZ !Tile ; | Reset current tile number LDA $1F17|!base2,y ; | STA $04 ; | LDA $1F19|!base2,y ; | STA $06 ; / ; Continue to get blocked state and return from there ; Get blocked state ; This routine will update $77 (Player blocked status) in the format CSxxUDLR ; C: Climbing ; S: In water (swimming) ; UDLR: Directions ; Input: ; $04 - X position (16-bit) ; $06 - Y position (16-bit) ; $08 - Not on main overworld (0 - Check main ow, non-0 Check Submap) GetBlockedState: STZ $77 ; > Reset blocked state JSR GetOwL2 ; > Get layer 2 tile JSR CheckWater BCC .ladder LDA #$0040 TSB $77 .ladder JSR CheckLadder BCC .noladder LDA #$0080 TSB $77 .noladder CheckSolid: LDA $04 ; \ Check tile right to the player CLC : ADC #$0004; | STA $04 ; / JSR GetOwL2 JSR CheckForSolidTile BCC .noR LDA #$0001 TSB $77 .noR LDA $04 ; \ Check tile left to the player SEC : SBC #$0008; | STA $04 ; / JSR GetOwL2 JSR CheckForSolidTile BCC .noL LDA #$0002 TSB $77 .noL LDA $04 ; \ Reset x position to middle CLC : ADC #$0004; | STA $04 ; / INC $06 ; \ Check below the player INC $06 ; | INC $06 ; / JSR GetOwL2 JSR CheckForSolidTile BCC .noD LDA #$0004 TSB $77 .noD DEC $06 DEC $06 DEC $06 DEC $06 JSR GetOwL2 JSR CheckForSolidTile BCC .noU LDA #$0008 TSB $77 .noU SEP #$20 RTS CheckWater: LDX.b #WaterTiles_end-WaterTiles-2 LDA !Tile .loop CMP WaterTiles,x DEX : DEX BCS .noTile CMP WaterTiles,x BCC .noTile RTS .noTile DEX : DEX : BPL .loop CLC : RTS CheckLadder: LDX.b #LadderTiles_end-LadderTiles-2 LDA !Tile .loop CMP LadderTiles,x DEX : DEX BCS .noTile CMP LadderTiles,x BCC .noTile RTS .noTile DEX : DEX : BPL .loop CLC : RTS CheckForSolidTile: LDX.b #SolidTiles_end-SolidTiles-2 .loop CMP SolidTiles,x DEX : DEX BCS .noTile CMP SolidTiles,x BCC .noTile RTS .noTile DEX : DEX : BPL .loop LDY $0DB3|!base2 ; > Load current player (0 = Mario, 1 = Luigi) LDA $0DBA|!base2,y ; > Load Yoshi color of current player BEQ .noYoshi TAY LDA !SpclYoshiFlag AND YoshiWater,y BEQ .checkYoshiLadder JSR CheckWater BCC .checkYoshiLadder RTS .checkYoshiLadder LDA !SpclYoshiFlag AND YoshiClimb,y BEQ .return JMP CheckLadder .noYoshi LDY $0DD6|!base2 ; > Load current player (0 = Mario, 4 = Luigi) LDA !SpclFlag AND WaterBits,y BEQ .checkLadder JSR CheckWater BCC .checkLadder RTS .checkLadder LDA !SpclFlag AND LadderBits,y BEQ .return JMP CheckLadder .return CLC : RTS ; Get layer 2 tile on Overworld. ; JSR to it and it'll return the 16-bit tile number in A ; Input: ; $04 = X position ; $06 = Y position ; $08 = Check submap (0 = Check Main Overworld, non-0 = Check Submaps) ; Also make sure the routine is called in 16-bit mode ; Output: ; 16-bit tile number in A GetOwL2: LDA $06 ; \ Get Y position AND #$00F8 ; | ASL : ASL ; / STA $00 ; > Store in $00 LDA $04 ; \ Get X position AND #$00FF ; | LSR : LSR : LSR ; / CLC : ADC $00 ; > Add Y position ASL ; > Index * 2 since it's two bytes per tile. ((Y&F8)<<2+((X&FF)>>3))<<1 LDX $08 BEQ + CLC : ADC #$2000 ; > Add #$2000 to index if player is on submap + LDX $05 BEQ + CLC : ADC #$0800 ; > Add #$0800 to index if player is in right half of ow + LDX $07 BEQ + CLC : ADC #$1000 ; > Add #$1000 to index if player is in bottom half of ow + REP #$10 ; > Index 16-bit TAX ; \ Get Tiles LDA $7F4000,x ; | AND #$03FF ; / Mask out property byte (Change this to #$1FFF if the tiles above should take the palette into account as well) SEP #$10 ; > Index 8-bit STA !Tile ; > Save current tile number RTS print freespaceuse, " bytes freespace used"