Table of Contents
Health Blocks
This hack adds a new type of tile to heal and/or hurt Samus. There are a number of configurable options in the code.
Assembling
This code is meant to be assembled with snarfblasm, which will produce an IPS patch. It should be possible with other assemblers, but minor syntax changes will be required and each section (identified by .PATCH directive) will need to be assembled and inserted separately.
This code requires an additional file, the Defines file, in the same folder and named “labels.asm”. This file declares all the existing variables used in Metroid.
To assemble, place the following each in the same folder: snarfblasm, labels.asm, and the code below in a file named “healthblocks.asm”. Run snarfblasm on the command line with the command:
snarfblasm healthblocks.asm
This should produce a file named “healthblocks.ips”, which can be applied to an unexpanded Metroid ROM, using a patcher such as Lunar IPS.
Options
There are a number of options in the “Config” section that can be configured. Place a semi-colon before a “define” to disable a feature or remove the semi-colon to enable a feature. The features are described below.
- HealBlocks - Include healing blocks.
- HurtBlocks - Include damaging blocks.
- HealSolid - Healing blocks are solid and can not be passed through by the player. Note that enemies will still be able to pass through these blocks.
- HurtSolid - Damaging blocks are solid and can not be passed through by the player. Note that enemies will still be able to pass through these blocks.
- NoHurtWithVaria - Player is immune to damaging effects of hurt blocks when equipped with Varia.
- BounceOnHurt - Causes the player to bounce back when hurt by damaging blocks, in the same manner as when hurt by an enemy. This includes momentary invincibility blink.
- HurtWhileBlinking - Causes the player to take damage during invincibility blink. Can be combined with BounceOnHurt to use the hurt bouce-back but continuous health drain.
- DEBUG - This option enabled debug code that was used while writing the hack
There are some additional configurable values.
- HealyTile - Specifies the tile number that has healing effects. This corresponds to the tile numbers shown in the left-most pane in Editroid's structure editor.
- HurtyTile - Specifies the tile number that has damaging effects. This corresponds to the tile numbers shown in the left-most pane in Editroid's structure editor.
- HealRate - Specifies the amount of health restored per frame. Note that this value is in a fixed-point BCD format, specifying tenths of health units. For example, to restore 1 health per frame, use the value $0010. To restore 2.5 health per frame, use the value $0025. To restore 1 energy tank per frame, use the value $1000.
- HurtRate - Specifies the amount of health lost per frame. Uses the same format as HealRate. If BounceOnHurt is enabled (without HurtWhileBlinking), this is effectively the amount of health the player will lose each time he touches the block.
Code
; --------------------------------------------------------------------------- ; Health Blocks ; ; by snarf and Drevan Zero ; ; 2/3/2013 - Version 1.0 ; --------------------------------------------------------------------------- .include labels.asm ; Includes all Metroid variables declared in the Metroid disassembly. ; Very convenient. ; --------------------------------------------------------------------------- ; Config ; --------------------------------------------------------------------------- ; To disable an option, place a semi-colon before the .define. ; To enable an option, remove a semi-colon. ;.define DEBUG ; Include debugging code .define HealBlocks ; Include heal blocks .define HurtBlocks ; Include hurt blocks ;.define HealSolid ; Heal blocks are solid ;.define HurtSolid ; Hurt blocks are solid ;.define BounceOnHurt ; Get thrown back when touching a hurt block ;.define HurtWhenBlinking ; Drain health while blinking .define NoHurtWithVaria ; Player is immune to hurt blocks with Varia HealyTile = $FD ; (Tile number must be greater than $80) HurtyTile = $FE ; (Tile number must be greater than $80) ; These two values use BCD. Use $, but write it as if decimal. E.g. to ; heal 1.5 health per frame, set HealRate to $0015 HealRate = $0001 ; Tenths of health added per frame HurtRate = $0001 ; Tenths of health subtracted per frame ; --------------------------------------------------------------------------- ; Declarations ; --------------------------------------------------------------------------- ; Variables ------------ TouchingHealyBlock = $C9 TouchingHurtyBlock = $CA ; Existing Functions---- MakeCartRAMPtr = $E96A IsBlastTile = $E9BE Exit16 = $E81D LavaAndMoveCheck = $E269 AddHealth = $CEF9 SubtractHealth = $CE92 ; --------------------------------------------------------------------------- ; Code ; --------------------------------------------------------------------------- ; --------------------------------------- .PATCH 07:E7E6 ; Hit test Hijack ; --------------------------------------- ; This part of the routine is unchanged HitTestHijack: LE7E6: jsr MakeCartRAMPtr ;($E96A)Find object position in room RAM. ldy #$00 lda ($04),y ; get tile value cmp #$4E beq $E81E jsr $95C0 jsr $D651 bcc Exit16 ; CF = 0 if tile # < $80 (solid tile)... CRASH!!! cmp #$A0 ; is tile >= A0h? (walkable tile) ; These branches are changed to use the new IsWalkableTile that does our additional ; testing for healy blocks. ;(its okay to overflow into next function because it is now unused) ; bcs IsWalkableTile ; jmp IsBlastTile ; tile is $80-$9F (blastable tiles) bcc + jmp IsWalkableTile * jmp IsBlastTile ; --------------------------------------- .PATCH 07:CD81 ; Per frame Hijack ; --------------------------------------- ; Relacing this: ; jsr LavaAndMoveCheck ; With this: jsr HealthBlockCheck ; HealyBlockCheck will jump to LavaAndMoveCheck ; --------------------------------------- .PATCH 07:CA35 ; NEW CODE ; --------------------------------------- IsWalkableTile: ; New implementation of IsWalkableTile that does ; additional testing for healy blocks ldy IsSamus beq @NotSamus ; special case for Samus ; Test for healing block .ifdef HealBlocks cmp #HealyTile ; Is it a healy block? bne @NotHealyBlock lda #$01 sta TouchingHealyBlock .ifdef DEBUG sta TankCount ; Give samus 1 energy tank (debug only) .endif .ifdef HealSolid clc ; Return, solid block .else sec ; Return, air block .endif rts @NotHealyBlock: .endif ; Test for hurting block .ifdef HurtBlocks cmp #HurtyTile ; Is it a hurty block? bne @NotHurtyBlock lda #$01 sta TouchingHurtyBlock .ifdef DEBUG lda #$02 sta TankCount ; Give samus 2 energy tanks (debug only) .endif .ifdef HurtSolid clc ; Return, solid block .else sec ; Return, air block .endif rts @NotHurtyBlock: .endif pha ; Preserve tile number lda #$00 sta TouchingHealyBlock sta TouchingHurtyBlock .ifdef DEBUG sta TankCount ; Remove energy tank .endif pla ; Proceed with normal hit testing ; Below code is unchagned from original routine dey ; = 0 sty SamusDoorData cmp #$A0 ; crash with tile #$A0? (scroll toggling door) beq + cmp #$A1 ; crash with tile #$A1? (horizontal scrolling door) bne ++ inc SamusDoorData * inc SamusDoorData * @NotSamus: dex beq + jsr $E98E jmp $E7E6 * sec ; no crash ; NO ; Exit16: rts HealthBlockCheck: ; New routine that checks the TouchingHealyBlock variable and ; heals samus if applicable .ifdef HealBlocks lda TouchingHealyBlock beq + ; Touching healy block? ; Add health .if HealRate > $00FF lda #>HealRate ; Only set high byte if > $FF sta HealthHiChange .endif lda #<HealRate sta HealthLoChange jsr AddHealth lda FrameCount ; Every 20th frame... and #$1F bne + jsr $CBCA ; Play sound * .endif .ifdef HurtBlocks lda TouchingHurtyBlock beq + ; Touching healy block? .ifndef HurtWhenBlinking lda SamusBlink ; Don't hurt samus if blinking bne + .endif .ifdef NoHurtWithVaria lda SamusGear ; Don't take damage with varia and #gr_VARIA bne + .endif ; Subtract health .if HurtRate > $00FF ; Only set high byte if > $FF lda #>HurtRate sta HealthHiChange .endif lda #<HurtRate sta HealthLoChange jsr SubtractHealth .ifdef BounceOnHurt lda SamusBlink ; Only bounce back when not blinking bne + lda #$FF ; This causes hit animation to occur sta SamusHit .else lda FrameCount ; Every 20th frame... and #$1F bne + jsr $CBCA ; Play sound .endif * .endif jmp LavaAndMoveCheck ; Return from hijack EndOfCode: ; If the assembled code extends beyond the available unused space, ; produce an error. .if EndOfCode >= $CAF5 .error Code exceeded available size! .endif