#include "z_en_crow.h" #include "array_count.h" #include "gfx_setupdl.h" #include "ichain.h" #include "rand.h" #include "sfx.h" #include "sys_matrix.h" #include "z_en_item00.h" #include "z_lib.h" #include "effect.h" #include "play_state.h" #include "player.h" #include "assets/objects/object_crow/object_crow.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_IGNORE_QUAKE | ACTOR_FLAG_CAN_ATTACH_TO_ARROW) void EnCrow_Init(Actor* thisx, PlayState* play); void EnCrow_Destroy(Actor* thisx, PlayState* play); void EnCrow_Update(Actor* thisx, PlayState* play); void EnCrow_Draw(Actor* thisx, PlayState* play); void EnCrow_SetupFlyIdle(EnCrow* this); void EnCrow_FlyIdle(EnCrow* this, PlayState* play); void EnCrow_Respawn(EnCrow* this, PlayState* play); void EnCrow_DiveAttack(EnCrow* this, PlayState* play); void EnCrow_Die(EnCrow* this, PlayState* play); void EnCrow_TurnAway(EnCrow* this, PlayState* play); void EnCrow_Damaged(EnCrow* this, PlayState* play); static Vec3f sZeroVecAccel = { 0.0f, 0.0f, 0.0f }; ActorProfile En_Crow_Profile = { /**/ ACTOR_EN_CROW, /**/ ACTORCAT_ENEMY, /**/ FLAGS, /**/ OBJECT_CROW, /**/ sizeof(EnCrow), /**/ EnCrow_Init, /**/ EnCrow_Destroy, /**/ EnCrow_Update, /**/ EnCrow_Draw, }; static ColliderJntSphElementInit sJntSphElementsInit[] = { { { ELEM_MATERIAL_UNK0, { 0xFFCFFFFF, HIT_SPECIAL_EFFECT_NONE, 0x08 }, { 0xFFCFFFFF, HIT_BACKLASH_NONE, 0x00 }, ATELEM_ON | ATELEM_SFX_HARD, ACELEM_ON, OCELEM_ON, }, { 1, { { 0, 0, 0 }, 20 }, 100 }, }, }; static ColliderJntSphInit sJntSphInit = { { COL_MATERIAL_HIT3, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_JNTSPH, }, ARRAY_COUNT(sJntSphElementsInit), sJntSphElementsInit, }; static CollisionCheckInfoInit sColChkInfoInit = { 1, 15, 30, 30 }; static DamageTable sDamageTable = { /* Deku nut */ DMG_ENTRY(0, 0x1), /* Deku stick */ DMG_ENTRY(2, 0x0), /* Slingshot */ DMG_ENTRY(1, 0x0), /* Explosive */ DMG_ENTRY(2, 0x0), /* Boomerang */ DMG_ENTRY(1, 0x0), /* Normal arrow */ DMG_ENTRY(2, 0x0), /* Hammer swing */ DMG_ENTRY(2, 0x0), /* Hookshot */ DMG_ENTRY(2, 0x0), /* Kokiri sword */ DMG_ENTRY(1, 0x0), /* Master sword */ DMG_ENTRY(2, 0x0), /* Giant's Knife */ DMG_ENTRY(4, 0x0), /* Fire arrow */ DMG_ENTRY(4, 0x2), /* Ice arrow */ DMG_ENTRY(2, 0x3), /* Light arrow */ DMG_ENTRY(2, 0x0), /* Unk arrow 1 */ DMG_ENTRY(4, 0x0), /* Unk arrow 2 */ DMG_ENTRY(2, 0x0), /* Unk arrow 3 */ DMG_ENTRY(2, 0x0), /* Fire magic */ DMG_ENTRY(4, 0x2), /* Ice magic */ DMG_ENTRY(0, 0x0), /* Light magic */ DMG_ENTRY(0, 0x0), /* Shield */ DMG_ENTRY(0, 0x0), /* Mirror Ray */ DMG_ENTRY(0, 0x0), /* Kokiri spin */ DMG_ENTRY(1, 0x0), /* Giant spin */ DMG_ENTRY(4, 0x0), /* Master spin */ DMG_ENTRY(2, 0x0), /* Kokiri jump */ DMG_ENTRY(2, 0x0), /* Giant jump */ DMG_ENTRY(8, 0x0), /* Master jump */ DMG_ENTRY(4, 0x0), /* Unknown 1 */ DMG_ENTRY(0, 0x0), /* Unblockable */ DMG_ENTRY(0, 0x0), /* Hammer jump */ DMG_ENTRY(4, 0x0), /* Unknown 2 */ DMG_ENTRY(0, 0x0), }; static u32 sDeathCount = 0; static InitChainEntry sInitChain[] = { ICHAIN_F32(cullingVolumeScale, 3000, ICHAIN_CONTINUE), ICHAIN_S8(naviEnemyId, NAVI_ENEMY_GUAY, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(gravity, -200, ICHAIN_CONTINUE), ICHAIN_F32(lockOnArrowOffset, 2000, ICHAIN_STOP), }; static Vec3f sHeadVec = { 2500.0f, 0.0f, 0.0f }; void EnCrow_Init(Actor* thisx, PlayState* play) { EnCrow* this = (EnCrow*)thisx; Actor_ProcessInitChain(&this->actor, sInitChain); SkelAnime_InitFlex(play, &this->skelAnime, &gGuaySkel, &gGuayFlyAnim, this->jointTable, this->morphTable, 9); Collider_InitJntSph(play, &this->collider); Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderElements); this->collider.elements[0].dim.worldSphere.radius = sJntSphInit.elements[0].dim.modelSphere.radius; CollisionCheck_SetInfo(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit); ActorShape_Init(&this->actor.shape, 2000.0f, ActorShadow_DrawCircle, 20.0f); sDeathCount = 0; EnCrow_SetupFlyIdle(this); } void EnCrow_Destroy(Actor* thisx, PlayState* play) { EnCrow* this = (EnCrow*)thisx; Collider_DestroyJntSph(play, &this->collider); } // Setup Action functions void EnCrow_SetupFlyIdle(EnCrow* this) { this->timer = 100; this->collider.base.acFlags |= AC_ON; this->actionFunc = EnCrow_FlyIdle; this->skelAnime.playSpeed = 1.0f; } void EnCrow_SetupDiveAttack(EnCrow* this) { this->timer = 300; this->actor.speed = 4.0f; this->skelAnime.playSpeed = 2.0f; this->actionFunc = EnCrow_DiveAttack; } void EnCrow_SetupDamaged(EnCrow* this, PlayState* play) { s32 i; f32 scale; Vec3f iceParticlePos; this->actor.speed *= Math_CosS(this->actor.world.rot.x); this->actor.velocity.y = 0.0f; Animation_Change(&this->skelAnime, &gGuayFlyAnim, 0.4f, 0.0f, 0.0f, ANIMMODE_LOOP_INTERP, -3.0f); scale = this->actor.scale.x * 100.0f; this->actor.world.pos.y += 20.0f * scale; this->actor.bgCheckFlags &= ~BGCHECKFLAG_GROUND; this->actor.shape.yOffset = 0.0f; this->actor.lockOnArrowOffset = 0.0f; Actor_PlaySfx(&this->actor, NA_SE_EN_KAICHO_DEAD); if (this->actor.colChkInfo.damageReaction == 3) { // Ice arrows Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_BLUE, 255, COLORFILTER_BUFFLAG_OPA, 40); for (i = 0; i < 8; i++) { iceParticlePos.x = ((i & 1 ? 7.0f : -7.0f) * scale) + this->actor.world.pos.x; iceParticlePos.y = ((i & 2 ? 7.0f : -7.0f) * scale) + this->actor.world.pos.y; iceParticlePos.z = ((i & 4 ? 7.0f : -7.0f) * scale) + this->actor.world.pos.z; EffectSsEnIce_SpawnFlyingVec3f(play, &this->actor, &iceParticlePos, 150, 150, 150, 250, 235, 245, 255, ((Rand_ZeroOne() * 0.15f) + 0.85f) * scale); } } else if (this->actor.colChkInfo.damageReaction == 2) { // Fire arrows and Din's Fire Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 40); for (i = 0; i < 4; i++) { EffectSsEnFire_SpawnVec3f(play, &this->actor, &this->actor.world.pos, 50.0f * scale, 0, 0, i); } } else { Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_RED, 255, COLORFILTER_BUFFLAG_OPA, 40); } if (this->actor.flags & ACTOR_FLAG_ATTACHED_TO_ARROW) { this->actor.speed = 0.0f; } this->collider.base.acFlags &= ~AC_ON; this->actor.flags |= ACTOR_FLAG_UPDATE_CULLING_DISABLED; this->actionFunc = EnCrow_Damaged; } void EnCrow_SetupDie(EnCrow* this) { this->actor.colorFilterTimer = 0; this->actionFunc = EnCrow_Die; } void EnCrow_SetupTurnAway(EnCrow* this) { this->timer = 100; this->actor.speed = 3.5f; this->aimRotX = -0x1000; this->aimRotY = this->actor.yawTowardsPlayer + 0x8000; this->skelAnime.playSpeed = 2.0f; Actor_SetColorFilter(&this->actor, COLORFILTER_COLORFLAG_BLUE, 255, COLORFILTER_BUFFLAG_OPA, 5); Actor_PlaySfx(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); this->actionFunc = EnCrow_TurnAway; } void EnCrow_SetupRespawn(EnCrow* this) { if (sDeathCount == 10) { this->actor.params = 1; sDeathCount = 0; this->collider.elements[0].dim.worldSphere.radius = sJntSphInit.elements[0].dim.modelSphere.radius * 0.03f * 100.0f; } else { this->actor.params = 0; this->collider.elements[0].dim.worldSphere.radius = sJntSphInit.elements[0].dim.modelSphere.radius; } Animation_PlayLoop(&this->skelAnime, &gGuayFlyAnim); Math_Vec3f_Copy(&this->actor.world.pos, &this->actor.home.pos); this->actor.shape.rot.x = 0; this->actor.shape.rot.z = 0; this->timer = 300; this->actor.shape.yOffset = 2000; this->actor.lockOnArrowOffset = 2000.0f; this->actor.draw = NULL; this->actionFunc = EnCrow_Respawn; } // Action functions void EnCrow_FlyIdle(EnCrow* this, PlayState* play) { Player* player = GET_PLAYER(play); s32 skelanimeUpdated; s16 var; SkelAnime_Update(&this->skelAnime); skelanimeUpdated = Animation_OnFrame(&this->skelAnime, 0.0f); this->actor.speed = (Rand_ZeroOne() * 1.5f) + 3.0f; if (this->actor.bgCheckFlags & BGCHECKFLAG_WALL) { this->aimRotY = this->actor.wallYaw; } else if (Actor_WorldDistXZToPoint(&this->actor, &this->actor.home.pos) > 300.0f) { this->aimRotY = Actor_WorldYawTowardPoint(&this->actor, &this->actor.home.pos); } if ((Math_SmoothStepToS(&this->actor.shape.rot.y, this->aimRotY, 5, 0x300, 0x10) == 0) && skelanimeUpdated && (Rand_ZeroOne() < 0.1f)) { var = Actor_WorldYawTowardPoint(&this->actor, &this->actor.home.pos) - this->actor.shape.rot.y; if (var > 0) { this->aimRotY += 0x1000 + (0x1000 * Rand_ZeroOne()); } else { this->aimRotY -= 0x1000 + (0x1000 * Rand_ZeroOne()); } Actor_PlaySfx(&this->actor, NA_SE_EN_KAICHO_CRY); } if (this->actor.depthInWater > -40.0f) { this->aimRotX = -0x1000; } else if (this->actor.world.pos.y < (this->actor.home.pos.y - 50.0f)) { this->aimRotX = -0x800 - (Rand_ZeroOne() * 0x800); } else if (this->actor.world.pos.y > (this->actor.home.pos.y + 50.0f)) { this->aimRotX = 0x800 + (Rand_ZeroOne() * 0x800); } if ((Math_SmoothStepToS(&this->actor.shape.rot.x, this->aimRotX, 10, 0x100, 8) == 0) && skelanimeUpdated && (Rand_ZeroOne() < 0.1f)) { if (this->actor.home.pos.y < this->actor.world.pos.y) { this->aimRotX -= (0x400 * Rand_ZeroOne()) + 0x400; } else { this->aimRotX += (0x400 * Rand_ZeroOne()) + 0x400; } this->aimRotX = CLAMP(this->aimRotX, -0x1000, 0x1000); } if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) { Math_ScaledStepToS(&this->actor.shape.rot.x, -0x100, 0x400); } if (this->timer != 0) { this->timer--; } if ((this->timer == 0) && (this->actor.xzDistToPlayer < 300.0f) && !(player->stateFlags1 & PLAYER_STATE1_23) && (this->actor.depthInWater < -40.0f) && (Player_GetMask(play) != PLAYER_MASK_SKULL)) { EnCrow_SetupDiveAttack(this); } } void EnCrow_DiveAttack(EnCrow* this, PlayState* play) { Player* player = GET_PLAYER(play); s32 facingPlayer; SkelAnime_Update(&this->skelAnime); if (this->timer != 0) { this->timer--; } facingPlayer = Actor_IsFacingPlayer(&this->actor, 0x2800); if (facingPlayer) { Vec3f pos; s16 target; pos.x = player->actor.world.pos.x; pos.y = player->actor.world.pos.y + 20.0f; pos.z = player->actor.world.pos.z; target = Actor_WorldPitchTowardPoint(&this->actor, &pos); if (target > 0x3000) { target = 0x3000; } Math_ApproachS(&this->actor.shape.rot.x, target, 2, 0x400); } else { Math_ApproachS(&this->actor.shape.rot.x, -0x1000, 2, 0x100); } if (facingPlayer || (this->actor.xzDistToPlayer > 80.0f)) { Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 4, 0xC00); } if ((this->timer == 0) || (Player_GetMask(play) == PLAYER_MASK_SKULL) || (this->collider.base.atFlags & AT_HIT) || (this->actor.bgCheckFlags & (BGCHECKFLAG_GROUND | BGCHECKFLAG_WALL)) || (player->stateFlags1 & PLAYER_STATE1_23) || (this->actor.depthInWater > -40.0f)) { if (this->collider.base.atFlags & AT_HIT) { this->collider.base.atFlags &= ~AT_HIT; Actor_PlaySfx(&this->actor, NA_SE_EN_KAICHO_ATTACK); } EnCrow_SetupFlyIdle(this); } } void EnCrow_Damaged(EnCrow* this, PlayState* play) { Math_StepToF(&this->actor.speed, 0.0f, 0.5f); this->actor.colorFilterTimer = 40; if (!(this->actor.flags & ACTOR_FLAG_ATTACHED_TO_ARROW)) { if (this->actor.colorFilterParams & 0x4000) { Math_ScaledStepToS(&this->actor.shape.rot.x, 0x4000, 0x200); this->actor.shape.rot.z += 0x1780; } if ((this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) || (this->actor.floorHeight == BGCHECK_Y_MIN)) { EffectSsDeadDb_Spawn(play, &this->actor.world.pos, &sZeroVecAccel, &sZeroVecAccel, this->actor.scale.x * 10000.0f, 0, 255, 255, 255, 255, 255, 0, 0, 1, 9, 1); EnCrow_SetupDie(this); } } } void EnCrow_Die(EnCrow* this, PlayState* play) { f32 step; if (this->actor.params != 0) { step = 0.006f; } else { step = 0.002f; } if (Math_StepToF(&this->actor.scale.x, 0.0f, step)) { if (this->actor.params == 0) { sDeathCount++; Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, COLLECTIBLE_DROP_RANDOM_PARAMS(COLLECTIBLE_DROP_TABLE_0, false)); } else { Item_DropCollectible(play, &this->actor.world.pos, ITEM00_RUPEE_RED); } EnCrow_SetupRespawn(this); } this->actor.scale.z = this->actor.scale.y = this->actor.scale.x; } void EnCrow_TurnAway(EnCrow* this, PlayState* play) { SkelAnime_Update(&this->skelAnime); if (this->actor.bgCheckFlags & BGCHECKFLAG_WALL) { this->aimRotY = this->actor.wallYaw; } else { this->aimRotY = this->actor.yawTowardsPlayer + 0x8000; } Math_ApproachS(&this->actor.shape.rot.y, this->aimRotY, 3, 0xC00); Math_ApproachS(&this->actor.shape.rot.x, this->aimRotX, 5, 0x100); if (this->timer != 0) { this->timer--; } if (this->timer == 0) { EnCrow_SetupFlyIdle(this); } } void EnCrow_Respawn(EnCrow* this, PlayState* play) { f32 target; if (this->timer != 0) { this->timer--; } if (this->timer == 0) { SkelAnime_Update(&this->skelAnime); this->actor.draw = EnCrow_Draw; if (this->actor.params != 0) { target = 0.03f; } else { target = 0.01f; } if (Math_StepToF(&this->actor.scale.x, target, target * 0.1f)) { this->actor.flags |= ACTOR_FLAG_ATTENTION_ENABLED; this->actor.flags &= ~ACTOR_FLAG_UPDATE_CULLING_DISABLED; this->actor.colChkInfo.health = 1; EnCrow_SetupFlyIdle(this); } this->actor.scale.z = this->actor.scale.y = this->actor.scale.x; } } void EnCrow_UpdateDamage(EnCrow* this, PlayState* play) { if (this->collider.base.acFlags & AC_HIT) { this->collider.base.acFlags &= ~AC_HIT; Actor_SetDropFlag(&this->actor, &this->collider.elements[0].base, true); if ((this->actor.colChkInfo.damageReaction != 0) || (this->actor.colChkInfo.damage != 0)) { if (this->actor.colChkInfo.damageReaction == 1) { // Deku Nuts EnCrow_SetupTurnAway(this); } else { Actor_ApplyDamage(&this->actor); this->actor.flags &= ~ACTOR_FLAG_ATTENTION_ENABLED; Enemy_StartFinishingBlow(play, &this->actor); EnCrow_SetupDamaged(this, play); } } } } void EnCrow_Update(Actor* thisx, PlayState* play) { EnCrow* this = (EnCrow*)thisx; f32 pad; f32 height; f32 scale; EnCrow_UpdateDamage(this, play); this->actionFunc(this, play); scale = this->actor.scale.x * 100.0f; this->actor.world.rot.y = this->actor.shape.rot.y; this->actor.world.rot.x = -this->actor.shape.rot.x; if (this->actionFunc != EnCrow_Respawn) { if (this->actor.colChkInfo.health != 0) { height = 20.0f * scale; Actor_MoveXYZ(&this->actor); } else { height = 0.0f; Actor_MoveXZGravity(&this->actor); } Actor_UpdateBgCheckInfo(play, &this->actor, 12.0f * scale, 25.0f * scale, 50.0f * scale, UPDBGCHECKINFO_FLAG_0 | UPDBGCHECKINFO_FLAG_1 | UPDBGCHECKINFO_FLAG_2); } else { height = 0.0f; } this->collider.elements[0].dim.worldSphere.center.x = this->actor.world.pos.x; this->collider.elements[0].dim.worldSphere.center.y = this->actor.world.pos.y + height; this->collider.elements[0].dim.worldSphere.center.z = this->actor.world.pos.z; if (this->actionFunc == EnCrow_DiveAttack) { CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); } if (this->collider.base.acFlags & AC_ON) { CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); } if (this->actionFunc != EnCrow_Respawn) { CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); } Actor_SetFocus(&this->actor, height); if (this->actor.colChkInfo.health != 0 && Animation_OnFrame(&this->skelAnime, 3.0f)) { Actor_PlaySfx(&this->actor, NA_SE_EN_KAICHO_FLUTTER); } } s32 EnCrow_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) { EnCrow* this = (EnCrow*)thisx; if (this->actor.colChkInfo.health != 0) { if (limbIndex == 7) { rot->y += 0xC00 * sinf(this->skelAnime.curFrame * (M_PI / 4)); } else if (limbIndex == 8) { rot->y += 0x1400 * sinf((this->skelAnime.curFrame + 2.5f) * (M_PI / 4)); } } return false; } void EnCrow_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { EnCrow* this = (EnCrow*)thisx; Vec3f* vec; if (limbIndex == 2) { Matrix_MultVec3f(&sHeadVec, &this->bodyPartsPos[0]); this->bodyPartsPos[0].y -= 20.0f; } else if ((limbIndex == 4) || (limbIndex == 6) || (limbIndex == 8)) { vec = &this->bodyPartsPos[(limbIndex >> 1) - 1]; Matrix_MultVec3f(&sZeroVecAccel, vec); vec->y -= 20.0f; } } void EnCrow_Draw(Actor* thisx, PlayState* play) { EnCrow* this = (EnCrow*)thisx; Gfx_SetupDL_25Opa(play->state.gfxCtx); SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, EnCrow_OverrideLimbDraw, EnCrow_PostLimbDraw, this); }