diff --git a/constants/seasons/parts.s b/constants/seasons/parts.s index 882a0a9a6..006b249ac 100644 --- a/constants/seasons/parts.s +++ b/constants/seasons/parts.s @@ -94,8 +94,8 @@ .define PART_MOTHULA_PROJECTILE_2 $42 ;; -; created by Gleeok - projectiles? -.define PART_43 $43 +; projectiles created by Gleeok +.define PART_GLEEOK_FLAME $43 ;; ; created by medusa head diff --git a/object_code/seasons/enemies/gleeok.s b/object_code/seasons/enemies/gleeok.s index ea791c392..fe9eddae2 100644 --- a/object_code/seasons/enemies/gleeok.s +++ b/object_code/seasons/enemies/gleeok.s @@ -1,68 +1,94 @@ ; ================================================================================================== ; ENEMY_GLEEOK +; +; Variables (subid 1, main body): +; var30-var37: Contain high bytes of other body parts with subids 2-9 +; var38: Bitfield of which 3 bits are used: +; Bit 0: Set while one of the heads is performing one of the attacks 0-2 to block the other +; head from the same attack range +; Bit 1: Set whenever the left head is detached and floating through the room +; Bit 2: Set whenever the right head is detached and floating through the room +; +; Variables (subids 2 and 3, heads): +; var03: Attack pattern (0-5) +; relatedObj1: Points to the main body +; var30: Angle sequence index for movement in attack patterns 4 and 5 +; var31: Wait timer for next angle update in attack patterns 4 and 5 +; var39: Projectile target y for attack pattern 1 +; var3a: Projectile target x for attack pattern 1 +; +; Variables (subids 4 to 9, neck segments): +; relatedObj1: Points to the main body +; relatedObj2: Points to the head on the same side ; ================================================================================================== enemyCode06: jr z,@normalStatus - sub $03 + sub ENEMYSTATUS_NO_HEALTH ret c jr nz,@normalStatus + ; If main body has no health, consider the boss defeated ld e,Enemy.subid ld a,(de) dec a jp z,enemyBoss_dead - ld e,$a4 + ld e,Enemy.collisionType ld a,(de) or a jp z,enemyDie_uncounted_withoutItemDrop + ; Otherwise a head has no health, mark it as floating in main body variable ld e,Enemy.subid ld a,(de) cp $02 - ld b,$02 + ld b,$02 ; left head jr z,+ - ld b,$04 + ld b,$04 ; right head + - ld a,$38 + ld a,Object.var38 call objectGetRelatedObject1Var ld a,(hl) or b ld (hl),a + ; If this head is currently performing a fire attack and the other head is also attacking, + ; advance the other head to state $0c so it can pick a fire attack soon ld e,Enemy.state ld a,(de) cp $0b jr nz,+ - ld e,$83 + ld e,Enemy.var03 ld a,(de) cp $03 jr nc,+ - ld e,$82 + ld e,Enemy.subid ld a,(de) xor $01 - add $ae + add Enemy.var30-2 ld l,a ld h,(hl) - ld l,$84 + ld l,Enemy.state ld a,(hl) cp $0b jr nz,+ inc (hl) + + ; Put head in state $0e (floating across the room) ld h,d - ld l,$84 + ld l,Enemy.state ld (hl),$0e - ld l,$a4 + ; Reset health and make immune to attacks + ld l,Enemy.collisionType set 7,(hl) inc l - ld (hl),$04 - ld l,$a9 + ld (hl),ENEMYCOLLISION_PODOBOO ; [enemyCollisionMode] + ld l,Enemy.health ld (hl),$19 - ld l,$ac + ld l,Enemy.knockbackAngle ld a,(hl) - ld l,$89 + ld l,Enemy.angle ld (hl),a - ld l,$90 - ld (hl),$50 - ld l,$86 - ld (hl),$96 + ld l,Enemy.speed + ld (hl),SPEED_200 + ld l,Enemy.counter1 + ld (hl),150 xor a jp enemySetAnimation @@ -70,128 +96,139 @@ enemyCode06: call ecom_getSubidAndCpStateTo08 jr nc,+ rst_jumpTable - .dw @state0 - .dw @state1 - .dw @stateStub - .dw @stateStub - .dw @stateStub - .dw @stateStub - .dw @stateStub - .dw @stateStub + .dw gleeok_state_uninitialized + .dw gleeok_state_spawner + .dw gleeok_state_stub + .dw gleeok_state_stub + .dw gleeok_state_stub + .dw gleeok_state_stub + .dw gleeok_state_stub + .dw gleeok_state_stub + dec b ld a,b rst_jumpTable - .dw @subid1 - .dw @subid2 - .dw @subid3 - .dw @subid4 - .dw @subid5 - .dw @subid6 - .dw @subid7 - .dw @subid8 - .dw @subid9 - -@state0: + .dw gleeok_body + .dw gleeok_headLeft + .dw gleeok_headRight + .dw gleeok_neckTopLeft + .dw gleeok_neckTopRight + .dw gleeok_neckMiddleLeft + .dw gleeok_neckMiddleRight + .dw gleeok_neckBottomLeft + .dw gleeok_neckBottomRight + +; Initialize room or body part depending on subid +gleeok_state_uninitialized: ld a,b or a - jp z,+ + jp z,@initializeRoom call ecom_setSpeedAndState8AndVisible - jp func_6c6b -+ + jp gleeok_initializeBodyPart + +@initializeRoom: inc a - ld (de),a - ld a,$06 - ld b,$87 + ld (de),a ; [state] + ld a,ENEMY_GLEEOK + ld b,PALH_SEASONS_87 call enemyBoss_initializeRoom -@state1: - ld b,$09 +; Spawn the 9 body parts with references between main body and other parts, then delete self +gleeok_state_spawner: + ld b,9 call checkBEnemySlotsAvailable ret nz ld b,ENEMY_GLEEOK call ecom_spawnUncountedEnemyWithSubid01 - ld l,$80 + ld l,Enemy.enabled ld e,l ld a,(de) ld (hl),a - ld l,$b0 + ld l,Enemy.var30 ld c,h - ld e,$08 + ; Spawn 8 enemy objects (2 heads and 6 neck segments) + ld e,8 - push hl call ecom_spawnUncountedEnemyWithSubid01 ld a,$0a sub e - ld (hl),a - ld l,$96 - ld a,$80 + ld (hl),a ; [subid] + ; Make relatedObj1 point to main body + ld l,Enemy.relatedObj1 + ld a,Enemy.start ldi (hl),a ld (hl),c ld a,h pop hl - ldi (hl),a + ldi (hl),a ; [var30] through [var37] dec e jr nz,- jp enemyDelete -@stateStub: +gleeok_state_stub: ret -@subid1: +; Main body +gleeok_body: ld a,(de) sub $08 rst_jumpTable - .dw @@state8 - .dw @@state9 - .dw @@stateA - .dw @@stateB - .dw @@stateC - .dw @@stateD - .dw @@stateE - .dw @@stateF - .dw @@stateG - -@@state8: + .dw gleeok_body_state8 + .dw gleeok_body_state9 + .dw gleeok_body_stateA + .dw gleeok_body_stateB + .dw gleeok_body_stateC + .dw gleeok_body_stateD + .dw gleeok_body_stateE + .dw gleeok_body_stateF + .dw gleeok_body_state10 + +; Start playing music once the shutter door is closed +gleeok_body_state8: ld a,(wcc93) or a ret nz ld h,d ld l,e - inc (hl) - ld a,$2e + inc (hl) ; [state] + ld a,MUS_BOSS ld (wActiveMusic),a jp playSound - -@@state9: - ld e,$b8 + +; Wait until both heads are floating +gleeok_body_state9: + ld e,Enemy.var38 ld a,(de) bit 1,a - jr z,@@animate + jr z,gleeok_body_animate bit 2,a - jr z,@@animate + jr z,gleeok_body_animate + ; Both heads are floating, prepare second phase ld h,d - ld l,$84 + ld l,Enemy.state inc (hl) - ld l,$87 - ld (hl),$3c - ld e,$b0 + ld l,Enemy.counter2 + ld (hl),60 + ; Set health of heads to 0 and disable their collision + ld e,Enemy.var30 ld a,(de) ld h,a - ld l,$a9 + ld l,Enemy.health xor a - ld (hl),a - ld l,$a4 + ld (hl),a ; left head + ld l,Enemy.collisionType ld (hl),a inc e - ld a,(de) + ld a,(de) ; [var31] ld h,a xor a - ld (hl),a - ld l,$a9 - ld (hl),a - ld hl,$ce16 - xor a + ld (hl),a ; [collisionType] + ld l,Enemy.health + ld (hl),a ; right head + ; Make floor under main body no longer solid + ld hl,wRoomCollisions+$16 + xor a ; Unnecessary ldi (hl),a ldi (hl),a ld (hl),a @@ -199,249 +236,276 @@ enemyCode06: ldi (hl),a ldi (hl),a ld (hl),a - ld a,$67 + ld a,SND_BOSS_DEAD call playSound - ld a,$f0 + ld a,SNDCTRL_STOPMUSIC jp playSound - -@@stateA: + +; Flicker and transition to second phase +gleeok_body_stateA: call ecom_decCounter2 jp nz,ecom_flickerVisibility ld bc,$020c call enemyBoss_spawnShadow jp nz,ecom_flickerVisibility ld h,d - ld l,$84 + ld l,Enemy.state inc (hl) - ld l,$86 - ld (hl),$1e + ld l,Enemy.counter1 + ld (hl),30 ld a,$04 call enemySetAnimation - -@@stateB: + +; Start playing music again +gleeok_body_stateB: call ecom_decCounter1 jp nz,ecom_flickerVisibility - inc (hl) + inc (hl) ; [counter1] = 1 ld l,e - inc (hl) - ld l,$a4 + inc (hl) ; [state] + ld l,Enemy.collisionType set 7,(hl) - ld a,$2e + ld a,MUS_BOSS ld (wActiveMusic),a call playSound - ld e,$84 - -@@stateC: + ld e,Enemy.state + +; Start jump +gleeok_body_stateC: call ecom_decCounter1 jr nz,+ ld l,e - inc (hl) + inc (hl) ; [state] ld bc,$fdc0 call objectSetSpeedZ jp objectSetVisible81 + - ld a,(hl) - cp $0a + ld a,(hl) ; [counter1] + cp 10 ret c -@@animate: +gleeok_body_animate: jp enemyAnimate - -@@stateD: + +; Perform jump and shake the ground when landing +gleeok_body_stateD: ld c,$20 call objectUpdateSpeedZ_paramC ret nz - ld l,$84 + ld l,Enemy.state inc (hl) - ld l,$86 - ld (hl),$96 - ld a,$78 + ld l,Enemy.counter1 + ld (hl),150 + ld a,120 call setScreenShakeCounter call objectSetVisible82 - ld a,$81 + ld a,SND_STRONG_POUND jp playSound - -@@stateE: + +; Stun Link if he touches the ground within 15 frames after landing, then after waiting set the angle to face Link +gleeok_body_stateE: call ecom_decCounter1 - jr z,+ - ld a,(hl) - cp $87 - jr c,@@animate - ld a,($d00f) + jr z,@prepareRunTowardsLink + ld a,(hl) ; [counter1] + cp 135 + jr c,gleeok_body_animate + ld a,(w1Link.zh) rlca ret c - ld hl,$cc6a + ld hl,wLinkForceState ld a,$14 ldi (hl),a - ld (hl),$00 + ld (hl),$00 ; [wcc50] ret -+ + +@prepareRunTowardsLink: ld l,e - inc (hl) - ld l,$90 - ld (hl),$50 + inc (hl) ; [state] + ld l,Enemy.speed + ld (hl),SPEED_200 call ecom_updateAngleTowardTarget - jr @@animate - -@@stateF: - ld a,$01 + jr gleeok_body_animate + +; Run until a wall is encountered +gleeok_body_stateF: + ld a,$01 ; Holes count as walls call ecom_getSideviewAdjacentWallsBitset - jr nz,+ + jr nz,@reachedWall call objectApplySpeed - jr @@animate -+ - ld a,$28 + jr gleeok_body_animate + +@reachedWall: + ld a,40 call setScreenShakeCounter ld h,d - ld l,$84 + ld l,Enemy.state inc (hl) - ld l,$90 - ld (hl),$14 - ld l,$89 + ld l,Enemy.speed + ld (hl),SPEED_80 + ; Bounce in opposite direction of running + ld l,Enemy.angle ld a,(hl) xor $10 ld (hl),a ld bc,$fe80 call objectSetSpeedZ - jr @@animate - -@@stateG: + jr gleeok_body_animate + +; Perform bounce away from wall +gleeok_body_state10: call ecom_applyVelocityForSideviewEnemyNoHoles ld c,$20 call objectUpdateSpeedZ_paramC - jr nz,@@animate - ld l,$84 + jr nz,gleeok_body_animate + ld l,Enemy.state ld (hl),$0c - ld l,$86 - ld (hl),$3c - jr @@animate + ld l,Enemy.counter1 + ld (hl),60 + jr gleeok_body_animate -@subid2: +; Left head +gleeok_headLeft: ld a,(de) sub $08 rst_jumpTable - .dw @@state8 - .dw @@incStateWhenCounter1Is0 - .dw @@stateA - .dw @@stateB - .dw @@stateC - .dw @@stateD - .dw @@stateE - .dw @@stateF - .dw @@incStateWhenCounter1Is0 - .dw @@stateH - -@@state8: + .dw gleeok_headLeft_state8 + .dw gleeok_head_state9 + .dw gleeok_headLeft_stateA + .dw gleeok_head_stateB + .dw gleeok_head_stateC + .dw gleeok_head_stateD + .dw gleeok_head_stateE + .dw gleeok_head_stateF + .dw gleeok_head_state10 + .dw gleeok_head_state11 + +; Initialize head-specific properties +gleeok_headLeft_state8: ld h,d ld l,Enemy.angle ld (hl),$14 -@@incStateEnableCollisionsSetCounterAndSpeed: +@rightHeadEntry: ld l,e - inc (hl) + inc (hl) ; [state] ld l,Enemy.collisionType set 7,(hl) ld l,Enemy.counter1 - ld (hl),$3c + ld (hl),60 ld l,Enemy.speed ld (hl),SPEED_80 ret - -@@incStateWhenCounter1Is0: + +; Increment state when counter1 reaches 0 after decrementing +gleeok_head_state9: +gleeok_head_state10: call ecom_decCounter1 jp nz,objectApplySpeed ld l,e - inc (hl) + inc (hl) ; [state] ret - -@@stateA: + +; Pick the next attack pattern +gleeok_headLeft_stateA: ld b,$04 -@@func_6905: - ld a,$38 +@rightHeadEntry: + ; If other head is currently floating, pick one of the attacks 0-2 + ld a,Object.var38 call objectGetRelatedObject1Var ld a,(hl) and b ld c,$03 - ld l,$b8 - jr nz,+ + ld l,Enemy.var38 + jr nz,@lowAttackPattern ; Confusing order, this branch could have been 2 operations earlier + ; If the other head is doing one of the attacks 0-2, pick an attack 3-5 bit 0,(hl) - jr nz,++ - ld e,$82 + jr nz,@highAttackPattern + ; If right head, pick an attack 0-2 + ld e,Enemy.subid ld a,(de) cp $03 - jr z,+ + jr z,@lowAttackPattern + ; If right head is in state $10 or $11 (just came back to main body after floating), pick an attack 0-2 ld b,h - ld l,$b1 + ld l,Enemy.var31 ld h,(hl) - ld l,$84 + ld l,Enemy.state ld a,(hl) cp $10 ld h,b - jr nc,+ + jr nc,@lowAttackPattern + ; If Link is in the left half of the room, pick an attack 0-2, otherwise 3-5 ldh a,(