/** * @file octorokBoss.c * @ingroup Enemies * * @brief Octorok boss enemy */ #define NENT_DEPRECATED #include "enemy/octorokBoss.h" #include "collision.h" #include "functions.h" #include "game.h" #include "object.h" #include "projectile.h" #define IS_FROZEN(this) ((this)->bossPhase & 1) /* for TAIL_END object: this->field_0x7c.BYTES.byte1 tailRadius */ #define GET_TAIL_RADIUS(this) this->currentAttack // this->field_0x7c.BYTES.byte1 void OctorokBoss_Hit(OctorokBossEntity*); void OctorokBoss_Action1(OctorokBossEntity*); void OctorokBoss_Burning_SubAction1(OctorokBossEntity*); void sub_080368D8(OctorokBossEntity*); void sub_08036914(Entity*, s32, s32); void sub_08036998(OctorokBossEntity*); void sub_080369D0(OctorokBossEntity*, s32, s32); void sub_08036AF0(OctorokBossEntity*, s32, s32); void OctorokBoss_SetAttackTimer(OctorokBossEntity*); void OctorokBoss_ResetToSubAction0(OctorokBossEntity*); void OctorokBoss_WaitAnotherTurn(OctorokBossEntity*); void OctorokBoss_SetWaitTurnsForNextAttack(OctorokBossEntity*); void OctorokBoss_StartRegularAttack(OctorokBossEntity*); void OctorokBoss_ChangePalette(OctorokBossEntity*, u32); void sub_08036F60(OctorokBossEntity*); void OctorokBoss_StepSound(OctorokBossEntity*, u32); void sub_08036FE4(OctorokBossEntity*); void OctorokBoss_OnTick(OctorokBossEntity*); void OctorokBoss_OnDeath(OctorokBossEntity*); void OctorokBoss_Hit_SubAction0(OctorokBossEntity*); void OctorokBoss_Hit_SubAction1(OctorokBossEntity*); void OctorokBoss_Hit_SubAction2(OctorokBossEntity*); void OctorokBoss_Hit_SubAction3(OctorokBossEntity*); void OctorokBoss_Hit_SubAction4(OctorokBossEntity*); void OctorokBoss_Hit_SubAction5(OctorokBossEntity*); void OctorokBoss_Hit_SubAction6(OctorokBossEntity*); void OctorokBoss_Intro_SubAction0(OctorokBossEntity*); void OctorokBoss_Intro_SubAction1(OctorokBossEntity*); void OctorokBoss_Intro_SubAction2(OctorokBossEntity*); void OctorokBoss_Intro_SubAction3(OctorokBossEntity*); void OctorokBoss_Intro_SubAction4(OctorokBossEntity*); void OctorokBoss_Intro_SubAction5(OctorokBossEntity*); void OctorokBoss_Action1_AimTowardsPlayer(OctorokBossEntity*); void OctorokBoss_Action1_WaitForTurn(OctorokBossEntity*); void OctorokBoss_Action1_WaitForAttack(OctorokBossEntity*); void OctorokBoss_Action1_ChargeAttack(OctorokBossEntity*); void OctorokBoss_Action1_Attack(OctorokBossEntity*); void OctorokBoss_Init(OctorokBossEntity*); void OctorokBoss_Action1(OctorokBossEntity*); void OctorokBoss_Hit(OctorokBossEntity*); void OctorokBoss_Intro(OctorokBossEntity*); void OctorokBoss_Burning(OctorokBossEntity*); void OctorokBoss_Action1_Attack_Type2_0(OctorokBossEntity*); void OctorokBoss_Action1_Attack_Type2_1(OctorokBossEntity*); void OctorokBoss_Action1_Attack_Type2_2(OctorokBossEntity*); void OctorokBoss_Action1_Attack_Type2_3(OctorokBossEntity*); void OctorokBoss_ExecuteAttackSpitRock(OctorokBossEntity*); void OctorokBoss_ExecuteAttackVacuum(OctorokBossEntity*); void OctorokBoss_ExecuteAttackSmoke(OctorokBossEntity*); void OctorokBoss_ExecuteAttackFreeze(OctorokBossEntity*); void OctorokBoss_Burning_SubAction0(OctorokBossEntity*); void OctorokBoss_Burning_SubAction1(OctorokBossEntity*); void OctorokBoss_Burning_SubAction2(OctorokBossEntity*); void OctorokBoss(OctorokBossEntity* this) { static void (*const OctorokBoss_Functions[])(OctorokBossEntity*) = { OctorokBoss_OnTick, OctorokBoss_OnTick, (void (*)(OctorokBossEntity*))GenericKnockback, OctorokBoss_OnDeath, (void (*)(OctorokBossEntity*))GenericConfused, }; OctorokBoss_Functions[GetNextFunction(super)](this); } void OctorokBoss_OnDeath(OctorokBossEntity* this) { if (super->type == WHOLE) { super->action = HIT; super->subAction = 0; super->knockbackDuration = 0; super->health = 1; sub_080368D8(this); OctorokBoss_Hit(this); } else { DeleteThisEntity(); } } /* Hit SubActions 0: Start 1-3: 4- */ void OctorokBoss_Hit(OctorokBossEntity* this) { static void (*const OctorokBoss_Hit_SubActions[])(OctorokBossEntity*) = { OctorokBoss_Hit_SubAction0, OctorokBoss_Hit_SubAction1, OctorokBoss_Hit_SubAction2, OctorokBoss_Hit_SubAction3, OctorokBoss_Hit_SubAction4, OctorokBoss_Hit_SubAction5, OctorokBoss_Hit_SubAction6, }; if (this->bossPhase == 0) { if (super->subAction != 3) { gRoomControls.camera_target = &this->heap->tailObjects[0]->base; this->heap->field_0x7 = 0x5a; sub_08078B48(); } } else { if (this->heap->field_0x7 != 0) { this->heap->field_0x7--; sub_08078B48(); } } OctorokBoss_Hit_SubActions[super->subAction](this); if (super->subAction > 3) { sub_08078B48(); } sub_0800445C(super); SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); } void OctorokBoss_Hit_SubAction0(OctorokBossEntity* this) { this->unk_76 = 0xa0; this->unk_74 = 0xa0; this->heap->fallingStonesTimer = 0; if (this->bossPhase == 4) { super->subAction = 4; gPauseMenuOptions.disabled = 1; sub_08078B48(); SoundReq(SFX_BOSS_DIE); } else { if (IS_FROZEN(this) == FALSE) { super->type2 = 0; this->timer = 0x3c; } else { if (this->heap->tailCount > 3) { this->heap->tailCount--; } this->heap->tailObjects[0]->currentAttack = 0; this->timer = 0x78; } super->subAction = 1; } } void OctorokBoss_Hit_SubAction1(OctorokBossEntity* this) { bool32 frozen = IS_FROZEN(this); u16 diffX; u16 diffY; if (frozen == 0) { if (this->heap->tailObjects[0]->currentAttack != 0) { this->heap->tailObjects[0]->currentAttack--; } // Move to the center of the screen before freezing diffX = 0x108 + gRoomControls.origin_x - super->x.HALF.HI + 0x4; diffY = gRoomControls.origin_y - super->y.HALF.HI + 0x8c; if (diffX > 8 || diffY > 8) { this->heap->field_0x2 = 1; #if defined(JP) || defined(DEMO_JP) || defined(EU) super->direction = ((s32)sub_080045DA((((gRoomControls.origin_x + 0x108) << 0x10) - super->x.WORD), (((gRoomControls.origin_y + 0x88) << 0x10) - super->y.WORD))) >> 3; #else super->direction = ((s32)sub_080045DA(gRoomControls.origin_x + 0x108 - super->x.HALF.HI, gRoomControls.origin_y + 0x88 - super->y.HALF.HI)) >> 3; #endif super->speed = 0x100; ProcessMovement0(super); } else { // Freeze if (super->type2 == 0) { CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 5, 0); CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 5, 1); CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 5, 2); CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 5, 3); CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 5, 4); super->type2 = 1; } this->heap->field_0x2 = frozen; this->timer--; } } else { u32 i; for (i = this->heap->tailCount - 1; i != 0; i--) { OctorokBossEntity* tail = this->heap->tailObjects[i - 1]; tail->base.spriteSettings.draw |= 1; } if ((gRoomTransition.frameCount & 2) != 0) { CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 6, 0); } this->timer--; } if (this->timer == 0) { super->subAction = 2; this->bossPhase++; UnloadGFXSlots(super); if (IS_FROZEN(this) == FALSE) { super->hitType = 0x5f; LoadFixedGFX(super, 0x108); ChangeObjPalette(super, 0xef); OctorokBoss_ChangePalette(this, 0xef); InitAnimationForceUpdate(&(this->heap->tailObjects[0]->base), 1); } else { super->hitType = 0x61; LoadFixedGFX(super, 0x109); ChangeObjPalette(super, 0xf0); OctorokBoss_ChangePalette(this, 0xf3); InitAnimationForceUpdate(&(this->heap->tailObjects[0]->base), 2); } CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 8, 0); } } void OctorokBoss_Hit_SubAction2(OctorokBossEntity* this) { if (this->heap->tailObjects[0]->currentAttack != 0x80) { this->heap->tailObjects[0]->currentAttack++; } else { super->subAction = 3; this->timer = 0x96; gRoomControls.camera_target = &gPlayerEntity; } } void OctorokBoss_Hit_SubAction3(OctorokBossEntity* this) { static const u8 OctorokBoss_HealthPerPhase[] = { 3, 1, 3, 1, 3, 1, 3, 0, }; if (this->timer-- == 0) { super->health = OctorokBoss_HealthPerPhase[this->bossPhase]; COLLISION_ON(super); super->action = ACTION1; super->subAction = ACTION1_AIMTOWARDSPLAYER; this->nextAttackIndex = 0; OctorokBoss_SetWaitTurnsForNextAttack(this); } } void OctorokBoss_Hit_SubAction4(OctorokBossEntity* this) { Entity* object; super->subAction = 5; object = CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 9, 0); if (object != NULL) { gRoomControls.camera_target = object; } } void OctorokBoss_Hit_SubAction5(OctorokBossEntity* this) { super->subAction = 6; this->timer = 0x78; this->unk_80 = 0; this->angularSpeed.HALF.LO = 0; } // Wildly rotating with explosion fx void OctorokBoss_Hit_SubAction6(OctorokBossEntity* this) { s16 tmp; this->angle.HALF.HI -= 4; this->phase4AttackPattern += 8; if ((this->unk_80 & 0x80) != 0) { this->unk_80 -= (Random() & 3); if ((this->unk_80 & 0x80) == 0) { this->unk_80 = ((this->unk_80 & 0x80) << 0x18) >> 0x18; } } else { this->unk_80 += (Random() & 3); if ((this->unk_80) >= 0x19) { this->unk_80 |= 0x80; } } tmp = FixedMul(gSineTable[this->phase4AttackPattern], (this->unk_80 & 0x7f) << 8); tmp = FixedDiv(tmp, 0x100); super->spriteOffsetX = tmp >> 8; tmp = FixedMul(gSineTable[this->phase4AttackPattern + 0x40], (this->unk_80 & 0x7f) * 0x100); tmp = FixedDiv(tmp, 0x100); super->spriteOffsetY = -((tmp << 0x10) >> 8) >> 0x10; if (this->timer == 0) { if ((gRoomTransition.frameCount & 0xfU) == 0) { // Explosion in the center CreateFx(super, FX_GIANT_EXPLOSION3, 0); // Explosion at the front right leg CreateFx(&this->heap->legObjects[0]->base, FX_GIANT_EXPLOSION3, 0); } if (++this->angularSpeed.HALF.LO == 0x79) { this->heap->mouthObject->base.health = 1; SoundReq(SFX_BOSS_DIE); // Kill this boss GenericDeath(super); } } else { this->timer--; } } void OctorokBoss_OnTick(OctorokBossEntity* this) { static void (*const OctorokBoss_Actions[])(OctorokBossEntity*) = { OctorokBoss_Init, OctorokBoss_Action1, OctorokBoss_Hit, OctorokBoss_Intro, OctorokBoss_Burning, }; OctorokBoss_Actions[super->action](this); super->spriteRendering.b3 = 3; } 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, }; ASM_FUNC("asm/non_matching/octorokBoss/OctorokBoss_Init.inc", void OctorokBoss_Init(OctorokBossEntity* this)) void OctorokBoss_Intro(OctorokBossEntity* this) { static void (*const OctorokBoss_Intro_SubActions[])(OctorokBossEntity*) = { OctorokBoss_Intro_SubAction0, OctorokBoss_Intro_SubAction1, OctorokBoss_Intro_SubAction2, OctorokBoss_Intro_SubAction3, OctorokBoss_Intro_SubAction4, OctorokBoss_Intro_SubAction5, }; sub_08078B48(); gPauseMenuOptions.disabled = 1; sub_08036F60(this); OctorokBoss_Intro_SubActions[super->subAction](this); SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); } void OctorokBoss_Intro_SubAction0(OctorokBossEntity* this) { // Wait until the camera is on the Octorok if (this->timer-- == 0) { super->subAction = 1; this->angularSpeed.HWORD = 0x100; this->heap->unk_0 = 2; } } void OctorokBoss_Intro_SubAction1(OctorokBossEntity* this) { // Rotate Octorok to player if (this->angle.HALF.HI == 0x80) { this->timer = 0x3c; super->subAction = 2; this->heap->unk_0 = 0; // Octorok scream SoundReq(SFX_159); } else { this->angle.HWORD += this->angularSpeed.HWORD; } OctorokBoss_StepSound(this, 0xf); } void OctorokBoss_Intro_SubAction2(OctorokBossEntity* this) { // Wait for scream end if (this->timer-- == 0) { super->subAction = 3; this->timer = 0x3c; gPlayerEntity.spriteSettings.draw |= 1; gRoomControls.camera_target = &gPlayerEntity; gRoomControls.unk5 = 1; } } void OctorokBoss_Intro_SubAction3(OctorokBossEntity* this) { // Move the camera to the player if (this->timer-- == 0) { // Move the player inside the arena gPlayerEntity.direction = 0x10; gPlayerEntity.animationState = 4; sub_08078AC0(0x1e, 0, 0); this->timer = 0x3c; super->subAction = 4; } } void OctorokBoss_Intro_SubAction4(OctorokBossEntity* this) { if (this->timer-- == 0) { super->subAction = 5; this->timer = 0x2d; // Make the player look towards the exit gPlayerEntity.animationState = 0; } else { // Spawn exclamation bubble at a certain time if (this->timer == 0x1e) { CreateSpeechBubbleExclamationMark(&gPlayerEntity, 0xc, -0x18); } } } void OctorokBoss_Intro_SubAction5(OctorokBossEntity* this) { if (gPlayerEntity.animationState == 4) { if (this->timer++ > 0x1e) { // Play boss theme, enable control and switch to main action super->action = ACTION1; super->subAction = 0; gRoomControls.unk5 = gPlayerEntity.animationState; OctorokBoss_SetAttackTimer(this); gPauseMenuOptions.disabled = 0; SoundReq(BGM_BOSS_THEME); } } else { if (this->timer-- == 0) { // Player looks back towards Octorok gPlayerEntity.animationState = 4; } } } void OctorokBoss_Action1(OctorokBossEntity* this) { static void (*const OctorokBoss_Action1_SubActions[])(OctorokBossEntity*) = { OctorokBoss_Action1_AimTowardsPlayer, OctorokBoss_Action1_WaitForTurn, OctorokBoss_Action1_ChargeAttack, OctorokBoss_Action1_WaitForAttack, OctorokBoss_Action1_Attack, }; static const u8 OctorokBoss_LegAngleOffset[] = { 40, 80, 176, 216, }; static const u8 OctorokBoss_LegAngleOffset2[] = { 128, 0, 0, 128, }; Entity* object; u32 radius; u8 angle; if (super->type != WHOLE) { super->iframes = super->parent->iframes; } switch (super->type) { case LEG_BR: case LEG_FR: case LEG_FL: case LEG_BL: if ((((OctorokBossEntity*)super->parent)->field_0x6c.HALF.HI & 2) != 0) { DeleteThisEntity(); } if (this->heap->mouthObject->base.health == 1) { if ((s16)this->unk_76 < 0) { this->unk_76 -= 4; } else { this->unk_76 += 4; } if ((s16)this->unk_74 < 0) { this->unk_74 -= 4; } else { this->unk_74 += 4; } } SortEntityBelow(super->parent, super); if (((this->heap->field_0x2 != 0) || (super->parent->action == INTRO)) || (1 < (u8)(super->parent->subAction - 3))) { if ((s8)super->field_0xf < 0) { super->field_0xf = -this->heap->unk_0; } else { super->field_0xf = this->heap->unk_0; } sub_08036998(this); } radius = 0x10000 / ((OctorokBossEntity*)super->parent)->unk_76; radius = radius << 0xd >> 0x8; radius = radius - 0x2000; if (this->heap->mouthObject->base.health == 1) { radius = radius + 0x2200; } else { radius = (radius >> 1) + 0x2200; } angle = -(((OctorokBossEntity*)super->parent)->angle.HALF.HI + OctorokBoss_LegAngleOffset[super->type - 1]); sub_08036914(super, angle, radius); this->angle.HALF.HI = ((OctorokBossEntity*)super->parent)->angle.HALF.HI + OctorokBoss_LegAngleOffset2[super->type - 1] + this->timer; SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); break; case TAIL: if (this->heap->mouthObject->base.health < 2) { DeleteThisEntity(); } if ((this->heap->tailCount - 2) < super->type2) { DeleteThisEntity(); } SortEntityAbove(super->parent, super); if (this->heap->tailCount - 2 == super->type2) { SortEntityAbove(super->parent, super); radius = 0x10000 / ((OctorokBossEntity*)super->parent)->unk_74; radius = radius << 0xd >> 0x8; angle = -((OctorokBossEntity*)super->parent)->angle.HALF.HI; sub_08036914(super, angle, radius); this->angle.HALF.HI = -((OctorokBossEntity*)super->parent)->angle.HALF.HI; } if (!IS_FROZEN((OctorokBossEntity*)super->parent)) { super->spriteSettings.draw |= 1; } break; case TAIL_END: if (this->heap->mouthObject->base.health < 2) { DeleteThisEntity(); } UpdateAnimationSingleFrame(super); if (IS_FROZEN((OctorokBossEntity*)super->parent)) { sub_08036AF0(this, GET_TAIL_RADIUS(this), 0x10); if ((super->bitfield & 0x7f) == 7) { COLLISION_OFF(super); object = CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 0, 0); super->child = object; if (object != NULL) { object->parent = super->parent; ((OctorokBossEntity*)super->child)->heap = this->heap; } } } else { COLLISION_ON(super); super->spriteSettings.draw |= 1; sub_08036998(this); sub_080369D0(this, GET_TAIL_RADIUS(this), 4); } super->bitfield = 0; SetAffineInfo(super, this->unk_76, this->unk_74, -this->angle.HWORD ^ 0x8000); break; case MOUTH: if (super->health == 1) { super->health = 0; } else { SortEntityBelow(super->parent, super); if ((super->parent->subAction != 4) && (super->health != 1)) { if (this->timer > 0x1c) { this->timer--; } if (this->unk_76 > 0x100) { this->unk_76--; } else { this->unk_76 = 0x100; } } radius = 0x10000 / ((OctorokBossEntity*)super->parent)->unk_74; radius = radius * (this->timer << 8) >> 8; angle = -(((OctorokBossEntity*)super->parent)->angle.HALF.HI + 0x80); sub_08036914(super, angle, radius); this->angle.HALF.HI = ((OctorokBossEntity*)super->parent)->angle.HALF.HI; SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); sub_0800445C(super); } break; case WHOLE: sub_0800445C(super); sub_08036F60(this); if (this->heap->fallingStonesTimer != 0) { this->heap->fallingStonesTimer--; if ((gRoomTransition.frameCount & 3) == 0) { // Falling stones CreateProjectileWithParent(super, OCTOROK_BOSS_PROJECTILE, 3); } } OctorokBoss_Action1_SubActions[super->subAction](this); SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); break; } } void OctorokBoss_Action1_AimTowardsPlayer(OctorokBossEntity* this) { s32 tmp1; s32 tmp2; tmp1 = (u8)(sub_080045DA(gPlayerEntity.x.WORD - super->x.WORD, gPlayerEntity.y.WORD - super->y.WORD) - (((u8)(-this->angle.HALF.HI) ^ 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 (this->phase4AttackPattern != 0xff) { OctorokBoss_SetAttackTimer(this); return; } if (this->attackWaitTurns == 0) { OctorokBoss_StartRegularAttack(this); } else { OctorokBoss_WaitAnotherTurn(this); // Resets to subaction1 } } else { // Rotate to face the player if ((u32)tmp1 > 0x80) { this->angle.HWORD += this->angularSpeed.HWORD; this->heap->rotation = ROTATION_CW; } else { this->angle.HWORD -= this->angularSpeed.HWORD; this->heap->rotation = ROTATION_CCW; } } super->direction = (u8)(-this->angle.HALF.HI ^ 0x80U) >> 3; if (IS_FROZEN(this)) { if (this->angularSpeed.HWORD < 0x280) { switch (this->bossPhase) { case 1: this->angularSpeed.HWORD++; break; case 3: this->angularSpeed.HWORD += 2; break; } } } OctorokBoss_StepSound(this, 0x1f); } void OctorokBoss_Action1_WaitForTurn(OctorokBossEntity* this) { if (((this->timer-- == 0) || (this->bossPhase == 0)) || (IS_FROZEN(this))) { this->attackWaitTurns--; OctorokBoss_ResetToSubAction0(this); } else { if (ProcessMovement0(super) == 0) { this->timer = 0; } } OctorokBoss_StepSound(this, 0x1f); } void OctorokBoss_Action1_WaitForAttack(OctorokBossEntity* this) { if (this->timer-- == 0) { OctorokBoss_SetWaitTurnsForNextAttack(this); } } // Charge forwards and let stones fall when a collision occurs. void OctorokBoss_Action1_ChargeAttack(OctorokBossEntity* this) { bool32 knockbackCondition; if (this->timer == 0) { ProcessMovement0(super); knockbackCondition = FALSE; if ((super->direction != 0) && (super->direction != 0x10)) { knockbackCondition = ((super->collisions & (COL_EAST_ANY | COL_WEST_ANY)) != COL_NONE); } if (((super->direction != 0x18) && (super->direction != 8)) && (super->collisions & (COL_NORTH_ANY | COL_SOUTH_ANY))) { knockbackCondition = TRUE; } if (knockbackCondition) { super->knockbackDuration = 0x20; super->knockbackSpeed = 0x200; super->knockbackDirection = super->direction ^ 0x10; this->heap->fallingStonesTimer += 0x3c; OctorokBoss_SetAttackTimer(this); InitScreenShake(0x3c, 0); SoundReq(SFX_158); SoundReq(SFX_14C); } } else { this->timer--; } OctorokBoss_StepSound(this, 0xf); } void OctorokBoss_Action1_Attack(OctorokBossEntity* this) { static void (*const OctorokBoss_Action1_Attack_Type2s[])(OctorokBossEntity*) = { OctorokBoss_Action1_Attack_Type2_0, OctorokBoss_Action1_Attack_Type2_1, OctorokBoss_Action1_Attack_Type2_2, OctorokBoss_Action1_Attack_Type2_3, }; OctorokBoss_Action1_Attack_Type2s[super->type2](this); sub_08036FE4(this); if (this->unk_80 != 0) { gPlayerEntity.spriteSettings.draw = 0; gPlayerEntity.flags &= ~ENT_COLLIDE; gPlayerEntity.collisionLayer = 2; sub_08078B48(); PutAwayItems(); gPlayerEntity.parent = super; sub_08036914(&gPlayerEntity, (u8) - (this->angle.HALF.HI + 0x80), 0x3800); } } void OctorokBoss_Action1_Attack_Type2_0(OctorokBossEntity* this) { if (this->currentAttack == NO_ATTACK) { OctorokBoss_ResetToSubAction0(this); } else { this->angularSpeed.HWORD = 0x100; super->type2 = 1; if (IS_FROZEN(this) == FALSE) { this->timer = 0x16; } else { this->timer = 0; } SoundReq(SFX_155); } } void OctorokBoss_Action1_Attack_Type2_1(OctorokBossEntity* this) { if (this->unk_74 < 0xc0) { this->unk_74++; } else { if (this->timer-- == 0) { if (this->currentAttack == ATTACK_VACUUM) { super->type2 = 3; if (IS_FROZEN(this)) { this->timer = 0x3c; } else { this->timer = 0x78; } this->heap->targetAngle = this->angle.HALF.HI; } else { super->type2 = 2; this->timer = 0x2d; } SoundReq(SFX_155); } } } void OctorokBoss_Action1_Attack_Type2_2(OctorokBossEntity* this) { if (this->timer == 0) { if (this->unk_76 < this->unk_74) { this->unk_74 -= 8; return; } super->type2 = 3; this->unk_74 = this->unk_76; if (this->currentAttack != ATTACK_SMOKE) { this->timer = 0x3c; } else { this->timer = 0; CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 4, 0); } } else { this->timer--; } } void OctorokBoss_Action1_Attack_Type2_3(OctorokBossEntity* this) { static void (*const OctorokBoss_AttackFunctions[])(OctorokBossEntity*) = { OctorokBoss_ExecuteAttackSpitRock, OctorokBoss_ExecuteAttackVacuum, OctorokBoss_ExecuteAttackSmoke, OctorokBoss_ExecuteAttackFreeze, }; if ((gRoomTransition.frameCount & 2) != 0) { this->heap->mouthObject->unk_76 -= 8; } else { this->heap->mouthObject->unk_76 += 8; if (0x180 < this->heap->mouthObject->unk_76) { this->heap->mouthObject->unk_76 = 0x180; } } if (this->currentAttack != ATTACK_VACUUM) { if (this->heap->mouthObject->timer < 0x20) { this->heap->mouthObject->timer++; this->heap->mouthObject->unk_76 += 8; } } OctorokBoss_AttackFunctions[this->currentAttack](this); } void OctorokBoss_ExecuteAttackSpitRock(OctorokBossEntity* this) { super->child = CreateProjectileWithParent(super, OCTOROK_BOSS_PROJECTILE, 0); if (super->child != NULL) { super->child->parent = super; super->child->direction = ((u8) - this->angle.HALF.HI ^ 0x80); } this->heap->mouthObject->timer++; OctorokBoss_SetAttackTimer(this); } void OctorokBoss_ExecuteAttackVacuum(OctorokBossEntity* this) { s32 tmp; if (this->unk_80 == 0) { super->direction = sub_080045DA(gPlayerEntity.x.WORD - super->x.WORD, gPlayerEntity.y.WORD - super->y.WORD); tmp = ((u8) - (this->angle.HALF.HI + 0x80)) - super->direction; if (tmp < 0) { tmp = -tmp; } if (tmp < 0x10) { if (sub_0806FC80(super, &gPlayerEntity, 0xf0) != 0) { if ((gPlayerState.flags & PL_FROZEN) == 0) { if ((gPlayerEntity.flags & PL_MINISH) != 0) { LinearMoveAngle(&gPlayerEntity, 0x280, -this->angle.HALF.HI); if (sub_0806FC80(super, &gPlayerEntity, 0x48) != 0) { this->unk_80 = 1; this->timer = 2; this->heap->targetAngle = sub_080045DA((gRoomControls.origin_x + 0x108) * 0x10000 - super->x.WORD, (gRoomControls.origin_y + 0x88) * 0x10000 - super->y.WORD); this->heap->targetAngle = (u8) - (this->heap->targetAngle + 0x80); SoundReq(SFX_ED); } } } else { gPlayerState.flags &= ~PL_FROZEN; } } } if ((gRoomTransition.frameCount & 3) == 0) { CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 2, 0); } } else { if ((IS_FROZEN(this)) || (this->heap->targetAngle == this->angle.HALF.HI)) { if (this->unk_80 == 1) { this->unk_80 = 2; super->type2 = 2; this->timer = 0x2d; this->angularSpeed.HWORD = 0x100; this->heap->field_0x2 = 0; return; } this->unk_80 = 0; this->angularSpeed.HWORD = 0x100; this->heap->mouthObject->timer++; gPlayerEntity.spriteSettings.draw = 1; gPlayerEntity.flags &= ~ENT_COLLIDE; gPlayerEntity.collisionLayer = 1; sub_080792BC(0x400, (u32)(-(this->angle.HALF.HI + 0x80) * 0x1000000) >> 0x1b, 0x30); OctorokBoss_SetAttackTimer(this); SoundReq(SFX_EF); return; } this->timer = 2; } if (this->timer == 0) { this->unk_74 = this->unk_76; super->type2 = 0; OctorokBoss_SetAttackTimer(this); } else { this->timer--; if ((gPlayerState.flags == PL_FROZEN) && (this->timer == 0x3c)) { tmp = sub_080045DA(gPlayerEntity.x.WORD - super->x.WORD, gPlayerEntity.y.WORD - super->y.WORD); if ((u8)((tmp - ((u8) - this->angle.HALF.HI ^ 0x80))) > 0x80) { this->heap->targetAngle = this->angle.HALF.HI + 0x30; } else { this->heap->targetAngle = this->angle.HALF.HI - 0x30; } } if (IS_FROZEN(this) == FALSE) { if (this->heap->targetAngle != this->angle.HALF.HI) { this->heap->field_0x2 = 1; if ((u8)(this->heap->targetAngle - this->angle.HALF.HI) > 0x80) { this->angle.HWORD -= this->angularSpeed.HWORD; } else { this->angle.HWORD += this->angularSpeed.HWORD; } } else { this->heap->field_0x2 = IS_FROZEN(this); } } } } void OctorokBoss_ExecuteAttackSmoke(OctorokBossEntity* this) { if (this->timer == 0xff) { super->type2 = 0; OctorokBoss_SetAttackTimer(this); this->timer = 0x78; } else { this->timer++; ChangeLightLevel(-1); if ((gRoomTransition.frameCount & 3) == 0) { if ((gRoomTransition.frameCount & 7) == 0) { SoundReq(SFX_124); } CreateObjectWithParent(super, OCTOROK_BOSS_OBJECT, 3, 0); } } } void OctorokBoss_ExecuteAttackFreeze(OctorokBossEntity* this) { if (this->timer == 0) { this->heap->field_0x2 = 0; OctorokBoss_SetAttackTimer(this); } else { this->timer--; if ((gRoomTransition.frameCount & 3) == 0) { super->child = CreateProjectileWithParent(super, OCTOROK_BOSS_PROJECTILE, 2); if (super->child != NULL) { super->child->parent = super; super->child->direction = (u8) - this->angle.HALF.HI ^ 0x80; } } } } void OctorokBoss_Burning(OctorokBossEntity* this) { static void (*const OctorokBoss_Burning_SubActions[])(OctorokBossEntity*) = { OctorokBoss_Burning_SubAction0, OctorokBoss_Burning_SubAction1, OctorokBoss_Burning_SubAction2, }; OctorokBoss_Burning_SubActions[super->subAction](this); if (this->heap->fallingStonesTimer != 0) { this->heap->fallingStonesTimer--; if ((gRoomTransition.frameCount & 7) == 0) { // Falling stones CreateProjectileWithParent(super, OCTOROK_BOSS_PROJECTILE, 3); } } SetAffineInfo(super, this->unk_76, this->unk_74, this->angle.HWORD); } void OctorokBoss_Burning_SubAction0(OctorokBossEntity* this) { super->subAction = 1; super->speed = 0x200; super->collisions = COL_NONE; super->direction = (u8)(-this->angle.HALF.HI ^ 0x80U) >> 3; this->timer = 0x78; this->angularSpeed.HWORD = 0x180; this->heap->unk_0 = 4; sub_080368D8(this); OctorokBoss_Burning_SubAction1(this); } void OctorokBoss_Burning_SubAction1(OctorokBossEntity* this) { ProcessMovement0(super); if (super->collisions != COL_NONE) { super->subAction = 2; this->heap->targetAngle = this->angle.HALF.HI; if ((super->collisions & (COL_EAST_ANY | COL_WEST_ANY)) != COL_NONE) { this->heap->targetAngle = -this->heap->targetAngle; } if ((super->collisions & (COL_NORTH_ANY | COL_SOUTH_ANY)) != COL_NONE) { this->heap->targetAngle = -this->heap->targetAngle ^ 0x80; } super->knockbackDuration = 0x18; super->knockbackSpeed = 0x200; super->knockbackDirection = super->direction ^ 0x10; this->heap->fallingStonesTimer += 0x1e; InitScreenShake(0x1e, 0); SoundReq(SFX_158); SoundReq(SFX_14C); } if (this->timer-- == 0) { super->health = 0; } if ((gRoomTransition.frameCount & 0x1f) == 0) { SoundReq(SFX_159); } } void OctorokBoss_Burning_SubAction2(OctorokBossEntity* this) { if ((u32)(this->heap->targetAngle - this->angle.HALF.HI + 7) < 0xf) { super->subAction = 1; super->direction = ((u8) - this->angle.HALF.HI ^ 0x80) >> 3; super->collisions = COL_NONE; ProcessMovement0(super); } else { if ((u8)(this->heap->targetAngle - this->angle.HALF.HI) > 0x80) { this->angle.HWORD -= this->angularSpeed.HWORD; } else { this->angle.HWORD += this->angularSpeed.HWORD; } } } void sub_080368D8(OctorokBossEntity* this) { if (this->unk_80 != 0) { gPlayerEntity.spriteSettings.draw = 1; gPlayerEntity.flags |= ENT_COLLIDE; gPlayerEntity.collisionLayer = 1; } this->unk_76 = 0xa0; this->unk_74 = 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(OctorokBossEntity* this)) { u32 tmp; s8* tmp2; s8 tmp3; s32 a, b; // TODO regalloc in this awful structure here tmp2 = &this->timer; tmp = super->field_0xf + (u8)*tmp2; *tmp2 = tmp; if ((s8)super->field_0xf < 0) { a = tmp; b = -super->actionDelay; if (a << 0x18 < b << 0x18) { super->field_0xf = -super->field_0xf; } } else { if (((s8)*tmp2) > ((s32)super->actionDelay)) { super->field_0xf = -super->field_0xf; } } } END_NONMATCH /** Calculate tail angles regular */ ASM_FUNC("asm/non_matching/octorokBoss/sub_080369D0.inc", void sub_080369D0(OctorokBossEntity* this, s32 radius, s32 angleSpeed)) /** Calculate tail angles frozen sub_08036AF0 */ ASM_FUNC("asm/non_matching/octorokBoss/sub_08036AF0.inc", void sub_08036AF0(OctorokBossEntity* this, s32 radius, s32 angleSpeed)) void OctorokBoss_SetAttackTimer(OctorokBossEntity* this) { // These attack timers are only used if the boss isn't frozen and gRoomVars.field_0xc != 0x100 static const u8 OctorokBoss_AttackTimerWeights[] = { 48, 96, 80, 32, }; static const u8 OctorokBoss_AttackTimerValues[] = { 5, 10, 15, 30, }; // 5: don't attack static const u8 OctorokBoss_Phase4AttackPattern0[] = { ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, ATTACK_SMOKE, END_OF_ATTACK_PATTERN, }; static const u8 OctorokBoss_Phase4AttackPattern1[] = { ATTACK_VACUUM, NO_ATTACK, ATTACK_SPITROCK, ATTACK_SMOKE, END_OF_ATTACK_PATTERN, }; static const u8 OctorokBoss_Phase4AttackPattern2[] = { ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, NO_ATTACK, ATTACK_SPITROCK, END_OF_ATTACK_PATTERN, }; static const u8* const OctorokBoss_Phase4AttackPatterns[] = { OctorokBoss_Phase4AttackPattern0, OctorokBoss_Phase4AttackPattern1, OctorokBoss_Phase4AttackPattern2, OctorokBoss_Phase4AttackPattern1, }; const u8* attackPatterns; if ((this->bossPhase == 4) && (this->phase4AttackPattern != 0xff)) { super->subAction = ACTION1_ATTACK; super->type2 = 0; this->unk_80 = 0; attackPatterns = OctorokBoss_Phase4AttackPatterns[this->phase4AttackPattern]; this->currentAttack = attackPatterns[this->nextAttackIndex]; this->nextAttackIndex += 1; if (this->currentAttack != END_OF_ATTACK_PATTERN) { return; } // End of this pattern, choose the next pattern. this->phase4AttackPattern = 0xff; } super->subAction = ACTION1_WAITFORATTACK; if (IS_FROZEN(this)) { switch (this->bossPhase) { case 1: this->timer = 30; break; case 3: this->timer = 10; break; } } else { if ((s16)gRoomVars.lightLevel != 0x100) { // Constantly attack when its dark. this->timer = 1; } else { this->timer = OctorokBoss_AttackTimerValues[GetRandomByWeight(OctorokBoss_AttackTimerWeights)]; } } } void OctorokBoss_ResetToSubAction0(OctorokBossEntity* this) { this->angularSpeed.HWORD = 0x100; this->heap->unk_0 = 2; this->heap->rotation = NO_ROTATION; super->subAction = ACTION1_AIMTOWARDSPLAYER; } void OctorokBoss_WaitAnotherTurn(OctorokBossEntity* this) { static const u8 OctorokBoss_TurnTimeWeights[] = { 48, 96, 80, 32, }; static const u8 OctorokBoss_TurnTimeValues[] = { 70, 80, 90, 100, }; super->subAction = ACTION1_WAITFORTURN; super->speed = 0xc0; this->heap->unk_0 = 1; this->timer = OctorokBoss_TurnTimeValues[GetRandomByWeight(OctorokBoss_TurnTimeWeights)]; } void OctorokBoss_SetWaitTurnsForNextAttack(OctorokBossEntity* this) { static const u8 OctorokBoss_WaitForAttackTurnsWeights[] = { 64, 128, 64, }; static const u8 OctorokBoss_WaitForAttackTurnsValues[] = { 1, 2, 3, }; this->phase4AttackPattern = 0xff; if (IS_FROZEN(this) == FALSE) { if ((s16)gRoomVars.lightLevel != 0x100) { // Constantly attack when its dark. this->attackWaitTurns = IS_FROZEN(this); } else { this->attackWaitTurns = OctorokBoss_WaitForAttackTurnsValues[GetRandomByWeight(OctorokBoss_WaitForAttackTurnsWeights)]; } } else { this->attackWaitTurns = 0; } OctorokBoss_ResetToSubAction0(this); } void OctorokBoss_StartRegularAttack(OctorokBossEntity* this) { static const u8 OctorokBoss_NormalAttackPatterns[] = { ATTACK_VACUUM, ATTACK_SPITROCK, ATTACK_VACUUM, ATTACK_SPITROCK, ATTACK_SPITROCK, }; static const u8 OctorokBoss_FrozenAttackPatterns[] = { ATTACK_VACUUM, ATTACK_FREEZE, ATTACK_VACUUM, ATTACK_FREEZE, ATTACK_VACUUM, }; const u8* attackPattern; // Set us up for an attack super->subAction = ACTION1_ATTACK; super->type2 = 0; this->phase4AttackPattern = 0xff; this->unk_80 = 0; this->heap->field_0x2 = 0; if (this->bossPhase == 0) { // In phase 0 just spit rocks. this->currentAttack = ATTACK_SPITROCK; return; } if (this->bossPhase == 4) { if (((Random() & 3) == 0) || ((s16)gRoomVars.lightLevel != 0x100)) { super->subAction = ACTION1_SUBACTION2; super->speed = 0x200; this->timer = 0x3c; super->collisions = COL_NONE; this->heap->unk_0 = 4; SoundReq(SFX_159); return; } if (this->bossPhase == 4) { // Select a new attack pattern that is not the previous one. u32 rand; this->nextAttackIndex = 0; rand = Random() & 3; if (this->heap->phase4PrevAttackPattern != rand) { this->phase4AttackPattern = rand; } else { this->phase4AttackPattern = (this->heap->phase4PrevAttackPattern + 1) & 3; } this->heap->phase4PrevAttackPattern = this->phase4AttackPattern; OctorokBoss_SetAttackTimer(this); return; } } if (IS_FROZEN(this) == FALSE) { attackPattern = OctorokBoss_NormalAttackPatterns; } else { attackPattern = OctorokBoss_FrozenAttackPatterns; } this->currentAttack = attackPattern[this->nextAttackIndex]; if (++this->nextAttackIndex > 4) { this->nextAttackIndex = 0; } } void OctorokBoss_ChangePalette(OctorokBossEntity* this, u32 paletteIndex) { u32 i; ChangeObjPalette(&this->heap->mouthObject->base, paletteIndex); for (i = 0; i < 4; i++) { ChangeObjPalette(&this->heap->legObjects[i]->base, paletteIndex); } for (i = this->heap->tailCount - 1; i != 0; i--) { ChangeObjPalette(&this->heap->tailObjects[i]->base, paletteIndex); } } void sub_08036F60(OctorokBossEntity* this) { if ((super->subAction != 4) && (IS_FROZEN(this) == FALSE)) { this->unk_76 += (s8)super->actionDelay; this->unk_74 += (s8)super->actionDelay; if (this->unk_76 < 0x9c) { super->actionDelay = 1; } else { if (this->unk_76 > 0xa4) { super->actionDelay = 0xff; } } } } void OctorokBoss_StepSound(OctorokBossEntity* 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(OctorokBossEntity* this) { if ((IS_FROZEN(this)) && (this->unk_80 == 0)) { if (this->angularSpeed.HWORD != 0) { if (this->heap->rotation != NO_ROTATION) { if (this->heap->rotation == ROTATION_CW) { this->angle.HWORD += this->angularSpeed.HWORD; } else { this->angle.HWORD -= this->angularSpeed.HWORD; } } switch (this->bossPhase) { case 1: this->angularSpeed.HWORD--; break; case 3: this->angularSpeed.HWORD -= 2; break; } } } }