Skip to content

Instantly share code, notes, and snippets.

@yuriks
Created August 16, 2023 18:50
Show Gist options
  • Save yuriks/896dc682211c8cf12a9ba466c6472d93 to your computer and use it in GitHub Desktop.
Save yuriks/896dc682211c8cf12a9ba466c6472d93 to your computer and use it in GitHub Desktop.
Bank $A8 - Morph Ball eye disassembly
asar 1.90
lorom
dpbase 0
optimize dp always
optimize address ram
;;; RAM Variables
samus_collected_items = $7E09A4
samus_pos_x = $7E0AF6
samus_pos_x_sub = $7E0AF8
samus_pos_y = $7E0AFA
samus_pos_y_sub = $7E0AFC
enemy_index = $7E0E54
enemies_pos_x = $7E0F7A
enemies_pos_x_sub = $7E0F7C
enemies_pos_y = $7E0F7E
enemies_pos_y_sub = $7E0F80
enemies_flags1 = $7E0F86
enemies_flags2 = $7E0F88
enemies_spritemap = $7E0F8E
enemies_timer = $7E0F90
enemies_instruction_list = $7E0F92
enemies_instruction_timer = $7E0F94
enemies_ai_var0 = $7E0FA8
enemies_ai_var1 = $7E0FAA
enemies_ai_var2 = $7E0FAC
enemies_ai_var3 = $7E0FAE
enemies_ai_var4 = $7E0FB0
enemies_ai_var5 = $7E0FB2
enemies_parameter1 = $7E0FB4
enemies_parameter2 = $7E0FB6
ram_hdma_buffer = $7E9100
;;; Common enemy bank routines/constants
!enemy_empty_spritemap = $804D
;;; External routines
snd_playLib2Sfx = $8090CB ; $90CB: Queue sound, sound library 2, max queued sounds allowed = 6
hdma_spawnEyeHdma = $88E8D9 ; $E8D9: Spawn morph ball eye beam HDMA object
ai_util_isSamusWithinYRadius = $A0AEED ; $AEED: Is Samus within [A] pixel rows of enemy
ai_util_isSamusWithinXRadius = $A0AF0B ; $AF0B: Is Samus within [A] pixel columns of enemy
ai_util_atan2 = $A0C0AE ; $C0AE: Calculate angle of ([$12], [$14]) offset
;;; Variable aliases
; Read by beam HDMA object, TODO purpose unknown
eye_vars_hdma_flag = enemies_ai_var2
; Read by beam HDMA object
eye_vars_angle = enemies_ai_var3
; Used to add a delay during the opening/closing AI states
eye_vars_anim_delay = enemies_ai_var4
; Pointer to current AI state handler, called from pre-instruction
eye_vars_state = enemies_ai_var5
org $A88F8C
;;; $8F8C: Palette - enemy $E6BF (morph ball eye) ;;;
skip $20 ; Let SMART manage this
;dw 3800, 72B2, 71C7, 2461, 1840, 7A8E, 660B, 4D03, 4900, 7FE0, 7E80, 44E0, 2C20, 0000, 0000, 0000
;;; $8FAC: Instruction list - ;;;
{
instrs_eye_directions:
dw $000A,$9210
dw $000A,$9210
dw $000A,$9217
dw $000A,$921E
dw $000A,$9225
dw $000A,$922C
dw $000A,$9233
dw $000A,$923A
dw $000A,$9209
dw $000A,$9202
dw $000A,$91FB
dw $000A,$91F4
dw $000A,$91ED
dw $000A,$91E6
dw $000A,$91DF
dw $000A,$91DF
dw $80ED,instrs_eye_directions ; Go to
}
;;; $8FF0: Instruction list - ;;;
{
instrs_eye_left_closing:
dw $0008,$9257
dw $0030,$91F4
dw $0005,$9257
.closed:
dw $0030,$9241
dw $812F ; Sleep
}
;;; $9002: Instruction list - ;;;
{
instrs_eye_right_closing:
dw $0008,$9283
dw $0030,$9225
dw $0005,$9283
.closed:
dw $0030,$926D
dw $812F ; Sleep
}
;;; $9014: Instruction list - ;;;
{
instrs_eye_left_opening:
dw $0020,$9241
dw $0005,$9257
dw $0030,$91F4
dw $0008,$9257
dw $812F ; Sleep
}
;;; $9026: Instruction list - ;;;
{
instrs_eye_right_opening:
dw $0020,$926D
dw $0005,$9283
dw $0030,$9225
dw $0008,$9283
dw $812F ; Sleep
}
;;; $9038: Instruction list - ;;;
{
instrs_mount_right:
dw $0001,$9299
dw $812F ; Sleep
}
;;; $903E: Instruction list - ;;;
{
instrs_mount_down:
dw $0001,$92A5
dw $812F ; Sleep
}
;;; $9044: Instruction list - ;;;
{
instrs_mount_left:
dw $0001,$92B1
dw $812F ; Sleep
}
;;; $904A: Instruction list - ;;;
{
instrs_mount_up:
dw $0001,$92BD
dw $812F ; Sleep
}
;;; $9050: Morph ball eye constants ;;;
{
; How close Samus needs to be for the eye to wake up and start tracking her
eye_acquire_xradius:
dw $80
; How close Samus needs to be for the eye to continue tracking once it's already awake
eye_track_xradius:
dw $B0
eye_acquire_yradius:
dw $80
eye_track_yradius:
dw $80
}
;;; $9058: Initialisation AI - enemy $E6BF (morph ball eye) ;;;
{
eye_ai_init:
LDX enemy_index
; Set "Process Instructions" flag
LDA enemies_flags1,x
ORA #$2000
STA enemies_flags1,x
LDA #!enemy_empty_spritemap
STA enemies_spritemap,x
LDA.w #1
STA enemies_instruction_timer,x
STZ enemies_timer,x
; If (param2 & 8000h != 0) this is the mount part, initialize mount graphics
LDA enemies_parameter2,x
BMI .init_mount
; Otherwise, this is the eye part, continue with eye initialization
LDA #eye_state_waiting
STA eye_vars_state,x
; param1 & 0x0001 determines eye facing: 0 = Left, 1 = Right
LDA enemies_parameter1,x
BIT #$0001
BEQ .facing_left
; Right-facing
LDA #instrs_eye_right_closing_closed
STA enemies_instruction_list,x
BRA .return
.facing_left:
; Left-facing
LDA #instrs_eye_left_closing_closed
STA enemies_instruction_list,x
BRA .return
.init_mount:
; Depending on the mount orientation, offset origin and set sprite accordingly
; Y = table offset for configured orientation ((param2 & Fh) * 2)
AND #$000F
ASL
TAY
; Offset X position
LDA enemies_pos_x,x
CLC
ADC .x_offsets_tbl,y
STA enemies_pos_x,x
; Offset Y position
LDA enemies_pos_y,x
CLC
ADC .y_offsets_tbl,y
STA enemies_pos_y,x
; Mount uses a null AI state
LDA #rtl_91DC
STA eye_vars_state,x
; Set instruction list with correct sprites for this orientation
LDA .instruction_lists_tbl,y
STA enemies_instruction_list,x
; Fill $7E:9100..92FF ("some HDMA RAM"?) with 00FFh
LDX #$01FE
- LDA #$00FF
STA ram_hdma_buffer,x
DEX #2
BPL -
.return:
RTL
; Left, Right, Up, Down
.x_offsets_tbl:
dw -8, 8, 0, 0
.y_offsets_tbl:
dw 0, 0, -8, 8
.instruction_lists_tbl:
dw instrs_mount_left, instrs_mount_right, instrs_mount_up, instrs_mount_down
}
;;; $90E2: Main AI - enemy $E6BF (morph ball eye) ;;;
{
eye_ai_main:
LDX enemy_index
LDA samus_collected_items
BIT #$0004 ; Morph ball
BEQ .return
; If morph ball has been collected, run current AI state handler
JMP (eye_vars_state,x)
.return:
RTL
}
;;; $90F1: AI State: Eye closed, waiting for Samus to come nearby
{
eye_state_waiting:
; Check if Samus is near the vicinity of the enemy and return otherwise
LDA eye_acquire_yradius
JSL ai_util_isSamusWithinYRadius
TAY
BEQ .return
LDA eye_acquire_xradius
JSL ai_util_isSamusWithinXRadius
TAY
BEQ .return
LDA.w #32
STA eye_vars_anim_delay,x
LDA.w #1
STA enemies_instruction_timer,x
; Check facing direction (param1 & 0x0001), 0 = Left, 1 = Right
LDA enemies_parameter1,x
BIT #$0001
BEQ .facing_left
; Otherwise facing right
LDA #instrs_eye_right_opening
STA enemies_instruction_list,x
BRA .next_state
.facing_left:
LDA #instrs_eye_left_opening
STA enemies_instruction_list,x
.next_state:
LDA #eye_state_opening
STA eye_vars_state,x
.return:
RTL
}
;;; $912E: AI State: Eye spotted Samus and is playing opening animation
{
eye_state_opening:
DEC eye_vars_anim_delay,x
BEQ .eye_opened
BPL .return
.eye_opened:
; Queue sound 17h, sound library 2, max queued sounds allowed = 6 (morph ball eye's ray)
LDA.w #$17
JSL snd_playLib2Sfx
JSL hdma_spawnEyeHdma
LDA #eye_state_tracking
STA eye_vars_state,x
; Calculate angle from enemy to Samus
LDA samus_pos_x
SEC : SBC enemies_pos_x,x
STA $12
LDA samus_pos_y
SEC : SBC enemies_pos_y,x
STA $14
JSL ai_util_atan2 ; Takes $12,$14 as params
STA eye_vars_angle,x
.return:
RTL
}
;;; $9160: AI State: Eye is open and tracking Samus' direction
{
eye_state_tracking:
; Check if Samus is still near the eye object
LDA eye_track_yradius
JSL ai_util_isSamusWithinYRadius
TAY
BEQ .samus_out_of_range
LDA eye_track_xradius
JSL ai_util_isSamusWithinXRadius
TAY
BNE .track_samus
.samus_out_of_range:
; Stop x-ray sound
; Queue sound 71h, sound library 2, max queued sounds allowed = 6 (silence)
LDA.w #$71
JSL snd_playLib2Sfx
STZ eye_vars_hdma_flag,x
LDA.w #32
STA eye_vars_anim_delay,x
; Play eye closing animation. The correct instruction list is set
; according to the eye direction. ((param1 & 1): 0=Left, 1=Right)
LDA enemies_parameter1,x
BIT #$0001
BEQ .facing_left
LDA #instrs_eye_right_closing
STA enemies_instruction_list,x
BRA .next_state
.facing_left:
LDA #instrs_eye_left_closing
STA enemies_instruction_list,x
.next_state:
LDA #eye_state_closing
STA eye_vars_state,x
BRA .return
.track_samus:
; Update beam angle by calculating new angle towards samus
LDA samus_pos_x
SEC : SBC enemies_pos_x,x
STA $12
LDA samus_pos_y
SEC : SBC enemies_pos_y,x
STA $14
JSL ai_util_atan2 ; Takes $12,$14 as params
STA eye_vars_angle,x
; Update eye sprite to look in the correct direction. Uses the angle to
; index into the instruction list containing each of the poses.
; angle / 16 (num. of poses) * 4 (bytes per instruction in the list)
AND #$00F0 : LSR #2
CLC : ADC #instrs_eye_directions
STA enemies_instruction_list,x
.return:
LDA #$0001
STA enemies_instruction_timer,x
RTL
}
;;; $91CE: AI State: Eye lost track of Samus and is playing its closing animation before going back to waiting state
{
eye_state_closing:
DEC eye_vars_anim_delay,x
BEQ .counter_expired
BPL .return
.counter_expired:
LDA #eye_state_waiting
STA eye_vars_state,x
.return:
RTL
}
;;; $91DC: RTL
{
rtl_91DC:
RTL
}
;;; $91DD: RTL (unused?)
{
rtl_91DD:
RTL
}
;;; $91DE: RTL (unused?)
{
rtl_91DE:
RTL
}
warnpc $A891DF
;;; $91DF: Morph ball eye spritemaps ;;;
{
macro sprmap_entry(xoff, yoff, tile)
dw <xoff> : db <yoff> : dw <tile>
endmacro
; Open eye looking towards various angles
eye_spritemaps:
.f0: dw 1 : %sprmap_entry($81F8, $F8, $2100)
.f1: dw 1 : %sprmap_entry($81F8, $F8, $2102)
.f2: dw 1 : %sprmap_entry($81F8, $F8, $2104)
.f3: dw 1 : %sprmap_entry($81F8, $F8, $2106)
.f4: dw 1 : %sprmap_entry($81F8, $F8, $2108)
.f5: dw 1 : %sprmap_entry($81F8, $F8, $210A)
.f6: dw 1 : %sprmap_entry($81F8, $F8, $210C)
.f7: dw 1 : %sprmap_entry($81F8, $F8, $6100)
.f8: dw 1 : %sprmap_entry($81F8, $F8, $6102)
.f9: dw 1 : %sprmap_entry($81F8, $F8, $6104)
.f10: dw 1 : %sprmap_entry($81F8, $F8, $6106)
.f11: dw 1 : %sprmap_entry($81F8, $F8, $6108)
.f12: dw 1 : %sprmap_entry($81F8, $F8, $610A)
.f13: dw 1 : %sprmap_entry($81F8, $F8, $610C)
; Eye opening left
.f14: dw 4
%sprmap_entry($01F8, $00, $A11F)
%sprmap_entry($01F8, $F8, $211F)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
.f15: dw 4
%sprmap_entry($01F8, $00, $A11E)
%sprmap_entry($01F8, $F8, $211E)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
; Eye opening right
.f16: dw 4
%sprmap_entry($0000, $00, $E11F)
%sprmap_entry($0000, $F8, $611F)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
.f17: dw 4
%sprmap_entry($0000, $00, $E11E)
%sprmap_entry($0000, $F8, $611E)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
; Mount in 4 directions
.f18: dw 2
%sprmap_entry($01FC, $00, $A10E)
%sprmap_entry($01FC, $F8, $210E)
.f19: dw 2
%sprmap_entry($0000, $FC, $610F)
%sprmap_entry($01F8, $FC, $210F)
.f20: dw 2
%sprmap_entry($01FC, $00, $E10E)
%sprmap_entry($01FC, $F8, $610E)
.f21: dw 2
%sprmap_entry($0000, $FC, $E10F)
%sprmap_entry($01F8, $FC, $A10F)
; Unused. Seem to be duplicates of 9241-9298
.f22: dw 4
%sprmap_entry($01F8, $00, $A11F)
%sprmap_entry($01F8, $F8, $211F)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
.f23: dw 4
%sprmap_entry($01F8, $00, $A11E)
%sprmap_entry($01F8, $F8, $211E)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
.f24: dw 4
%sprmap_entry($0000, $00, $E11F)
%sprmap_entry($0000, $F8, $611F)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
.f25: dw 4
%sprmap_entry($0000, $00, $E11E)
%sprmap_entry($0000, $F8, $611E)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
.f26: dw 4
%sprmap_entry($0000, $00, $E11F)
%sprmap_entry($0000, $F8, $611F)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
.f27: dw 4
%sprmap_entry($0000, $00, $E11E)
%sprmap_entry($0000, $F8, $611E)
%sprmap_entry($01F8, $00, $6117)
%sprmap_entry($01F8, $F8, $6107)
.f28: dw 4
%sprmap_entry($01F8, $00, $A11F)
%sprmap_entry($01F8, $F8, $211F)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
.f29: dw 4
%sprmap_entry($01F8, $00, $A11E)
%sprmap_entry($01F8, $F8, $211E)
%sprmap_entry($0000, $00, $2117)
%sprmap_entry($0000, $F8, $2107)
}
}
; End of eye code
warnpc $A89379
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment