/** * @file octorokBoss.c * @ingroup Enemies * * @brief Octorok boss enemy */ #include "enemy.h" #include "functions.h" #include "object.h" #include "projectile.h" #include "game.h" enum OctorokRotation { ROTATION_CW, ROTATION_CCW, NO_ROTATION = 0xff }; typedef struct HelperStruct { u8 field_0x0; // [0,1,2,4] is later stored in this->field_0xf u8 tailCount; u8 field_0x2; // [0,1] u8 targetAngle; // relates to this->field_0x7a.HALF.HI u8 rotation; // [0,1,0xff] u8 phase4PrevAttackPattern; // [0-4], sets this->field_0x80.HALF.HI u8 fallingStonesTimer; u8 field_0x7; // some sort of counter that is only set when hit for the first time? Entity* mouthObject; Entity* tailObjects[5]; Entity* legObjects[4]; } HelperStruct; static_assert(sizeof(HelperStruct) == 0x30); /* this->field_0x7c.BYTES.byte0 Boss Phase 0: unfrozen 1: frozen 1 2: unfrozen 3: frozen 2 4: unfrozen -> death */ #define IS_FROZEN(this) ((this)->field_0x7c.BYTES.byte0 & 1) #define GET_BOSS_PHASE(this) ((this)->field_0x7c.BYTES.byte0) /* this->field_0x78.HALF.HI reused timer */ #define GET_TIMER(this) ((this)->field_0x78.HALF.HI) /* this->field_0x78.HALF.LO turns until the next attack */ #define GET_ATTACK_WAIT_TURNS(this) ((this)->field_0x78.HALF.LO) /* this->field_0x7a.HALF.HI angle of legs */ #define GET_ANGLE(this) ((this)->field_0x7a.HWORD) #define GET_ANGLE_HI(this) ((this)->field_0x7a.HALF.HI) /* this->field_0x82.HWORD angular speed */ #define GET_ANGULAR_VEL(this) ((this)->field_0x82.HWORD) enum OctorokBossPart { WHOLE, LEG_BR, LEG_FR, LEG_FL, LEG_BL, MOUTH, TAIL_END, TAIL }; enum OctorokBossAction { INIT, // 0 ACTION1, // 1 HIT, // 2 INTRO, // 3 BURNING, // 4 }; enum OctorokBossAttack { ATTACK_SPITROCK, // 0 ATTACK_VACUUM, // 1 ATTACK_SMOKE, // 2 ATTACK_FREEZE, // 3 NO_ATTACK, // 4 END_OF_ATTACK_PATTERN // 5 }; enum OctorokBossAction1SubAction { ACTION1_AIMTOWARDSPLAYER, // Moving around with step sounds ACTION1_WAITFORTURN, // Also step sounds ACTION1_SUBACTION2, // Step sounds, some kind of attack that is started in OctorokBoss_StartRegularAttack? ACTION1_WAITFORATTACK, // Wait for GET_TIMER(), then OctorokBoss_SetWaitTurnsForNextAttack ACTION1_ATTACK, // Attack }; /* this->field_0x7c.BYTES.byte1 currentAttack */ #define GET_CURRENT_ATTACK(this) this->field_0x7c.BYTES.byte1 /* this->field_0x7c.BYTES.byte2 nextAttackIndex */ #define GET_NEXT_ATTACK_INDEX(this) this->field_0x7c.BYTES.byte2 /* for TAIL_END object: this->field_0x7c.BYTES.byte1 tailRadius */ #define GET_TAIL_RADIUS(this) this->field_0x7c.BYTES.byte1 #define GET_HELPER(this) (*(HelperStruct**)&this->cutsceneBeh) // Which attack pattern is currently used in phase 4 #define GET_PHASE4_ATTACK_PATTERN(this) this->field_0x80.HALF.HI extern void (*const OctorokBoss_Functions[])(Entity*); extern void (*const OctorokBoss_Hit_SubActions[])(Entity*); extern const u8 OctorokBoss_HealthPerPhase[]; extern void (*const OctorokBoss_Actions[])(Entity*); extern const u8 gUnk_080CF08C[]; extern void (*const OctorokBoss_Intro_SubActions[])(Entity*); extern void (*const OctorokBoss_Action1_SubActions[])(Entity*); extern const u8 OctorokBoss_LegAngleOffset[]; extern const u8 OctorokBoss_LegAngleOffset2[]; extern void (*const OctorokBoss_Action1_Attack_Type2s[])(Entity*); extern void (*const OctorokBoss_AttackFunctions[])(Entity*); extern void (*const OctorokBoss_Burning_SubActions[])(Entity*); extern const u8 OctorokBoss_AttackTimerWeights[]; extern const u8 OctorokBoss_AttackTimerValues[]; extern const u8* const OctorokBoss_Phase4AttackPatterns[]; extern const u8 OctorokBoss_TurnTimeWeights[]; extern const u8 OctorokBoss_TurnTimeValues[]; extern const u8 OctorokBoss_WaitForAttackTurnsWeights[]; extern const u8 OctorokBoss_WaitForAttackTurnsValues[]; extern const u8 OctorokBoss_NormalAttackPatterns[]; extern const u8 OctorokBoss_FrozenAttackPatterns[]; void OctorokBoss_Hit(Entity*); void OctorokBoss_Action1(Entity*); void OctorokBoss_Burning_SubAction1(Entity*); void sub_080368D8(Entity*); void sub_08036914(Entity*, s32, s32); void sub_08036998(Entity*); void sub_080369D0(Entity*, s32, s32); void sub_08036AF0(Entity*, s32, s32); void OctorokBoss_SetAttackTimer(Entity*); void OctorokBoss_ResetToSubAction0(Entity*); void OctorokBoss_WaitAnotherTurn(Entity*); void OctorokBoss_SetWaitTurnsForNextAttack(Entity*); void OctorokBoss_StartRegularAttack(Entity*); void OctorokBoss_ChangePalette(Entity*, u32); void sub_08036F60(Entity*); void OctorokBoss_StepSound(Entity*, u32); void sub_08036FE4(Entity*); void OctorokBoss(Entity* this) { OctorokBoss_Functions[GetNextFunction(this)](this); } void OctorokBoss_Death(Entity* this) { if (this->type == WHOLE) { this->action = HIT; this->subAction = 0; this->knockbackDuration = 0; this->health = 1; sub_080368D8(this); OctorokBoss_Hit(this); } else { DeleteThisEntity(); } } /* Hit SubActions 0: Start 1-3: 4- */ void OctorokBoss_Hit(Entity* this) { if (GET_BOSS_PHASE(this) == 0) { if (this->subAction != 3) { gRoomControls.camera_target = GET_HELPER(this)->tailObjects[0]; GET_HELPER(this)->field_0x7 = 0x5a; sub_08078B48(); } } else { if (GET_HELPER(this)->field_0x7 != 0) { GET_HELPER(this)->field_0x7--; sub_08078B48(); } } OctorokBoss_Hit_SubActions[this->subAction](this); if (this->subAction > 3) { sub_08078B48(); } sub_0800445C(this); sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); } void OctorokBoss_Hit_SubAction0(Entity* this) { this->field_0x76.HWORD = 0xa0; this->field_0x74.HWORD = 0xa0; GET_HELPER(this)->fallingStonesTimer = 0; if (GET_BOSS_PHASE(this) == 4) { this->subAction = 4; gUnk_02034490.unk0 = 1; sub_08078B48(); SoundReq(SFX_BOSS_DIE); } else { if (IS_FROZEN(this) == FALSE) { this->type2 = 0; GET_TIMER(this) = 0x3c; } else { if (GET_HELPER(this)->tailCount > 3) { GET_HELPER(this)->tailCount--; } GET_HELPER(this)->tailObjects[0]->field_0x7c.BYTES.byte1 = 0; GET_TIMER(this) = 0x78; } this->subAction = 1; } } void OctorokBoss_Hit_SubAction1(Entity* this) { bool32 frozen = IS_FROZEN(this); u16 diffX; u16 diffY; if (frozen == 0) { if (GET_HELPER(this)->tailObjects[0]->field_0x7c.BYTES.byte1 != 0) { GET_HELPER(this)->tailObjects[0]->field_0x7c.BYTES.byte1--; } // Move to the center of the screen before freezing diffX = 0x108 + gRoomControls.origin_x - this->x.HALF.HI + 0x4; diffY = gRoomControls.origin_y - this->y.HALF.HI + 0x8c; if (diffX > 8 || diffY > 8) { GET_HELPER(this)->field_0x2 = 1; #if defined(JP) || defined(DEMO_JP) || defined(EU) this->direction = ((s32)sub_080045DA((((gRoomControls.origin_x + 0x108) << 0x10) - this->x.WORD), (((gRoomControls.origin_y + 0x88) << 0x10) - this->y.WORD))) >> 3; #else this->direction = ((s32)sub_080045DA(gRoomControls.origin_x + 0x108 - this->x.HALF.HI, gRoomControls.origin_y + 0x88 - this->y.HALF.HI)) >> 3; #endif this->speed = 0x100; ProcessMovement0(this); } else { // Freeze if (this->type2 == 0) { CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 5, 0); CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 5, 1); CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 5, 2); CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 5, 3); CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 5, 4); this->type2 = 1; } GET_HELPER(this)->field_0x2 = frozen; GET_TIMER(this)--; } } else { u32 i; for (i = GET_HELPER(this)->tailCount - 1; i != 0; i--) { Entity* tail = GET_HELPER(this)->tailObjects[i - 1]; tail->spriteSettings.draw |= 1; } if ((gRoomTransition.frameCount & 2) != 0) { CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 6, 0); } GET_TIMER(this)--; } if (GET_TIMER(this) == 0) { this->subAction = 2; GET_BOSS_PHASE(this)++; sub_080AE068(this); if (IS_FROZEN(this) == FALSE) { this->hitType = 0x5f; LoadFixedGFX(this, 0x108); ChangeObjPalette(this, 0xef); OctorokBoss_ChangePalette(this, 0xef); InitAnimationForceUpdate(GET_HELPER(this)->tailObjects[0], 1); } else { this->hitType = 0x61; LoadFixedGFX(this, 0x109); ChangeObjPalette(this, 0xf0); OctorokBoss_ChangePalette(this, 0xf3); InitAnimationForceUpdate(GET_HELPER(this)->tailObjects[0], 2); } CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 8, 0); } } void OctorokBoss_Hit_SubAction2(Entity* this) { if (GET_HELPER(this)->tailObjects[0]->field_0x7c.BYTES.byte1 != 0x80) { GET_HELPER(this)->tailObjects[0]->field_0x7c.BYTES.byte1++; } else { this->subAction = 3; GET_TIMER(this) = 0x96; gRoomControls.camera_target = &gPlayerEntity; } } void OctorokBoss_Hit_SubAction3(Entity* this) { if (GET_TIMER(this)-- == 0) { this->health = OctorokBoss_HealthPerPhase[GET_BOSS_PHASE(this)]; COLLISION_ON(this); this->action = ACTION1; this->subAction = ACTION1_AIMTOWARDSPLAYER; GET_NEXT_ATTACK_INDEX(this) = 0; OctorokBoss_SetWaitTurnsForNextAttack(this); } } void OctorokBoss_Hit_SubAction4(Entity* this) { Entity* object; this->subAction = 5; object = CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 9, 0); if (object != NULL) { gRoomControls.camera_target = object; } } void OctorokBoss_Hit_SubAction5(Entity* this) { this->subAction = 6; GET_TIMER(this) = 0x78; this->field_0x80.HALF.LO = 0; this->field_0x82.HALF.LO = 0; } // Wildly rotating with explosion fx void OctorokBoss_Hit_SubAction6(Entity* this) { s16 tmp; GET_ANGLE_HI(this) -= 4; this->field_0x80.HALF.HI += 8; if ((this->field_0x80.HALF.LO & 0x80) != 0) { this->field_0x80.HALF.LO -= (Random() & 3); if ((this->field_0x80.HALF.LO & 0x80) == 0) { this->field_0x80.HALF.LO = ((this->field_0x80.HALF.LO & 0x80) << 0x18) >> 0x18; } } else { this->field_0x80.HALF.LO += (Random() & 3); if ((this->field_0x80.HALF.LO) >= 0x19) { this->field_0x80.HALF.LO |= 0x80; } } tmp = FixedMul(gSineTable[this->field_0x80.HALF.HI], (this->field_0x80.HALF.LO & 0x7f) << 8); tmp = FixedDiv(tmp, 0x100); this->spriteOffsetX = tmp >> 8; tmp = FixedMul(gSineTable[this->field_0x80.HALF.HI + 0x40], (this->field_0x80.HALF.LO & 0x7f) * 0x100); tmp = FixedDiv(tmp, 0x100); this->spriteOffsetY = -((tmp << 0x10) >> 8) >> 0x10; if (GET_TIMER(this) == 0) { if ((gRoomTransition.frameCount & 0xfU) == 0) { // Explosion in the center CreateFx(this, FX_GIANT_EXPLOSION3, 0); // Explosion at the front right leg CreateFx(GET_HELPER(this)->legObjects[0], FX_GIANT_EXPLOSION3, 0); } if (++this->field_0x82.HALF.LO == 0x79) { GET_HELPER(this)->mouthObject->health = 1; SoundReq(SFX_BOSS_DIE); // Kill this boss GenericDeath(this); } } else { GET_TIMER(this)--; } } void OctorokBoss_OnTick(Entity* this) { OctorokBoss_Actions[this->action](this); this->spriteRendering.b3 = 3; } ASM_FUNC("asm/non_matching/octorokBoss/OctorokBoss_Init.inc", void OctorokBoss_Init(Entity* this)) /*{ // TODO where to use GET_HELPER(this) and where to use helper? u32 leg; u32 nextLeg; u32 tail; u32 nextTail; HelperStruct* helper; this->action = ACTION1; this->spriteSettings.draw = 3; switch (this->type) { case WHOLE: this->spritePriority.b0 = 4; GET_BOSS_PHASE(this) = 0; this->actionDelay = 1; helper = (HelperStruct*)zMalloc(sizeof(HelperStruct)); GET_HELPER(this) = helper; if (helper == NULL) { // Kill this boss GenericDeath(this); return; } this->myHeap = (u32*)helper; GET_HELPER(this)->fallingStonesTimer = 0; helper->field_0x0 = 2; helper->field_0x2 = 0; helper->tailCount = 5; this->spriteRendering.b0 = 3; this->field_0x6c.HALF.HI |= 1; this->field_0x76.HWORD = 0xa0; this->field_0x74.HWORD = 0xa0; GET_ANGLE(this) = 0; // Create legs leg = 0; while (leg < 4) { this->child = CreateEnemy(OCTOROK_BOSS, leg + 1); if (this->child != NULL) { CopyPosition(this, this->child); this->child->parent = this; GET_HELPER(this->child) = helper; helper->legObjects[leg] = this->child; } leg = leg + 1; } // Create mouth this->child = CreateEnemy(OCTOROK_BOSS, MOUTH); if (this->child != NULL) { CopyPosition(this, this->child); this->child->parent = this; GET_HELPER(this->child) = GET_HELPER(this); } // Create tail end this->child = CreateEnemy(OCTOROK_BOSS, TAIL_END); if (this->child != NULL) { CopyPosition(this, this->child); this->child->parent = this; GET_HELPER(this->child) = helper; GET_HELPER(this)->tailObjects[0] = this->child; } // Create tails tail = 0; while (tail < 4) { this->child = CreateEnemy(OCTOROK_BOSS, TAIL); nextTail = tail + 1; if (this->child != NULL) { this->child->type2 = tail; CopyPosition(this, this->child); this->child->parent = this; GET_HELPER(this->child) = helper; helper->tailObjects[tail + 1] = this->child; } tail = nextTail; } this->action = INTRO; this->subAction = 0; GET_TIMER(this) = 0x3c; gPlayerEntity.spriteSettings.draw = 0; gPlayerEntity.x.HALF.HI = this->x.HALF.HI; gPlayerEntity.y.HALF.HI = this->y.HALF.HI - 0xa0; gRoomControls.camera_target = this; break; case LEG_BR: case LEG_FR: case LEG_FL: case LEG_BL: this->actionDelay = 0x10; GET_TIMER(this) = 0; if ((this->type & 2) == 0) { this->field_0xf = 2; } else { this->field_0xf = 0xfe; } this->field_0x74.HWORD = 0x100; if ((this->type & 1) == 0) { this->field_0x76.HWORD = 0xff00; } else { this->field_0x76.HWORD = 0x100; } break; case MOUTH: this->field_0x76.HWORD = 0x100; this->field_0x74.HWORD = 0x100; GET_TIMER(this) = 0x1c; GET_HELPER(this)->mouthObject = this; break; case TAIL_END: this->field_0x76.HWORD = 0x100; this->field_0x74.HWORD = 0x100; this->spritePriority.b0 = 0; GET_TIMER(this) = 0; this->actionDelay = 0x10; this->field_0xf = 1; GET_TAIL_RADIUS(this) = 0x80; break; } if (this->type != TAIL_END) { InitializeAnimation(this, gUnk_080CF08C[this->type * 4]); } else { InitAnimationForceUpdate(this, gUnk_080CF08C[this->type * 4]); } OctorokBoss_Action1(this); } */ void OctorokBoss_Intro(Entity* this) { sub_08078B48(); gUnk_02034490.unk0 = 1; sub_08036F60(this); OctorokBoss_Intro_SubActions[this->subAction](this); sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); } void OctorokBoss_Intro_SubAction0(Entity* this) { // Wait until the camera is on the Octorok if (GET_TIMER(this)-- == 0) { this->subAction = 1; GET_ANGULAR_VEL(this) = 0x100; GET_HELPER(this)->field_0x0 = 2; } } void OctorokBoss_Intro_SubAction1(Entity* this) { // Rotate Octorok to player if (GET_ANGLE_HI(this) == 0x80) { GET_TIMER(this) = 0x3c; this->subAction = 2; GET_HELPER(this)->field_0x0 = 0; // Octorok scream SoundReq(SFX_159); } else { GET_ANGLE(this) += GET_ANGULAR_VEL(this); } OctorokBoss_StepSound(this, 0xf); } void OctorokBoss_Intro_SubAction2(Entity* this) { // Wait for scream end if (GET_TIMER(this)-- == 0) { this->subAction = 3; GET_TIMER(this) = 0x3c; gPlayerEntity.spriteSettings.draw |= 1; gRoomControls.camera_target = &gPlayerEntity; gRoomControls.unk5 = 1; } } void OctorokBoss_Intro_SubAction3(Entity* this) { // Move the camera to the player if (GET_TIMER(this)-- == 0) { // Move the player inside the arena gPlayerEntity.direction = 0x10; gPlayerEntity.animationState = 4; sub_08078AC0(0x1e, 0, 0); GET_TIMER(this) = 0x3c; this->subAction = 4; } } void OctorokBoss_Intro_SubAction4(Entity* this) { if (GET_TIMER(this)-- == 0) { this->subAction = 5; GET_TIMER(this) = 0x2d; // Make the player look towards the exit gPlayerEntity.animationState = 0; } else { // Spawn exclamation bubble at a certain time if (GET_TIMER(this) == 0x1e) { CreateSpeechBubbleExclamationMark(&gPlayerEntity, 0xc, -0x18); } } } void OctorokBoss_Intro_SubAction5(Entity* this) { if (gPlayerEntity.animationState == 4) { if (GET_TIMER(this)++ > 0x1e) { // Play boss theme, enable control and switch to main action this->action = ACTION1; this->subAction = 0; gRoomControls.unk5 = gPlayerEntity.animationState; OctorokBoss_SetAttackTimer(this); gUnk_02034490.unk0 = 0; SoundReq(BGM_BOSS_THEME); } } else { if (GET_TIMER(this)-- == 0) { // Player looks back towards Octorok gPlayerEntity.animationState = 4; } } } void OctorokBoss_Action1(Entity* this) { Entity* object; u32 radius; u8 angle; if (this->type != WHOLE) { this->iframes = this->parent->iframes; } switch (this->type) { case LEG_BR: case LEG_FR: case LEG_FL: case LEG_BL: if ((this->parent->field_0x6c.HALF.HI & 2) != 0) { DeleteThisEntity(); } if (GET_HELPER(this)->mouthObject->health == 1) { if ((s16)this->field_0x76.HWORD < 0) { this->field_0x76.HWORD -= 4; } else { this->field_0x76.HWORD += 4; } if ((s16)this->field_0x74.HWORD < 0) { this->field_0x74.HWORD -= 4; } else { this->field_0x74.HWORD += 4; } } SortEntityBelow(this->parent, this); if (((GET_HELPER(this)->field_0x2 != 0) || (this->parent->action == INTRO)) || (1 < (u8)(this->parent->subAction - 3))) { if ((s8)this->field_0xf < 0) { this->field_0xf = -GET_HELPER(this)->field_0x0; } else { this->field_0xf = GET_HELPER(this)->field_0x0; } sub_08036998(this); } radius = 0x10000 / this->parent->field_0x76.HWORD; radius = radius << 0xd >> 0x8; radius = radius - 0x2000; if (GET_HELPER(this)->mouthObject->health == 1) { radius = radius + 0x2200; } else { radius = (radius >> 1) + 0x2200; } angle = -(this->parent->field_0x7a.HALF.HI + OctorokBoss_LegAngleOffset[this->type - 1]); sub_08036914(this, angle, radius); GET_ANGLE_HI(this) = this->parent->field_0x7a.HALF.HI + OctorokBoss_LegAngleOffset2[this->type - 1] + GET_TIMER(this); sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); break; case TAIL: if (GET_HELPER(this)->mouthObject->health < 2) { DeleteThisEntity(); } if ((GET_HELPER(this)->tailCount - 2) < this->type2) { DeleteThisEntity(); } SortEntityAbove(this->parent, this); if (GET_HELPER(this)->tailCount - 2 == this->type2) { SortEntityAbove(this->parent, this); radius = 0x10000 / this->parent->field_0x74.HWORD; radius = radius << 0xd >> 0x8; angle = -this->parent->field_0x7a.HALF.HI; sub_08036914(this, angle, radius); GET_ANGLE_HI(this) = -this->parent->field_0x7a.HALF.HI; } if (IS_FROZEN(this->parent) == 0) { this->spriteSettings.draw |= 1; } break; case TAIL_END: if (GET_HELPER(this)->mouthObject->health < 2) { DeleteThisEntity(); } UpdateAnimationSingleFrame(this); if (IS_FROZEN(this->parent)) { sub_08036AF0(this, GET_TAIL_RADIUS(this), 0x10); if ((this->bitfield & 0x7f) == 7) { COLLISION_OFF(this); object = CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 0, 0); this->child = object; if (object != NULL) { object->parent = this->parent; GET_HELPER(this->child) = GET_HELPER(this); } } } else { COLLISION_ON(this); this->spriteSettings.draw |= 1; sub_08036998(this); sub_080369D0(this, GET_TAIL_RADIUS(this), 4); } this->bitfield = 0; sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, -GET_ANGLE(this) ^ 0x8000); break; case MOUTH: if (this->health == 1) { this->health = 0; } else { SortEntityBelow(this->parent, this); if ((this->parent->subAction != 4) && (this->health != 1)) { if (GET_TIMER(this) > 0x1c) { GET_TIMER(this)--; } if (this->field_0x76.HWORD > 0x100) { this->field_0x76.HWORD--; } else { this->field_0x76.HWORD = 0x100; } } radius = 0x10000 / this->parent->field_0x74.HWORD; radius = radius * (GET_TIMER(this) << 8) >> 8; angle = -(this->parent->field_0x7a.HALF.HI + 0x80); sub_08036914(this, angle, radius); GET_ANGLE_HI(this) = this->parent->field_0x7a.HALF.HI; sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); sub_0800445C(this); } break; case WHOLE: sub_0800445C(this); sub_08036F60(this); if (GET_HELPER(this)->fallingStonesTimer != 0) { GET_HELPER(this)->fallingStonesTimer--; if ((gRoomTransition.frameCount & 3) == 0) { // Falling stones CreateProjectileWithParent(this, OCTOROK_BOSS_PROJECTILE, 3); } } OctorokBoss_Action1_SubActions[this->subAction](this); sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); break; } } void OctorokBoss_Action1_AimTowardsPlayer(Entity* this) { s32 tmp1; s32 tmp2; tmp1 = (u8)(sub_080045DA(gPlayerEntity.x.WORD - this->x.WORD, gPlayerEntity.y.WORD - this->y.WORD) - (((u8)(-GET_ANGLE_HI(this)) ^ 0x80))); if (IS_FROZEN(this) == FALSE) { tmp2 = 8; } else { tmp2 = 32; } // Probably that the boss is aiming at the player? if (tmp1 > -tmp2 && tmp1 < tmp2) { if (GET_PHASE4_ATTACK_PATTERN(this) != 0xff) { OctorokBoss_SetAttackTimer(this); return; } if (GET_ATTACK_WAIT_TURNS(this) == 0) { OctorokBoss_StartRegularAttack(this); } else { OctorokBoss_WaitAnotherTurn(this); // Resets to subaction1 } } else { // Rotate to face the player if ((u32)tmp1 > 0x80) { GET_ANGLE(this) += GET_ANGULAR_VEL(this); GET_HELPER(this)->rotation = ROTATION_CW; } else { GET_ANGLE(this) -= GET_ANGULAR_VEL(this); GET_HELPER(this)->rotation = ROTATION_CCW; } } this->direction = (u8)(-GET_ANGLE_HI(this) ^ 0x80U) >> 3; if (IS_FROZEN(this)) { if (GET_ANGULAR_VEL(this) < 0x280) { switch (GET_BOSS_PHASE(this)) { case 1: GET_ANGULAR_VEL(this)++; break; case 3: GET_ANGULAR_VEL(this) += 2; break; } } } OctorokBoss_StepSound(this, 0x1f); } void OctorokBoss_Action1_WaitForTurn(Entity* this) { if (((GET_TIMER(this)-- == 0) || (GET_BOSS_PHASE(this) == 0)) || (IS_FROZEN(this))) { GET_ATTACK_WAIT_TURNS(this)--; OctorokBoss_ResetToSubAction0(this); } else { if (ProcessMovement0(this) == 0) { GET_TIMER(this) = 0; } } OctorokBoss_StepSound(this, 0x1f); } void OctorokBoss_Action1_WaitForAttack(Entity* this) { if (GET_TIMER(this)-- == 0) { OctorokBoss_SetWaitTurnsForNextAttack(this); } } // Charge forwards and let stones fall when a collision occurs. void OctorokBoss_Action1_ChargeAttack(Entity* this) { bool32 knockbackCondition; if (GET_TIMER(this) == 0) { ProcessMovement0(this); knockbackCondition = 0; if ((this->direction != 0) && (this->direction != 0x10)) { knockbackCondition = ((u32)this->collisions & 0xee00) != 0; } if (((this->direction != 0x18) && (this->direction != 8)) && ((this->collisions & 0xee) != 0)) { knockbackCondition = 1; } if (knockbackCondition != 0) { this->knockbackDuration = 0x20; this->knockbackSpeed = 0x200; this->knockbackDirection = this->direction ^ 0x10; GET_HELPER(this)->fallingStonesTimer += 0x3c; OctorokBoss_SetAttackTimer(this); InitScreenShake(0x3c, 0); SoundReq(SFX_158); SoundReq(SFX_14C); } } else { GET_TIMER(this)--; } OctorokBoss_StepSound(this, 0xf); } void OctorokBoss_Action1_Attack(Entity* this) { OctorokBoss_Action1_Attack_Type2s[this->type2](this); sub_08036FE4(this); if (this->field_0x80.HALF.LO != 0) { gPlayerEntity.spriteSettings.draw = 0; gPlayerEntity.flags &= ~ENT_COLLIDE; gPlayerEntity.collisionLayer = 2; sub_08078B48(); sub_08077B20(); gPlayerEntity.parent = this; sub_08036914(&gPlayerEntity, (u8) - (GET_ANGLE_HI(this) + 0x80), 0x3800); } } void OctorokBoss_Action1_Attack_Type2_0(Entity* this) { if (GET_CURRENT_ATTACK(this) == NO_ATTACK) { OctorokBoss_ResetToSubAction0(this); } else { GET_ANGULAR_VEL(this) = 0x100; this->type2 = 1; if (IS_FROZEN(this) == FALSE) { GET_TIMER(this) = 0x16; } else { GET_TIMER(this) = 0; } SoundReq(SFX_155); } } void OctorokBoss_Action1_Attack_Type2_1(Entity* this) { if (this->field_0x74.HWORD < 0xc0) { this->field_0x74.HWORD++; } else { if (GET_TIMER(this)-- == 0) { if (GET_CURRENT_ATTACK(this) == ATTACK_VACUUM) { this->type2 = 3; if (IS_FROZEN(this)) { GET_TIMER(this) = 0x3c; } else { GET_TIMER(this) = 0x78; } GET_HELPER(this)->targetAngle = GET_ANGLE_HI(this); } else { this->type2 = 2; GET_TIMER(this) = 0x2d; } SoundReq(SFX_155); } } } void OctorokBoss_Action1_Attack_Type2_2(Entity* this) { if (GET_TIMER(this) == 0) { if (this->field_0x76.HWORD < this->field_0x74.HWORD) { this->field_0x74.HWORD -= 8; return; } this->type2 = 3; this->field_0x74.HWORD = this->field_0x76.HWORD; if (GET_CURRENT_ATTACK(this) != ATTACK_SMOKE) { GET_TIMER(this) = 0x3c; } else { GET_TIMER(this) = 0; CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 4, 0); } } else { GET_TIMER(this)--; } } void OctorokBoss_Action1_Attack_Type2_3(Entity* this) { if ((gRoomTransition.frameCount & 2) != 0) { GET_HELPER(this)->mouthObject->field_0x76.HWORD -= 8; } else { GET_HELPER(this)->mouthObject->field_0x76.HWORD += 8; if (0x180 < GET_HELPER(this)->mouthObject->field_0x76.HWORD) { GET_HELPER(this)->mouthObject->field_0x76.HWORD = 0x180; } } if (GET_CURRENT_ATTACK(this) != ATTACK_VACUUM) { if (GET_HELPER(this)->mouthObject->field_0x78.HALF.HI < 0x20) { GET_HELPER(this)->mouthObject->field_0x78.HALF.HI++; GET_HELPER(this)->mouthObject->field_0x76.HWORD += 8; } } OctorokBoss_AttackFunctions[GET_CURRENT_ATTACK(this)](this); } void OctorokBoss_ExecuteAttackSpitRock(Entity* this) { this->child = CreateProjectileWithParent(this, OCTOROK_BOSS_PROJECTILE, 0); if (this->child != NULL) { this->child->parent = this; this->child->direction = ((u8)-GET_ANGLE_HI(this) ^ 0x80); } GET_HELPER(this)->mouthObject->field_0x78.HALF.HI++; OctorokBoss_SetAttackTimer(this); } void OctorokBoss_ExecuteAttackVacuum(Entity* this) { s32 tmp; if (this->field_0x80.HALF.LO == 0) { this->direction = sub_080045DA(gPlayerEntity.x.WORD - this->x.WORD, gPlayerEntity.y.WORD - this->y.WORD); tmp = ((u8) - (GET_ANGLE_HI(this) + 0x80)) - this->direction; if (tmp < 0) { tmp = -tmp; } if (tmp < 0x10) { if (sub_0806FC80(this, &gPlayerEntity, 0xf0) != 0) { if ((gPlayerState.flags & PL_FROZEN) == 0) { if ((gPlayerEntity.flags & PL_MINISH) != 0) { LinearMoveAngle(&gPlayerEntity, 0x280, -GET_ANGLE_HI(this)); if (sub_0806FC80(this, &gPlayerEntity, 0x48) != 0) { this->field_0x80.HALF.LO = 1; GET_TIMER(this) = 2; GET_HELPER(this)->targetAngle = sub_080045DA((gRoomControls.origin_x + 0x108) * 0x10000 - this->x.WORD, (gRoomControls.origin_y + 0x88) * 0x10000 - this->y.WORD); GET_HELPER(this)->targetAngle = (u8) - (GET_HELPER(this)->targetAngle + 0x80); SoundReq(SFX_ED); } } } else { gPlayerState.flags &= ~PL_FROZEN; } } } if ((gRoomTransition.frameCount & 3) == 0) { CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 2, 0); } } else { if ((IS_FROZEN(this)) || (GET_HELPER(this)->targetAngle == GET_ANGLE_HI(this))) { if (this->field_0x80.HALF.LO == 1) { this->field_0x80.HALF.LO = 2; this->type2 = 2; GET_TIMER(this) = 0x2d; GET_ANGULAR_VEL(this) = 0x100; GET_HELPER(this)->field_0x2 = 0; return; } this->field_0x80.HALF.LO = 0; GET_ANGULAR_VEL(this) = 0x100; GET_HELPER(this)->mouthObject->field_0x78.HALF.HI++; gPlayerEntity.spriteSettings.draw = 1; gPlayerEntity.flags &= ~ENT_COLLIDE; gPlayerEntity.collisionLayer = 1; sub_080792BC(0x400, (u32)(-(GET_ANGLE_HI(this) + 0x80) * 0x1000000) >> 0x1b, 0x30); OctorokBoss_SetAttackTimer(this); SoundReq(SFX_EF); return; } GET_TIMER(this) = 2; } if (GET_TIMER(this) == 0) { this->field_0x74.HWORD = this->field_0x76.HWORD; this->type2 = 0; OctorokBoss_SetAttackTimer(this); } else { GET_TIMER(this)--; if ((gPlayerState.flags == PL_FROZEN) && (GET_TIMER(this) == 0x3c)) { tmp = sub_080045DA(gPlayerEntity.x.WORD - this->x.WORD, gPlayerEntity.y.WORD - this->y.WORD); if ((u8)((tmp - ((u8)-GET_ANGLE_HI(this) ^ 0x80))) > 0x80) { GET_HELPER(this)->targetAngle = GET_ANGLE_HI(this) + 0x30; } else { GET_HELPER(this)->targetAngle = GET_ANGLE_HI(this) - 0x30; } } if (IS_FROZEN(this) == FALSE) { if (GET_HELPER(this)->targetAngle != GET_ANGLE_HI(this)) { GET_HELPER(this)->field_0x2 = 1; if ((u8)(GET_HELPER(this)->targetAngle - GET_ANGLE_HI(this)) > 0x80) { GET_ANGLE(this) -= GET_ANGULAR_VEL(this); } else { GET_ANGLE(this) += GET_ANGULAR_VEL(this); } } else { GET_HELPER(this)->field_0x2 = IS_FROZEN(this); } } } } void OctorokBoss_ExecuteAttackSmoke(Entity* this) { if (GET_TIMER(this) == 0xff) { this->type2 = 0; OctorokBoss_SetAttackTimer(this); GET_TIMER(this) = 0x78; } else { GET_TIMER(this)++; ChangeLightLevel(-1); if ((gRoomTransition.frameCount & 3) == 0) { if ((gRoomTransition.frameCount & 7) == 0) { SoundReq(SFX_124); } CreateObjectWithParent(this, OCTOROK_BOSS_OBJECT, 3, 0); } } } void OctorokBoss_ExecuteAttackFreeze(Entity* this) { if (GET_TIMER(this) == 0) { GET_HELPER(this)->field_0x2 = 0; OctorokBoss_SetAttackTimer(this); } else { GET_TIMER(this)--; if ((gRoomTransition.frameCount & 3) == 0) { this->child = CreateProjectileWithParent(this, OCTOROK_BOSS_PROJECTILE, 2); if (this->child != NULL) { this->child->parent = this; this->child->direction = (u8)-GET_ANGLE_HI(this) ^ 0x80; } } } } void OctorokBoss_Burning(Entity* this) { OctorokBoss_Burning_SubActions[this->subAction](this); if (GET_HELPER(this)->fallingStonesTimer != 0) { GET_HELPER(this)->fallingStonesTimer--; if ((gRoomTransition.frameCount & 7) == 0) { // Falling stones CreateProjectileWithParent(this, OCTOROK_BOSS_PROJECTILE, 3); } } sub_0805EC9C(this, this->field_0x76.HWORD, this->field_0x74.HWORD, GET_ANGLE(this)); } void OctorokBoss_Burning_SubAction0(Entity* this) { this->subAction = 1; this->speed = 0x200; this->collisions = 0; this->direction = (u8)(-GET_ANGLE_HI(this) ^ 0x80U) >> 3; GET_TIMER(this) = 0x78; GET_ANGULAR_VEL(this) = 0x180; GET_HELPER(this)->field_0x0 = 4; sub_080368D8(this); OctorokBoss_Burning_SubAction1(this); } void OctorokBoss_Burning_SubAction1(Entity* this) { ProcessMovement0(this); if (this->collisions != 0) { this->subAction = 2; GET_HELPER(this)->targetAngle = GET_ANGLE_HI(this); if ((this->collisions & 0xee00) != 0) { GET_HELPER(this)->targetAngle = -GET_HELPER(this)->targetAngle; } if ((this->collisions & 0xee) != 0) { GET_HELPER(this)->targetAngle = -GET_HELPER(this)->targetAngle ^ 0x80; } this->knockbackDuration = 0x18; this->knockbackSpeed = 0x200; this->knockbackDirection = this->direction ^ 0x10; GET_HELPER(this)->fallingStonesTimer += 0x1e; InitScreenShake(0x1e, 0); SoundReq(SFX_158); SoundReq(SFX_14C); } if (GET_TIMER(this)-- == 0) { this->health = 0; } if ((gRoomTransition.frameCount & 0x1f) == 0) { SoundReq(SFX_159); } } void OctorokBoss_Burning_SubAction2(Entity* this) { if ((u32)(GET_HELPER(this)->targetAngle - GET_ANGLE_HI(this) + 7) < 0xf) { this->subAction = 1; this->direction = ((u8)-GET_ANGLE_HI(this) ^ 0x80) >> 3; this->collisions = 0; ProcessMovement0(this); } else { if ((u8)(GET_HELPER(this)->targetAngle - GET_ANGLE_HI(this)) >= 0x81) { GET_ANGLE(this) -= GET_ANGULAR_VEL(this); } else { GET_ANGLE(this) += GET_ANGULAR_VEL(this); } } } void sub_080368D8(Entity* this) { if (this->field_0x80.HALF.LO != 0) { gPlayerEntity.spriteSettings.draw = 1; gPlayerEntity.flags |= ENT_COLLIDE; gPlayerEntity.collisionLayer = 1; } this->field_0x76.HWORD = 0xa0; this->field_0x74.HWORD = 0xa0; } void sub_08036914(Entity* this, s32 angle, s32 radius) { s16 tmp; tmp = FixedMul(gSineTable[angle], radius); tmp = FixedDiv(tmp, 0x100); this->x.WORD = this->parent->x.WORD + ((tmp << 0x10) >> 8); tmp = FixedMul(gSineTable[angle + 0x40], radius); tmp = FixedDiv(tmp, 0x100); this->y.WORD = this->parent->y.WORD - ((tmp << 0x10) >> 8); this->spriteOffsetX = this->parent->spriteOffsetX; this->spriteOffsetY = this->parent->spriteOffsetY; } NONMATCH("asm/non_matching/octorokBoss/sub_08036998.inc", void sub_08036998(Entity* this)) { u32 tmp; s8* tmp2; s8 tmp3; s32 a, b; // TODO regalloc in this awful structure here tmp2 = &GET_TIMER(this); tmp = this->field_0xf + (u8)*tmp2; *tmp2 = tmp; if ((s8)this->field_0xf < 0) { a = tmp; b = -this->actionDelay; if (a << 0x18 < b << 0x18) { this->field_0xf = -this->field_0xf; } } else { if (((s8)*tmp2) > ((s32)this->actionDelay)) { this->field_0xf = -this->field_0xf; } } } END_NONMATCH // Calculate tail angles regular ASM_FUNC("asm/non_matching/octorokBoss/sub_080369D0.inc", void sub_080369D0(Entity* this, s32 radius, s32 angleSpeed)) /*{ s16 r1; s32 iVar7; u32 index; s32 tmp; s16 angleDiff; Entity** tailObj; HelperStruct* helper = GET_HELPER(this); // Calculate the angle for the tail end helper->tailObjects[0]->field_0x7a.HALF.HI = helper->tailObjects[helper->tailCount - 1]->field_0x7a.HALF.HI + GET_TIMER(this); // iterate tails from 0 to tailCount-1 to calculate the angles for (index = 0; index < helper->tailCount - 1; index++) { if (helper->tailObjects[index]->field_0x7a.HALF.HI != helper->tailObjects[index + 1]->field_0x7a.HALF.HI) { angleDiff = helper->tailObjects[index + 1]->field_0x7a.HALF.HI - helper->tailObjects[index]->field_0x7a.HALF.HI; if (angleDiff >= 1) { if ((s8)-angleSpeed > angleDiff) { helper->tailObjects[index + 1]->field_0x7a.HALF.HI = helper->tailObjects[index]->field_0x7a.HALF.HI + angleSpeed; } } else { if ((u8)angleSpeed < angleDiff) { helper->tailObjects[index + 1]->field_0x7a.HALF.HI = helper->tailObjects[index]->field_0x7a.HALF.HI - angleSpeed; } } } } // iterate tails from tailCount-1 to 0 to calculate the positions index = helper->tailCount - 1; tailObj = &helper->tailObjects[index]; while (index != 0) { // r1 = (s16)((u32)(radius << 0x14) >> 0x10); index--; tmp = FixedMul(gSineTable[(*tailObj)->prev->field_0x7a.HALF.HI ^ 0x80], radius); tmp = FixedDiv(tmp, 0x100); (*tailObj)->prev->x.WORD = (*tailObj)->next->x.WORD + ((tmp << 0x10) >> 8); tmp = FixedMul(gSineTable[((*tailObj)->prev->field_0x7a.HALF.HI ^ 0x80) + 0x40], radius); tmp = FixedDiv(tmp, 0x100); (*tailObj)->prev->y.WORD = (*tailObj)->next->y.WORD - ((tmp << 0x10) >> 8); tailObj--; } }*/ // calculate tail angles frozen sub_08036AF0 ASM_FUNC("asm/non_matching/octorokBoss/sub_08036AF0.inc", void sub_08036AF0(Entity* this, s32 radius, s32 angleSpeed)) /*{ u8 bVar1; u8 cVar2; s16 _newY; Entity** tailObjects; u32 uVar5; s16 angleDiff; s16 tmp; u32 index; Entity* prevTailObject; Entity* currentTailObject; HelperStruct* helper; helper = GET_HELPER(this); for (index = helper->tailCount - 1; index != 0; index--) { if (angleSpeed == 0) { if (radius >= sub_080041DC(helper->tailObjects[index], helper->tailObjects[index - 1]->x.HALF.HI, helper->tailObjects[index - 1]->y.HALF.HI)) { return; } else { helper->tailObjects[index - 1]->field_0x7a.HALF.HI = sub_080045DA(helper->tailObjects[index - 1]->x.WORD - helper->tailObjects[index]->x.WORD, helper->tailObjects[index - 1]->y.WORD - helper->tailObjects[index]->y.WORD); tmp = FixedMul(gSineTable[helper->tailObjects[index - 1]->field_0x7a.HALF.HI], radius); tmp = FixedDiv(tmp, 0x100); helper->tailObjects[index - 1]->x.WORD = helper->tailObjects[index]->x.WORD + (((s32)tmp << 0x10) >> 8); tmp = FixedMul(gSineTable[helper->tailObjects[index - 1]->field_0x7a.HALF.HI + 0x40], radius); helper->tailObjects[index - 1]->y.WORD = helper->tailObjects[index]->y.WORD - ((FixedDiv(tmp, 0x100) << 0x10) >> 8); } } else { if (helper->tailObjects[index - 1]->field_0x7a.HALF.HI != helper->tailObjects[index]->field_0x7a.HALF.HI) { angleDiff = ((helper->tailObjects[index]->field_0x7a.HALF.HI - helper->tailObjects[index - 1]->field_0x7a.HALF.HI) * 0x1000000) >> 0x18; if (angleDiff >= 1) { if ((u8)angleSpeed > angleDiff) { helper->tailObjects[index - 1]->field_0x7a.HALF.HI = helper->tailObjects[index]->field_0x7a.HALF.HI - angleSpeed; } } else { if ((s8)-angleSpeed < angleDiff) { helper->tailObjects[index - 1]->field_0x7a.HALF.HI = helper->tailObjects[index]->field_0x7a.HALF.HI + angleSpeed; } } } tmp = FixedMul(gSineTable[helper->tailObjects[index - 1]->field_0x7a.HALF.HI], radius); tmp = FixedDiv(tmp, 0x100); helper->tailObjects[index - 1]->x.WORD = helper->tailObjects[index]->x.WORD + ((tmp << 0x10) >> 8); tmp = FixedMul(gSineTable[helper->tailObjects[index - 1]->field_0x7a.HALF.HI + 0x40], radius); helper->tailObjects[index - 1]->y.WORD = helper->tailObjects[index]->y.WORD - ((FixedDiv(tmp, 0x100) << 0x10) >> 8); } } }*/ void OctorokBoss_SetAttackTimer(Entity* this) { const u8* attackPatterns; if ((GET_BOSS_PHASE(this) == 4) && (GET_PHASE4_ATTACK_PATTERN(this) != 0xff)) { this->subAction = ACTION1_ATTACK; this->type2 = 0; this->field_0x80.HALF.LO = 0; attackPatterns = OctorokBoss_Phase4AttackPatterns[GET_PHASE4_ATTACK_PATTERN(this)]; GET_CURRENT_ATTACK(this) = attackPatterns[GET_NEXT_ATTACK_INDEX(this)]; GET_NEXT_ATTACK_INDEX(this) += 1; if (GET_CURRENT_ATTACK(this) != END_OF_ATTACK_PATTERN) { return; } // End of this pattern, choose the next pattern. GET_PHASE4_ATTACK_PATTERN(this) = 0xff; } this->subAction = ACTION1_WAITFORATTACK; if (IS_FROZEN(this)) { switch (GET_BOSS_PHASE(this)) { case 1: GET_TIMER(this) = 30; break; case 3: GET_TIMER(this) = 10; break; } } else { if ((s16)gRoomVars.lightLevel != 0x100) { // Constantly attack when its dark. GET_TIMER(this) = 1; } else { GET_TIMER(this) = OctorokBoss_AttackTimerValues[GetRandomByWeight(OctorokBoss_AttackTimerWeights)]; } } } void OctorokBoss_ResetToSubAction0(Entity* this) { GET_ANGULAR_VEL(this) = 0x100; GET_HELPER(this)->field_0x0 = 2; GET_HELPER(this)->rotation = NO_ROTATION; this->subAction = ACTION1_AIMTOWARDSPLAYER; } void OctorokBoss_WaitAnotherTurn(Entity* this) { this->subAction = ACTION1_WAITFORTURN; this->speed = 0xc0; GET_HELPER(this)->field_0x0 = 1; GET_TIMER(this) = OctorokBoss_TurnTimeValues[GetRandomByWeight(OctorokBoss_TurnTimeWeights)]; } void OctorokBoss_SetWaitTurnsForNextAttack(Entity* this) { GET_PHASE4_ATTACK_PATTERN(this) = 0xff; if (IS_FROZEN(this) == FALSE) { if ((s16)gRoomVars.lightLevel != 0x100) { // Constantly attack when its dark. GET_ATTACK_WAIT_TURNS(this) = IS_FROZEN(this); } else { GET_ATTACK_WAIT_TURNS(this) = OctorokBoss_WaitForAttackTurnsValues[GetRandomByWeight(OctorokBoss_WaitForAttackTurnsWeights)]; } } else { GET_ATTACK_WAIT_TURNS(this) = 0; } OctorokBoss_ResetToSubAction0(this); } void OctorokBoss_StartRegularAttack(Entity* this) { const u8* attackPattern; // Set us up for an attack this->subAction = ACTION1_ATTACK; this->type2 = 0; GET_PHASE4_ATTACK_PATTERN(this) = 0xff; this->field_0x80.HALF.LO = 0; GET_HELPER(this)->field_0x2 = 0; if (GET_BOSS_PHASE(this) == 0) { // In phase 0 just spit rocks. GET_CURRENT_ATTACK(this) = ATTACK_SPITROCK; return; } if (GET_BOSS_PHASE(this) == 4) { if (((Random() & 3) == 0) || ((s16)gRoomVars.lightLevel != 0x100)) { this->subAction = ACTION1_SUBACTION2; this->speed = 0x200; GET_TIMER(this) = 0x3c; this->collisions = 0; GET_HELPER(this)->field_0x0 = 4; SoundReq(SFX_159); return; } if (GET_BOSS_PHASE(this) == 4) { // Select a new attack pattern that is not the previous one. u32 rand; GET_NEXT_ATTACK_INDEX(this) = 0; rand = Random() & 3; if (GET_HELPER(this)->phase4PrevAttackPattern != rand) { GET_PHASE4_ATTACK_PATTERN(this) = rand; } else { GET_PHASE4_ATTACK_PATTERN(this) = (GET_HELPER(this)->phase4PrevAttackPattern + 1) & 3; } GET_HELPER(this)->phase4PrevAttackPattern = GET_PHASE4_ATTACK_PATTERN(this); OctorokBoss_SetAttackTimer(this); return; } } if (IS_FROZEN(this) == FALSE) { attackPattern = OctorokBoss_NormalAttackPatterns; } else { attackPattern = OctorokBoss_FrozenAttackPatterns; } GET_CURRENT_ATTACK(this) = attackPattern[GET_NEXT_ATTACK_INDEX(this)]; if (++GET_NEXT_ATTACK_INDEX(this) > 4) { GET_NEXT_ATTACK_INDEX(this) = 0; } } void OctorokBoss_ChangePalette(Entity* this, u32 paletteIndex) { u32 i; ChangeObjPalette(GET_HELPER(this)->mouthObject, paletteIndex); for (i = 0; i < 4; i++) { ChangeObjPalette(GET_HELPER(this)->legObjects[i], paletteIndex); } for (i = GET_HELPER(this)->tailCount - 1; i != 0; i--) { ChangeObjPalette(GET_HELPER(this)->tailObjects[i], paletteIndex); } } void sub_08036F60(Entity* this) { if ((this->subAction != 4) && (IS_FROZEN(this) == FALSE)) { this->field_0x76.HWORD += (s8)this->actionDelay; this->field_0x74.HWORD += (s8)this->actionDelay; if (this->field_0x76.HWORD < 0x9c) { this->actionDelay = 1; } else { if (this->field_0x76.HWORD > 0xa4) { this->actionDelay = 0xff; } } } } void OctorokBoss_StepSound(Entity* this, u32 frameMask) { if ((gRoomTransition.frameCount & frameMask) == 0) { if (IS_FROZEN(this) == FALSE) { SoundReq(SFX_163); } else { SoundReq(SFX_ICE_BLOCK_SLIDE); } } } void sub_08036FE4(Entity* this) { if ((IS_FROZEN(this)) && (this->field_0x80.HALF.LO == 0)) { if (GET_ANGULAR_VEL(this) != 0) { if (GET_HELPER(this)->rotation != NO_ROTATION) { if (GET_HELPER(this)->rotation == ROTATION_CW) { GET_ANGLE(this) += GET_ANGULAR_VEL(this); } else { GET_ANGLE(this) -= GET_ANGULAR_VEL(this); } } switch (GET_BOSS_PHASE(this)) { case 1: GET_ANGULAR_VEL(this)--; break; case 3: GET_ANGULAR_VEL(this) -= 2; break; } } } } void (*const OctorokBoss_Functions[])(Entity*) = { OctorokBoss_OnTick, OctorokBoss_OnTick, GenericKnockback, OctorokBoss_Death, GenericConfused, }; void (*const OctorokBoss_Hit_SubActions[])(Entity*) = { OctorokBoss_Hit_SubAction0, OctorokBoss_Hit_SubAction1, OctorokBoss_Hit_SubAction2, OctorokBoss_Hit_SubAction3, OctorokBoss_Hit_SubAction4, OctorokBoss_Hit_SubAction5, OctorokBoss_Hit_SubAction6, }; const u8 OctorokBoss_HealthPerPhase[] = { 3, 1, 3, 1, 3, 1, 3, 0, }; void (*const OctorokBoss_Actions[])(Entity*) = { OctorokBoss_Init, OctorokBoss_Action1, OctorokBoss_Hit, OctorokBoss_Intro, OctorokBoss_Burning, }; const u8 gUnk_080CF08C[] = { 0, 4, 0, 0, 1, 5, 0, 0, 1, 4, 0, 0, 1, 3, 0, 0, 1, 2, 0, 0, 2, 1, 0, 0, 1, 1, 0, 0, 3, 6, 0, 0, }; void (*const OctorokBoss_Intro_SubActions[])(Entity*) = { OctorokBoss_Intro_SubAction0, OctorokBoss_Intro_SubAction1, OctorokBoss_Intro_SubAction2, OctorokBoss_Intro_SubAction3, OctorokBoss_Intro_SubAction4, OctorokBoss_Intro_SubAction5, }; void (*const OctorokBoss_Action1_SubActions[])(Entity*) = { OctorokBoss_Action1_AimTowardsPlayer, OctorokBoss_Action1_WaitForTurn, OctorokBoss_Action1_ChargeAttack, OctorokBoss_Action1_WaitForAttack, OctorokBoss_Action1_Attack, }; const u8 OctorokBoss_LegAngleOffset[] = { 40, 80, 176, 216, }; const u8 OctorokBoss_LegAngleOffset2[] = { 128, 0, 0, 128, }; void (*const OctorokBoss_Action1_Attack_Type2s[])(Entity*) = { OctorokBoss_Action1_Attack_Type2_0, OctorokBoss_Action1_Attack_Type2_1, OctorokBoss_Action1_Attack_Type2_2, OctorokBoss_Action1_Attack_Type2_3, }; void (*const OctorokBoss_AttackFunctions[])(Entity*) = { OctorokBoss_ExecuteAttackSpitRock, OctorokBoss_ExecuteAttackVacuum, OctorokBoss_ExecuteAttackSmoke, OctorokBoss_ExecuteAttackFreeze, }; void (*const OctorokBoss_Burning_SubActions[])(Entity*) = { OctorokBoss_Burning_SubAction0, OctorokBoss_Burning_SubAction1, OctorokBoss_Burning_SubAction2, }; // These attack timers are only used if the boss isn't frozen and gRoomVars.field_0xc != 0x100 const u8 OctorokBoss_AttackTimerWeights[] = { 48, 96, 80, 32, }; const u8 OctorokBoss_AttackTimerValues[] = { 5, 10, 15, 30, }; // 5: don't attack const u8 OctorokBoss_Phase4AttackPattern0[] = { ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, ATTACK_SMOKE, END_OF_ATTACK_PATTERN, }; const u8 OctorokBoss_Phase4AttackPattern1[] = { ATTACK_VACUUM, NO_ATTACK, ATTACK_SPITROCK, ATTACK_SMOKE, END_OF_ATTACK_PATTERN, }; const u8 OctorokBoss_Phase4AttackPattern2[] = { ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, END_OF_ATTACK_PATTERN, }; const u8* const OctorokBoss_Phase4AttackPatterns[] = { OctorokBoss_Phase4AttackPattern0, OctorokBoss_Phase4AttackPattern1, OctorokBoss_Phase4AttackPattern2, OctorokBoss_Phase4AttackPattern1, }; const u8 OctorokBoss_TurnTimeWeights[] = { 48, 96, 80, 32, }; const u8 OctorokBoss_TurnTimeValues[] = { 70, 80, 90, 100, }; const u8 OctorokBoss_WaitForAttackTurnsWeights[] = { 64, 128, 64, }; const u8 OctorokBoss_WaitForAttackTurnsValues[] = { 1, 2, 3, }; const u8 OctorokBoss_NormalAttackPatterns[] = { ATTACK_VACUUM, ATTACK_SPITROCK, ATTACK_VACUUM, ATTACK_SPITROCK, ATTACK_SPITROCK, }; const u8 OctorokBoss_FrozenAttackPatterns[] = { ATTACK_VACUUM, ATTACK_FREEZE, ATTACK_VACUUM, ATTACK_FREEZE, ATTACK_VACUUM, };