diff --git a/src/actors/fireball.asm b/src/actors/fireball.asm index e753f863..4743b09e 100644 --- a/src/actors/fireball.asm +++ b/src/actors/fireball.asm @@ -36,6 +36,7 @@ .IMPORT Ram_ActorState1_byte_arr .IMPORT Ram_ActorState2_byte_arr .IMPORT Ram_ActorState3_byte_arr +.IMPORT Ram_ActorState4_byte_arr .IMPORT Ram_ActorType_eActor_arr .IMPORT Ram_DeviceTarget_byte_arr .IMPORT Ram_DeviceType_eDevice_arr @@ -115,6 +116,13 @@ _HandleCollision: bcc FuncA_Actor_RemoveProjFireballOrFireblast ; preserves X jsr FuncA_Actor_CenterHitsTerrain ; preserves X, returns C bcs FuncA_Actor_ExpireProjFireballOrFireblast ; preserves X +_UpdateAngle: + lda Ram_ActorState4_byte_arr, x ; angle delta + beq @done + add Ram_ActorState1_byte_arr, x ; current angle + sta Ram_ActorState1_byte_arr, x ; current angle + jmp Func_ReinitActorProjFireblastVelocity ; preserves X + @done: rts .ENDPROC diff --git a/src/actors/fireball.inc b/src/actors/fireball.inc index b0d2af9d..ba76bd55 100644 --- a/src/actors/fireball.inc +++ b/src/actors/fireball.inc @@ -28,6 +28,8 @@ ;;; can hit a mirror again. This is set to a nonzero value when the ;;; fireblast hits a mirror (so that it can't immediately hit the same ;;; mirror again), and decrements each frame when nonzero. +;;; * State4: For fireballs only, a delta to be added to the projectile angle +;;; each frame, in increments of tau/256. ;;; Tile IDs for drawing fireball/fireblast projectile actors. kTileIdObjFireballFirst = $c0 diff --git a/src/actors/ghost.asm b/src/actors/ghost.asm index d7361f06..c31f853a 100644 --- a/src/actors/ghost.asm +++ b/src/actors/ghost.asm @@ -24,6 +24,8 @@ .INCLUDE "orc.inc" .IMPORT Data_PowersOfTwo_u8_arr8 +.IMPORT FuncA_Actor_FaceTowardsAvatar +.IMPORT FuncA_Actor_SetPointInFrontOfActor .IMPORT FuncA_Objects_BobActorShapePosUpAndDown .IMPORT FuncA_Objects_Draw2x2Shape .IMPORT FuncA_Objects_Draw2x3TownsfolkShape @@ -32,13 +34,29 @@ .IMPORT FuncA_Objects_MoveShapeVert .IMPORT FuncA_Objects_SetShapePosToActorCenter .IMPORT Func_Cosine +.IMPORT Func_FindEmptyActorSlot +.IMPORT Func_GetAngleFromPointToAvatar +.IMPORT Func_GetRandomByte +.IMPORT Func_InitActorProjFireball .IMPORT Func_InitActorWithState1 +.IMPORT Func_MovePointUpByA .IMPORT Func_Noop +.IMPORT Func_SetActorCenterToPoint +.IMPORT Func_SetPointToActorCenter .IMPORT Func_SignedMult .IMPORT Func_Sine .IMPORT Ram_ActorFlags_bObj_arr .IMPORT Ram_ActorState1_byte_arr .IMPORT Ram_ActorState2_byte_arr +.IMPORT Ram_ActorState4_byte_arr + +;;;=========================================================================;;; + +;;; How long a mermaid ghost attack salve lasts, in frames. +kBadGhostMermaidAttackFrames = 100 + +;;; How long an orc ghost poses after attacking, in frames. +kBadGhostOrcAttackFrames = 70 ;;;=========================================================================;;; @@ -77,10 +95,10 @@ D_TABLE_HI table, _JumpTable_ptr_1_arr D_TABLE .enum, eBadGhost d_entry table, Absent, Func_Noop - d_entry table, Idle, Func_Noop + d_entry table, Idle, FuncA_Actor_FaceTowardsAvatar d_entry table, Disappearing, FuncA_Actor_TickBadGhost_Disappearing d_entry table, Reappearing, FuncA_Actor_TickBadGhost_Reappearing - d_entry table, Attacking, Func_Noop + d_entry table, Attacking, FuncA_Actor_TickBadGhostMermaid_Attacking D_END .ENDREPEAT .ENDPROC @@ -101,10 +119,10 @@ D_TABLE_HI table, _JumpTable_ptr_1_arr D_TABLE .enum, eBadGhost d_entry table, Absent, Func_Noop - d_entry table, Idle, Func_Noop + d_entry table, Idle, FuncA_Actor_FaceTowardsAvatar d_entry table, Disappearing, FuncA_Actor_TickBadGhost_Disappearing d_entry table, Reappearing, FuncA_Actor_TickBadGhost_Reappearing - d_entry table, Attacking, Func_Noop + d_entry table, Attacking, FuncA_Actor_TickBadGhostOrc_Attacking D_END .ENDREPEAT .ENDPROC @@ -119,7 +137,7 @@ lda Ram_ActorState2_byte_arr, x ; mode timer cmp #kBadGhostAppearFrames blt @done - ;; When the timer finishes, make the ghost absent (and clear its timer). + ;; When the timer finishes, make the ghost absent and clear its timer. lda #eBadGhost::Absent sta Ram_ActorState1_byte_arr, x ; current eBadGhost mode .assert eBadGhost::Absent = 0, error @@ -133,7 +151,7 @@ ;;; @param X The actor index. ;;; @preserve X .PROC FuncA_Actor_TickBadGhost_Reappearing - ;; Increment timer until it reaches zero. + ;; Decrement timer until it reaches zero. dec Ram_ActorState2_byte_arr, x ; mode timer bne @done ;; When the timer finishes, make the ghost idle. Its timer will already be @@ -144,6 +162,104 @@ rts .ENDPROC +;;; Performs per-frame updates for a mermaid ghost baddie actor that's in +;;; Attacking mode. +;;; @param X The actor index. +;;; @preserve X +.PROC FuncA_Actor_TickBadGhostMermaid_Attacking + jsr FuncA_Actor_FaceTowardsAvatar ; preserves X +_ShootProjectile: + ;; Shoot a fireball every 16 frames. + lda Ram_ActorState2_byte_arr, x ; mode timer + mod #16 + cmp #15 + bne @done + ;; Position the point in fromt of the mermaid ghost's hands. + lda #8 ; param: offset + jsr FuncA_Actor_SetPointInFrontOfActor ; preserves X + lda #4 ; param: offset + jsr Func_MovePointUpByA ; preserves X + ;; Shoot a fireball towards the player avatar. + stx T4 ; mermaid ghost actor index + jsr Func_FindEmptyActorSlot ; preserves T0+, returns C and X + bcs @break ; no room for any more projectiles + jsr Func_SetActorCenterToPoint ; preserves X and T0+ + jsr Func_GetAngleFromPointToAvatar ; preserves X and T4+, returns A + sta T0 ; center angle + ;; Randomize the fireball angle +/- 4 binary degrees. + jsr Func_GetRandomByte ; preserves T0+, returns A + mod #8 + sub #4 + add T0 ; center angle + jsr Func_InitActorProjFireball ; preserves T3+ + @break: + ldx T4 ; mermaid ghost actor index + @done: +_IncrementTimer: + ;; Increment timer until it reaches its end value. + inc Ram_ActorState2_byte_arr, x ; mode timer + lda Ram_ActorState2_byte_arr, x ; mode timer + cmp #kBadGhostMermaidAttackFrames + blt @done + ;; When the timer finishes, make the ghost idle and clear its timer. + lda #eBadGhost::Idle + sta Ram_ActorState1_byte_arr, x ; current eBadGhost mode + lda #0 + sta Ram_ActorState2_byte_arr, x ; mode timer + @done: + rts +.ENDPROC + +;;; Performs per-frame updates for a orc ghost baddie actor that's in Attacking +;;; mode. +;;; @param X The actor index. +;;; @preserve X +.PROC FuncA_Actor_TickBadGhostOrc_Attacking +_ShootProjectiles: + ;; If the timer is still at zero, fire a ring of projectiles. + lda Ram_ActorState2_byte_arr, x ; mode timer + bne @done + ;; Choose a random angle delta of -1 or 1. + jsr Func_GetRandomByte ; preserves X, returns A + mod #2 ; now A is 0 or 1 + mul #2 ; now A is 0 or 2 + tay ; now Y is 0 or 2 + dey ; now Y is -1 or 1 + sty T5 ; angle delta + ;; Fire a ring of projectiles. + jsr Func_SetPointToActorCenter ; preserves X + stx T4 ; orc ghost actor index + lda #10 ; starting angle + @loop: + sta T3 ; current angle + jsr Func_FindEmptyActorSlot ; preserves T0+, returns C and X + bcs @break ; no room for any more projectiles + jsr Func_SetActorCenterToPoint ; preserves X and T0+ + lda T3 ; param: angle + jsr Func_InitActorProjFireball ; preserves X and T3+ + lda T5 ; angle delta + sta Ram_ActorState4_byte_arr, x ; angle delta + lda T3 ; param: angle + add #43 ; tau/6 (approximately) + bcc @loop + @break: + ldx T4 ; orc ghost actor index + @done: +_IncrementTimer: + ;; Increment timer until it reaches its end value. + inc Ram_ActorState2_byte_arr, x ; mode timer + lda Ram_ActorState2_byte_arr, x ; mode timer + cmp #kBadGhostOrcAttackFrames + blt @done + ;; When the timer finishes, make the ghost idle and clear its timer. + lda #eBadGhost::Idle + sta Ram_ActorState1_byte_arr, x ; current eBadGhost mode + lda #0 + sta Ram_ActorState2_byte_arr, x ; mode timer + @done: + rts +.ENDPROC + ;;;=========================================================================;;; .SEGMENT "PRGA_Objects" diff --git a/src/actors/ghost.inc b/src/actors/ghost.inc index 6d8a3bc8..f67f7c1b 100644 --- a/src/actors/ghost.inc +++ b/src/actors/ghost.inc @@ -35,7 +35,7 @@ Idle ; visible, waiting for a new mode to be set Disappearing ; disappearing Reappearing ; reappearing - Attacking ; visible in attack pose, waiting for a new mode to be set + Attacking ; running attack cycle NUM_VALUES .ENDENUM diff --git a/src/rooms/boss_shadow.asm b/src/rooms/boss_shadow.asm index 522bc1a1..f51a9b48 100644 --- a/src/rooms/boss_shadow.asm +++ b/src/rooms/boss_shadow.asm @@ -58,6 +58,7 @@ .IMPORT FuncA_Room_TickBoss .IMPORT FuncA_Terrain_FadeInShortRoomWithLava .IMPORT Func_AckIrqAndLatchWindowFromParam4 +.IMPORT Func_DivMod .IMPORT Func_GetRandomByte .IMPORT Func_IsPointInPlatform .IMPORT Func_MachineEmitterReadReg @@ -76,6 +77,8 @@ .IMPORT Ram_PlatformType_ePlatform_arr .IMPORTZP Zp_Buffered_sIrq .IMPORTZP Zp_FrameCounter_u8 +.IMPORTZP Zp_PointX_i16 +.IMPORTZP Zp_PointY_i16 .IMPORTZP Zp_RoomScrollY_u8 .IMPORTZP Zp_RoomState @@ -528,7 +531,21 @@ _BossMode_SingleAttackPending: lda Zp_RoomState + sState::BossCooldown_u8 bne @done ;; Chose a random point for the ghost to appear at. - nop ; TODO set point + jsr Func_GetRandomByte ; returns A + ldy #10 ; param: divisor + jsr Func_DivMod ; returns remainder in A + mul #kBlockWidthPx + adc #kForcefieldMinPlatformLeft + kTileWidthPx ; carry is already clear + sta Zp_PointX_i16 + 0 + jsr Func_GetRandomByte ; returns A + ldy #7 ; param: divisor + jsr Func_DivMod ; returns remainder in A + mul #kBlockHeightPx + adc #kForcefieldMinPlatformTop + kTileHeightPx ; carry is already clear + sta Zp_PointY_i16 + 0 + lda #0 + sta Zp_PointX_i16 + 1 + sta Zp_PointY_i16 + 1 ;; Get the actor index for the ghost that should attack this time (and ;; toggle it for the next time), and also set the subboss mode for that ;; ghost. @@ -570,7 +587,7 @@ _BeginNextAttackWave: ;; If there are no more attack waves left in this group, perform a special ;; attack. lda Zp_RoomState + sState::AttackWavesRemaining_u8 - beq _BeginSpecialAttack + ;; TODO: beq _BeginSpecialAttack dec Zp_RoomState + sState::AttackWavesRemaining_u8 ;; If boss health is at half or below, perform double-attack waves. ;; Otherwise, perform single-attack waves. @@ -730,21 +747,16 @@ _BossMermaidMode_AttackAppearing: @done: rts _BossMermaidMode_AttackSpraying: - ;; When the cooldown expires, start dodging. - lda Zp_RoomState + sState::GhostCooldown_u8_arr + kGhostMermaidActorIndex + ;; Wait for the ghost to be idle, then start dodging. + lda Ram_ActorState1_byte_arr + kGhostMermaidActorIndex ; eBadGhost mode + cmp #eBadGhost::Idle beq _StartAttackDodging - ;; Shoot a fireball every 32 frames. - mod #32 - cmp #31 - bne @done - ;; TODO: Shoot a fireball at the player avatar. - @done: rts _StartAttackDodging: ;; TODO pick destination ;; TODO set velocity lda #eBossMermaidMode::AttackDodging - sta Zp_RoomState + sState::Current_eBossOrcMode + sta Zp_RoomState + sState::Current_eBossMermaidMode ;; TODO set cooldown rts _BossMermaidMode_AttackDodging: @@ -813,26 +825,28 @@ _BossOrcMode_AttackMoving: jsr Func_GetRandomByte ; returns N bmi _StartAttackMoving ;; Start attack pattern. - ;; TODO spawn projectiles lda #eBossOrcMode::AttackSpraying sta Zp_RoomState + sState::Current_eBossOrcMode - lda #120 - sta Zp_RoomState + sState::GhostCooldown_u8_arr + kGhostOrcActorIndex lda #eBadGhost::Attacking sta Ram_ActorState1_byte_arr + kGhostOrcActorIndex ; eBadGhost mode @done: rts -_BossOrcMode_Injured: _BossOrcMode_AttackSpraying: - ;; Wait for the cooldown to expire. + ;; Wait for the ghost to be idle, then disappear. + lda Ram_ActorState1_byte_arr + kGhostOrcActorIndex ; eBadGhost mode + cmp #eBadGhost::Idle + beq _Disappear +_Return: + rts +_BossOrcMode_Injured: + ;; Wait for the cooldown to expire, then disappear. lda Zp_RoomState + sState::GhostCooldown_u8_arr + kGhostOrcActorIndex - bne @done - ;; Disappear. + bne _Return +_Disappear: lda #eBossOrcMode::Disappearing sta Zp_RoomState + sState::Current_eBossOrcMode lda #eBadGhost::Disappearing sta Ram_ActorState1_byte_arr + kGhostOrcActorIndex ; eBadGhost mode - @done: rts _BossOrcMode_LavaAppearing: ;; Wait for the cooldown to expire.