/* * File: z_en_mb.c * Overlay: ovl_En_Mb * Description: Moblins */ #include "z_en_mb.h" #include "objects/object_mb/object_mb.h" /* * This actor can have three behaviors: * - "Spear Guard" (variable -1): uses a spear, walks around home point, charges player if too close * - "Club" (variable 0): uses a club, stands still and smashes its club on the ground when the player approaches * - "Spear Patrol" (variable 0xPP00 PP=pathId): uses a spear, patrols following a path, charges */ #define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_2 | ACTOR_FLAG_4) typedef enum { /* -1 */ ENMB_TYPE_SPEAR_GUARD = -1, /* 0 */ ENMB_TYPE_CLUB, /* 1 */ ENMB_TYPE_SPEAR_PATROL } EnMbType; #define ENMB_ATTACK_NONE 0 #define ENMB_ATTACK_SPEAR 1 #define ENMB_ATTACK_CLUB_RIGHT 1 #define ENMB_ATTACK_CLUB_MIDDLE 2 #define ENMB_ATTACK_CLUB_LEFT 3 /* Spear and Club moblins use a different skeleton but the limbs are organized the same */ typedef enum { /* 1 */ ENMB_LIMB_ROOT = 1, /* 3 */ ENMB_LIMB_WAIST = 3, /* 6 */ ENMB_LIMB_CHEST = 6, /* 7 */ ENMB_LIMB_HEAD, /* 9 */ ENMB_LIMB_LSHOULDER = 9, /* 11 */ ENMB_LIMB_LFOREARM = 11, /* 12 */ ENMB_LIMB_LHAND, /* 14 */ ENMB_LIMB_RSHOULDER = 14, /* 16 */ ENMB_LIMB_RFOREARM = 16, /* 17 */ ENMB_LIMB_RHAND, /* 20 */ ENMB_LIMB_LTHIGH = 20, /* 21 */ ENMB_LIMB_LSHIN, /* 22 */ ENMB_LIMB_LFOOT, /* 25 */ ENMB_LIMB_RTHIGH = 25, /* 26 */ ENMB_LIMB_RSHIN, /* 27 */ ENMB_LIMB_RFOOT } EnMbLimb; void EnMb_Init(Actor* thisx, GlobalContext* globalCtx); void EnMb_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnMb_Update(Actor* thisx, GlobalContext* globalCtx); void EnMb_Draw(Actor* thisx, GlobalContext* globalCtx); const ActorInit En_Mb_InitVars = { ACTOR_EN_MB, ACTORCAT_ENEMY, FLAGS, OBJECT_MB, sizeof(EnMb), (ActorFunc)EnMb_Init, (ActorFunc)EnMb_Destroy, (ActorFunc)EnMb_Update, (ActorFunc)EnMb_Draw, NULL, }; void EnMb_SetupSpearPatrolTurnTowardsWaypoint(EnMb* this, GlobalContext* globalCtx); void EnMb_SetupClubWaitPlayerNear(EnMb* this); void EnMb_SpearGuardLookAround(EnMb* this, GlobalContext* globalCtx); void EnMb_SetupSpearGuardLookAround(EnMb* this); void EnMb_SetupSpearDamaged(EnMb* this); void EnMb_SpearGuardWalk(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearGuardPrepareAndCharge(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearPatrolPrepareAndCharge(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearEndChargeQuick(EnMb* this, GlobalContext* globalCtx); void EnMb_Stunned(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubDead(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubDamagedWhileKneeling(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubWaitPlayerNear(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubAttack(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearDead(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearDamaged(EnMb* this, GlobalContext* globalCtx); void EnMb_SetupSpearDead(EnMb* this); void EnMb_SpearPatrolTurnTowardsWaypoint(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearPatrolWalkTowardsWaypoint(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearPatrolEndCharge(EnMb* this, GlobalContext* globalCtx); void EnMb_SpearPatrolImmediateCharge(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubWaitAfterAttack(EnMb* this, GlobalContext* globalCtx); void EnMb_ClubDamaged(EnMb* this, GlobalContext* globalCtx); static ColliderCylinderInit sHitboxInit = { { COLTYPE_HIT0, AT_NONE, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_2, COLSHAPE_CYLINDER, }, { ELEMTYPE_UNK1, { 0x00000000, 0x00, 0x00 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_NONE, BUMP_ON, OCELEM_ON, }, { 20, 70, 0, { 0, 0, 0 } }, }; static ColliderTrisElementInit sFrontShieldingTrisInit[2] = { { { ELEMTYPE_UNK2, { 0x00000000, 0x00, 0x00 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_NONE, BUMP_ON | BUMP_HOOKABLE | BUMP_NO_AT_INFO, OCELEM_NONE, }, { { { -10.0f, 14.0f, 2.0f }, { -10.0f, -6.0f, 2.0f }, { 9.0f, 14.0f, 2.0f } } }, }, { { ELEMTYPE_UNK2, { 0x00000000, 0x00, 0x00 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_NONE, BUMP_ON | BUMP_HOOKABLE | BUMP_NO_AT_INFO, OCELEM_NONE, }, { { { -10.0f, -6.0f, 2.0f }, { 9.0f, -6.0f, 2.0f }, { 9.0f, 14.0f, 2.0f } } }, }, }; static ColliderTrisInit sFrontShieldingInit = { { COLTYPE_METAL, AT_NONE, AC_ON | AC_HARD | AC_TYPE_PLAYER, OC1_NONE, OC2_NONE, COLSHAPE_TRIS, }, 2, sFrontShieldingTrisInit, }; static ColliderQuadInit sAttackColliderInit = { { COLTYPE_NONE, AT_ON | AT_TYPE_ENEMY, AC_NONE, OC1_NONE, OC2_NONE, COLSHAPE_QUAD, }, { ELEMTYPE_UNK0, { 0xFFCFFFFF, 0x00, 0x08 }, { 0x00000000, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_NONE, OCELEM_NONE, }, { { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } } }, }; typedef enum { /* 0x0 */ ENMB_DMGEFF_IGNORE, /* 0x1 */ ENMB_DMGEFF_STUN, /* 0x5 */ ENMB_DMGEFF_FREEZE = 0x5, /* 0x6 */ ENMB_DMGEFF_STUN_ICE, /* 0xF */ ENMB_DMGEFF_DEFAULT = 0xF } EnMbDamageEffect; static DamageTable sSpearMoblinDamageTable = { /* Deku nut */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Deku stick */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Slingshot */ DMG_ENTRY(1, ENMB_DMGEFF_DEFAULT), /* Explosive */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Boomerang */ DMG_ENTRY(0, ENMB_DMGEFF_STUN), /* Normal arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Hammer swing */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Hookshot */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Kokiri sword */ DMG_ENTRY(1, ENMB_DMGEFF_DEFAULT), /* Master sword */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Giant's Knife */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Fire arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Ice arrow */ DMG_ENTRY(4, ENMB_DMGEFF_STUN_ICE), /* Light arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Unk arrow 1 */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unk arrow 2 */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Unk arrow 3 */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Fire magic */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Ice magic */ DMG_ENTRY(3, ENMB_DMGEFF_STUN_ICE), /* Light magic */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Shield */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Mirror Ray */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Kokiri spin */ DMG_ENTRY(1, ENMB_DMGEFF_DEFAULT), /* Giant spin */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Master spin */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Kokiri jump */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Giant jump */ DMG_ENTRY(8, ENMB_DMGEFF_DEFAULT), /* Master jump */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unknown 1 */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Unblockable */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Hammer jump */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unknown 2 */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), }; static DamageTable sClubMoblinDamageTable = { /* Deku nut */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Deku stick */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Slingshot */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Explosive */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Boomerang */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Normal arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Hammer swing */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Hookshot */ DMG_ENTRY(0, ENMB_DMGEFF_STUN), /* Kokiri sword */ DMG_ENTRY(1, ENMB_DMGEFF_DEFAULT), /* Master sword */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Giant's Knife */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Fire arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Ice arrow */ DMG_ENTRY(4, ENMB_DMGEFF_STUN_ICE), /* Light arrow */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Unk arrow 1 */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unk arrow 2 */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Unk arrow 3 */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Fire magic */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Ice magic */ DMG_ENTRY(3, ENMB_DMGEFF_STUN_ICE), /* Light magic */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Shield */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Mirror Ray */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Kokiri spin */ DMG_ENTRY(1, ENMB_DMGEFF_DEFAULT), /* Giant spin */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Master spin */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Kokiri jump */ DMG_ENTRY(2, ENMB_DMGEFF_DEFAULT), /* Giant jump */ DMG_ENTRY(8, ENMB_DMGEFF_DEFAULT), /* Master jump */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unknown 1 */ DMG_ENTRY(0, ENMB_DMGEFF_FREEZE), /* Unblockable */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), /* Hammer jump */ DMG_ENTRY(4, ENMB_DMGEFF_DEFAULT), /* Unknown 2 */ DMG_ENTRY(0, ENMB_DMGEFF_IGNORE), }; static InitChainEntry sInitChain[] = { ICHAIN_S8(naviEnemyId, 0x4A, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(gravity, -1000, ICHAIN_CONTINUE), ICHAIN_F32(targetArrowOffset, 5300, ICHAIN_STOP), }; void EnMb_SetupAction(EnMb* this, EnMbActionFunc actionFunc) { this->actionFunc = actionFunc; } void EnMb_Init(Actor* thisx, GlobalContext* globalCtx) { EnMb* this = (EnMb*)thisx; s32 pad; Player* player = GET_PLAYER(globalCtx); s16 relYawFromPlayer; Actor_ProcessInitChain(&this->actor, sInitChain); ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 46.0f); this->actor.colChkInfo.mass = MASS_IMMOVABLE; this->actor.colChkInfo.damageTable = &sSpearMoblinDamageTable; Collider_InitCylinder(globalCtx, &this->hitbox); Collider_SetCylinder(globalCtx, &this->hitbox, &this->actor, &sHitboxInit); Collider_InitTris(globalCtx, &this->frontShielding); Collider_SetTris(globalCtx, &this->frontShielding, &this->actor, &sFrontShieldingInit, this->frontShieldingTris); Collider_InitQuad(globalCtx, &this->attackCollider); Collider_SetQuad(globalCtx, &this->attackCollider, &this->actor, &sAttackColliderInit); switch (this->actor.params) { case ENMB_TYPE_SPEAR_GUARD: SkelAnime_InitFlex(globalCtx, &this->skelAnime, &gEnMbSpearSkel, &gEnMbSpearStandStillAnim, this->jointTable, this->morphTable, 28); this->actor.colChkInfo.health = 2; this->actor.colChkInfo.mass = MASS_HEAVY; this->maxHomeDist = 1000.0f; this->playerDetectionRange = 1750.0f; EnMb_SetupSpearGuardLookAround(this); break; case ENMB_TYPE_CLUB: SkelAnime_InitFlex(globalCtx, &this->skelAnime, &gEnMbClubSkel, &gEnMbClubStandStillClubDownAnim, this->jointTable, this->morphTable, 28); this->actor.colChkInfo.health = 6; this->actor.colChkInfo.mass = MASS_IMMOVABLE; this->actor.colChkInfo.damageTable = &sClubMoblinDamageTable; Actor_SetScale(&this->actor, 0.02f); this->hitbox.dim.height = 170; this->hitbox.dim.radius = 45; this->actor.uncullZoneForward = 4000.0f; this->actor.uncullZoneScale = 800.0f; this->actor.uncullZoneDownward = 1800.0f; this->playerDetectionRange = 710.0f; this->attackCollider.info.toucher.dmgFlags = 0x20000000; relYawFromPlayer = this->actor.world.rot.y - Math_Vec3f_Yaw(&this->actor.world.pos, &player->actor.world.pos); if (ABS(relYawFromPlayer) > 0x4000) { this->actor.world.rot.y = thisx->world.rot.y + 0x8000; this->actor.shape.rot.y = thisx->world.rot.y; this->actor.world.pos.z = thisx->world.pos.z + 600.0f; } ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawFeet, 90.0f); this->actor.flags &= ~ACTOR_FLAG_0; this->actor.naviEnemyId += 1; EnMb_SetupClubWaitPlayerNear(this); break; default: /* Spear Patrol */ SkelAnime_InitFlex(globalCtx, &this->skelAnime, &gEnMbSpearSkel, &gEnMbSpearStandStillAnim, this->jointTable, this->morphTable, 28); Actor_SetScale(&this->actor, 0.014f); this->path = (thisx->params & 0xFF00) >> 8; this->actor.params = ENMB_TYPE_SPEAR_PATROL; this->waypoint = 0; this->actor.colChkInfo.health = 1; this->actor.colChkInfo.mass = MASS_HEAVY; this->maxHomeDist = 350.0f; this->playerDetectionRange = 1750.0f; this->actor.flags &= ~ACTOR_FLAG_0; EnMb_SetupSpearPatrolTurnTowardsWaypoint(this, globalCtx); break; } } void EnMb_Destroy(Actor* thisx, GlobalContext* globalCtx) { EnMb* this = (EnMb*)thisx; Collider_DestroyTris(globalCtx, &this->frontShielding); Collider_DestroyCylinder(globalCtx, &this->hitbox); Collider_DestroyQuad(globalCtx, &this->attackCollider); } void EnMb_FaceWaypoint(EnMb* this, GlobalContext* globalCtx) { s16 yawToWaypoint = Math_Vec3f_Yaw(&this->actor.world.pos, &this->waypointPos); this->actor.shape.rot.y = yawToWaypoint; this->actor.world.rot.y = yawToWaypoint; } void EnMb_NextWaypoint(EnMb* this, GlobalContext* globalCtx) { Path* path; Vec3s* waypointPos; path = &globalCtx->setupPathList[this->path]; if (this->waypoint == 0) { this->direction = 1; } else if (this->waypoint == (s8)(path->count - 1)) { this->direction = -1; } this->waypoint += this->direction; waypointPos = (Vec3s*)SEGMENTED_TO_VIRTUAL(path->points) + this->waypoint; this->waypointPos.x = waypointPos->x; this->waypointPos.y = waypointPos->y; this->waypointPos.z = waypointPos->z; } /** * Checks if the player is in a 800*74 units XZ area centered on this actor, * the area being directed along its line of sight snapped to a cardinal angle. * Note: the longest corridor in Sacred Forest Meadows is 800 units long, * and they all are 100 units wide. */ s32 EnMb_IsPlayerInCorridor(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); f32 xFromPlayer; f32 zFromPlayer; f32 cos; f32 sin; f32 xFromPlayerAbs; f32 zFromPlayerAbs; s16 alignedYaw = 0; if ((this->actor.world.rot.y < -0x62A2) || (this->actor.world.rot.y > 0x62A2)) { alignedYaw = -0x8000; } else if (this->actor.world.rot.y < -0x20E0) { alignedYaw = -0x4000; } else if (this->actor.world.rot.y > 0x20E0) { alignedYaw = 0x4000; } cos = Math_CosS(alignedYaw); sin = Math_SinS(alignedYaw); cos = ABS(cos); sin = ABS(sin); xFromPlayer = this->actor.world.pos.x - player->actor.world.pos.x; zFromPlayer = this->actor.world.pos.z - player->actor.world.pos.z; xFromPlayerAbs = ABS(xFromPlayer); if (xFromPlayerAbs < (cos * 37.0f + sin * 400.0f)) { zFromPlayerAbs = ABS(zFromPlayer); if (zFromPlayerAbs < (sin * 37.0f + cos * 400.0f)) { return true; } } return false; } void EnMb_FindWaypointTowardsPlayer(EnMb* this, GlobalContext* globalCtx) { Path* path = &globalCtx->setupPathList[this->path]; s16 yawToWaypoint; Vec3f waypointPosF; Vec3s* waypointPosS; s16 yawPlayerToWaypoint; s32 i; s32 waypoint; for (waypoint = 0, i = path->count - 1; i >= 0; i--, waypoint++) { waypointPosS = (Vec3s*)SEGMENTED_TO_VIRTUAL(path->points) + waypoint; waypointPosF.x = waypointPosS->x; waypointPosF.y = waypointPosS->y; waypointPosF.z = waypointPosS->z; yawToWaypoint = Math_Vec3f_Yaw(&this->actor.world.pos, &waypointPosF); yawPlayerToWaypoint = yawToWaypoint - this->actor.yawTowardsPlayer; if (ABS(yawPlayerToWaypoint) <= 0x1770) { this->actor.world.rot.y = yawToWaypoint; if (waypoint == this->waypoint) { this->direction = -this->direction; } this->waypointPos = waypointPosF; this->waypoint = waypoint; break; } } } void EnMb_SetupSpearGuardLookAround(EnMb* this) { Animation_MorphToLoop(&this->skelAnime, &gEnMbSpearLookLeftAndRightAnim, -4.0f); this->actor.speedXZ = 0.0f; this->timer1 = Rand_S16Offset(30, 50); this->state = ENMB_STATE_IDLE; EnMb_SetupAction(this, EnMb_SpearGuardLookAround); } void EnMb_SetupClubWaitPlayerNear(EnMb* this) { Animation_PlayLoop(&this->skelAnime, &gEnMbClubStandStillClubDownAnim); this->actor.speedXZ = 0.0f; this->timer1 = Rand_S16Offset(30, 50); this->state = ENMB_STATE_IDLE; EnMb_SetupAction(this, EnMb_ClubWaitPlayerNear); } void EnMb_SetupSpearPatrolTurnTowardsWaypoint(EnMb* this, GlobalContext* globalCtx) { Animation_MorphToLoop(&this->skelAnime, &gEnMbSpearLookLeftAndRightAnim, -4.0f); this->actor.speedXZ = 0.0f; this->timer1 = Rand_S16Offset(40, 80); this->state = ENMB_STATE_IDLE; EnMb_NextWaypoint(this, globalCtx); EnMb_SetupAction(this, EnMb_SpearPatrolTurnTowardsWaypoint); } void EnMb_SetupSpearGuardWalk(EnMb* this) { Animation_Change(&this->skelAnime, &gEnMbSpearWalkAnim, 0.0f, 0.0f, Animation_GetLastFrame(&gEnMbSpearWalkAnim), ANIMMODE_LOOP, -4.0f); this->actor.speedXZ = 0.59999996f; this->timer1 = Rand_S16Offset(50, 70); this->unk_332 = 1; this->state = ENMB_STATE_WALK; EnMb_SetupAction(this, EnMb_SpearGuardWalk); } void EnMb_SetupSpearPatrolWalkTowardsWaypoint(EnMb* this) { f32 frameCount = Animation_GetLastFrame(&gEnMbSpearWalkAnim); this->actor.speedXZ = 0.59999996f; this->timer1 = Rand_S16Offset(50, 70); this->unk_332 = 1; this->state = ENMB_STATE_WALK; Animation_Change(&this->skelAnime, &gEnMbSpearWalkAnim, 0.0f, 0.0f, frameCount, ANIMMODE_LOOP_INTERP, -4.0f); EnMb_SetupAction(this, EnMb_SpearPatrolWalkTowardsWaypoint); } void EnMb_SetupSpearPrepareAndCharge(EnMb* this) { f32 frameCount = Animation_GetLastFrame(&gEnMbSpearPrepareChargeAnim); Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbSpearPrepareChargeAnim, -4.0f); this->state = ENMB_STATE_ATTACK; this->actor.speedXZ = 0.0f; this->timer3 = (s16)frameCount + 6; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_SPEAR_AT); if (this->actor.params == ENMB_TYPE_SPEAR_GUARD) { EnMb_SetupAction(this, EnMb_SpearGuardPrepareAndCharge); } else { EnMb_SetupAction(this, EnMb_SpearPatrolPrepareAndCharge); } } void EnMb_SetupSpearPatrolImmediateCharge(EnMb* this) { Animation_PlayLoop(&this->skelAnime, &gEnMbSpearChargeAnim); Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_ATTACK); this->attack = ENMB_ATTACK_SPEAR; this->state = ENMB_STATE_ATTACK; this->timer3 = 3; this->actor.speedXZ = 10.0f; EnMb_SetupAction(this, EnMb_SpearPatrolImmediateCharge); } void EnMb_SetupClubAttack(EnMb* this) { f32 frames = Animation_GetLastFrame(&gEnMbClubLiftClubAnim); s16 relYawFromPlayer; this->state = ENMB_STATE_ATTACK; Animation_Change(&this->skelAnime, &gEnMbClubLiftClubAnim, 3.0f, 0.0f, frames, ANIMMODE_ONCE_INTERP, 0.0f); this->timer3 = 1; relYawFromPlayer = this->actor.world.rot.y - this->actor.yawTowardsPlayer; if (ABS(relYawFromPlayer) <= 0x258) { this->attack = ENMB_ATTACK_CLUB_MIDDLE; } else if (relYawFromPlayer >= 0) { this->attack = ENMB_ATTACK_CLUB_RIGHT; } else { this->attack = ENMB_ATTACK_CLUB_LEFT; } EnMb_SetupAction(this, EnMb_ClubAttack); } void EnMb_SetupSpearEndChargeQuick(EnMb* this) { Animation_PlayOnce(&this->skelAnime, &gEnMbSpearSlowDownAnim); this->state = ENMB_STATE_ATTACK_END; this->timer1 = 0; this->timer3 = 5; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_SLIDE); EnMb_SetupAction(this, EnMb_SpearEndChargeQuick); } void EnMb_SetupSpearPatrolEndCharge(EnMb* this) { Animation_PlayOnce(&this->skelAnime, &gEnMbSpearSlowDownAnim); this->state = ENMB_STATE_ATTACK_END; this->actor.bgCheckFlags &= ~1; this->timer1 = 0; this->timer3 = 50; this->actor.speedXZ = -8.0f; this->actor.velocity.y = 6.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_SLIDE); EnMb_SetupAction(this, EnMb_SpearPatrolEndCharge); } void EnMb_SetupClubWaitAfterAttack(EnMb* this) { f32 frameCount = Animation_GetLastFrame(&gEnMbClubStandStillClubDownAnim); this->state = ENMB_STATE_ATTACK_END; Animation_Change(&this->skelAnime, &gEnMbClubStandStillClubDownAnim, 5.0f, 0.0f, frameCount, ANIMMODE_ONCE_INTERP, 0.0f); EnMb_SetupAction(this, EnMb_ClubWaitAfterAttack); } void EnMb_SetupClubDamaged(EnMb* this) { Animation_PlayOnce(&this->skelAnime, &gEnMbClubDamagedKneelAnim); this->state = ENMB_STATE_CLUB_KNEELING; this->timer1 = 0; this->timer3 = 20; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DEAD); EnMb_SetupAction(this, EnMb_ClubDamaged); } void EnMb_SetupClubDamagedWhileKneeling(EnMb* this) { f32 frames = Animation_GetLastFrame(&gEnMbClubBeatenKneelingAnim); this->state = ENMB_STATE_CLUB_KNEELING_DAMAGED; this->timer1 = 0; this->timer3 = 6; Animation_Change(&this->skelAnime, &gEnMbClubBeatenKneelingAnim, 1.0f, 4.0f, frames, ANIMMODE_ONCE_INTERP, 0.0f); EnMb_SetupAction(this, EnMb_ClubDamagedWhileKneeling); } void EnMb_SetupClubDead(EnMb* this) { Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbClubFallOnItsBackAnim, -4.0f); this->state = ENMB_STATE_CLUB_DEAD; this->actor.flags &= ~ACTOR_FLAG_0; this->hitbox.dim.height = 80; this->hitbox.dim.radius = 95; this->timer1 = 30; this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DEAD); EnMb_SetupAction(this, EnMb_ClubDead); } void EnMb_SetupStunned(EnMb* this) { this->state = ENMB_STATE_STUNNED; this->actor.speedXZ = 0.0f; Actor_SetColorFilter(&this->actor, 0, 0x78, 0, 0x50); if (this->damageEffect == ENMB_DMGEFF_STUN_ICE) { this->iceEffectTimer = 40; } else { if (this->actor.params != ENMB_TYPE_CLUB) { Animation_PlayOnceSetSpeed(&this->skelAnime, &gEnMbSpearDamagedFromFrontAnim, 0.0f); } Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); } EnMb_SetupAction(this, EnMb_Stunned); } void EnMb_Stunned(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.world.rot.y, 4.0f); this->attack = ENMB_ATTACK_NONE; } if (this->actor.colorFilterTimer == 0) { if (this->actor.params == ENMB_TYPE_CLUB) { if (this->actor.colChkInfo.health == 0) { EnMb_SetupClubDead(this); } else if (this->state == ENMB_STATE_CLUB_KNEELING) { /* dead code: the setup for this action sets state to something else */ EnMb_SetupClubDamagedWhileKneeling(this); } else { EnMb_SetupClubWaitPlayerNear(this); } } else { if (this->actor.colChkInfo.health == 0) { EnMb_SetupSpearDead(this); } else { EnMb_SetupSpearDamaged(this); } } } } void EnMb_SpearGuardLookAround(EnMb* this, GlobalContext* globalCtx) { s16 timer1; SkelAnime_Update(&this->skelAnime); if (this->timer1 == 0) { timer1 = 0; } else { this->timer1--; timer1 = this->timer1; } if (timer1 == 0 && Animation_OnFrame(&this->skelAnime, 0.0f)) { EnMb_SetupSpearGuardWalk(this); } } void EnMb_SpearPatrolTurnTowardsWaypoint(EnMb* this, GlobalContext* globalCtx) { s16 relYawFromPlayer; SkelAnime_Update(&this->skelAnime); if (this->timer1 == 0) { this->yawToWaypoint = Math_Vec3f_Yaw(&this->actor.world.pos, &this->waypointPos); if (Math_SmoothStepToS(&this->actor.shape.rot.y, this->yawToWaypoint, 1, 0x3E8, 0) == 0) { this->actor.world.rot.y = this->actor.shape.rot.y; EnMb_SetupSpearPatrolWalkTowardsWaypoint(this); } } else { this->timer1--; Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.home.rot.y, 1, 0x3E8, 0); } if (ABS(this->actor.yDistToPlayer) <= 20.0f && EnMb_IsPlayerInCorridor(this, globalCtx)) { relYawFromPlayer = this->actor.shape.rot.y - this->actor.yawTowardsPlayer; if (ABS(relYawFromPlayer) <= 0x4000 || (func_8002DDE4(globalCtx) && this->actor.xzDistToPlayer < 160.0f)) { EnMb_FindWaypointTowardsPlayer(this, globalCtx); Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_VOICE); EnMb_SetupSpearPrepareAndCharge(this); } } } /** * Slow down and resume walking. */ void EnMb_SpearEndChargeQuick(EnMb* this, GlobalContext* globalCtx) { s32 pad; Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 0.5f, 1.0f, 0.0f); if (this->actor.speedXZ > 1.0f) { Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 5.0f, 3, 4.0f, 100, 15, false); } if (SkelAnime_Update(&this->skelAnime)) { if (this->timer1 == 0) { this->timer3--; if (this->timer3 == 0) { /* Play the charge animation in reverse: let go of the spear and stand normally */ Animation_Change(&this->skelAnime, &gEnMbSpearPrepareChargeAnim, -1.0f, Animation_GetLastFrame(&gEnMbSpearPrepareChargeAnim), 0.0f, ANIMMODE_ONCE, 0.0f); this->timer1 = 1; this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_SPEAR_NORM); } } else { if (this->actor.params <= ENMB_TYPE_SPEAR_GUARD) { EnMb_SetupSpearGuardWalk(this); this->timer1 = this->timer2 = this->timer3 = 80; } else { EnMb_SetupSpearPatrolTurnTowardsWaypoint(this, globalCtx); } } } } void EnMb_ClubWaitAfterAttack(EnMb* this, GlobalContext* globalCtx) { this->attack = ENMB_ATTACK_NONE; if (SkelAnime_Update(&this->skelAnime)) { EnMb_SetupClubWaitPlayerNear(this); } } /** * Slow down, charge again if the player is near, or resume walking. */ void EnMb_SpearPatrolEndCharge(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); f32 lastFrame; s16 relYawFromPlayer; s16 yawPlayerToWaypoint; if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.world.rot.y, 4.0f); } if (this->actor.bgCheckFlags & 1) { Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 1.5f, 0.0f); if (this->actor.speedXZ > 1.0f) { Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 5.0f, 3, 4.0f, 100, 15, false); } if (this->timer1 != 0) { this->timer3--; if (this->timer3 == 0) { relYawFromPlayer = this->actor.shape.rot.y - this->actor.yawTowardsPlayer; if (ABS(this->actor.yDistToPlayer) <= 20.0f && EnMb_IsPlayerInCorridor(this, globalCtx) && ABS(relYawFromPlayer) <= 0x4000 && this->actor.xzDistToPlayer <= 200.0f) { EnMb_SetupSpearPrepareAndCharge(this); } else { lastFrame = Animation_GetLastFrame(&gEnMbSpearPrepareChargeAnim); /* Play the charge animation in reverse: let go of the spear and stand normally */ Animation_Change(&this->skelAnime, &gEnMbSpearPrepareChargeAnim, -1.0f, lastFrame, 0.0f, ANIMMODE_ONCE, 0.0f); this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_SPEAR_NORM); } } else { if (this->actor.xzDistToPlayer <= 160.0f) { this->actor.speedXZ = -5.0f; } else { this->actor.speedXZ = 0.0f; } } } if (SkelAnime_Update(&this->skelAnime)) { if (this->timer1 == 0) { lastFrame = Animation_GetLastFrame(&gEnMbSpearChargeAnim); Animation_Change(&this->skelAnime, &gEnMbSpearChargeAnim, 0.5f, 0.0f, lastFrame, ANIMMODE_LOOP_INTERP, 0.0f); this->timer1 = 1; } else { yawPlayerToWaypoint = Math_Vec3f_Yaw(&this->actor.world.pos, &this->waypointPos) - this->actor.yawTowardsPlayer; if (ABS(yawPlayerToWaypoint) <= 0x4000) { EnMb_SetupSpearPatrolTurnTowardsWaypoint(this, globalCtx); } else { EnMb_SetupSpearPatrolWalkTowardsWaypoint(this); } } } } } /** * Prepare charge (animation), then charge until the player isn't in front. */ void EnMb_SpearGuardPrepareAndCharge(EnMb* this, GlobalContext* globalCtx) { s32 prevFrame; s16 relYawTowardsPlayerAbs = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; if (relYawTowardsPlayerAbs < 0) { relYawTowardsPlayerAbs = -relYawTowardsPlayerAbs; } prevFrame = this->skelAnime.curFrame; if (SkelAnime_Update(&this->skelAnime)) { Animation_PlayLoop(&this->skelAnime, &gEnMbSpearChargeAnim); Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_ATTACK); } if (this->timer3 != 0) { this->timer3--; Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 1, 0xBB8, 0); } else { this->actor.speedXZ = 10.0f; this->attack = ENMB_ATTACK_SPEAR; Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 5.0f, 3, 4.0f, 100, 15, false); if (prevFrame != (s32)this->skelAnime.curFrame && ((s32)this->skelAnime.curFrame == 2 || (s32)this->skelAnime.curFrame == 6)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DASH); } } if (relYawTowardsPlayerAbs > 0x1388) { this->attack = ENMB_ATTACK_NONE; EnMb_SetupSpearEndChargeQuick(this); } } void EnMb_ClubAttack(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); s32 pad; Vec3f effSpawnPos; Vec3f effWhiteShockwaveDynamics = { 0.0f, 0.0f, 0.0f }; f32 flamesParams[] = { 18.0f, 18.0f, 0.0f }; s16 flamesUnused[] = { 20, 40, 0 }; s16 relYawTarget[] = { -0x9C4, 0, 0xDAC }; Math_SmoothStepToS(&this->actor.shape.rot.y, relYawTarget[this->attack - 1] + this->actor.world.rot.y, 1, 0x2EE, 0); if (this->attackCollider.base.atFlags & AT_HIT) { this->attackCollider.base.atFlags &= ~AT_HIT; if (this->attackCollider.base.at == &player->actor) { u8 prevPlayerInvincibilityTimer = player->invincibilityTimer; if (player->invincibilityTimer < 0) { if (player->invincibilityTimer <= -40) { player->invincibilityTimer = 0; } else { player->invincibilityTimer = 0; globalCtx->damagePlayer(globalCtx, -8); } } func_8002F71C(globalCtx, &this->actor, (650.0f - this->actor.xzDistToPlayer) * 0.04f + 4.0f, this->actor.world.rot.y, 8.0f); player->invincibilityTimer = prevPlayerInvincibilityTimer; } } if (SkelAnime_Update(&this->skelAnime)) { if (this->timer3 != 0) { this->timer3--; if (this->timer3 == 0) { f32 lastAnimFrame = Animation_GetLastFrame(&gEnMbClubStrikeDownAnim); Animation_Change(&this->skelAnime, &gEnMbClubStrikeDownAnim, 1.5f, 0.0f, lastAnimFrame, ANIMMODE_ONCE_INTERP, 0.0f); } } else { effSpawnPos = this->effSpawnPos; effSpawnPos.y = this->actor.floorHeight; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MONBLIN_HAM_LAND); func_800AA000(this->actor.xzDistToPlayer, 0xFF, 0x14, 0x96); EffectSsBlast_SpawnWhiteShockwave(globalCtx, &effSpawnPos, &effWhiteShockwaveDynamics, &effWhiteShockwaveDynamics); func_80033480(globalCtx, &effSpawnPos, 2.0f, 3, 0x12C, 0xB4, 1); Camera_AddQuake(&globalCtx->mainCamera, 2, 0x19, 5); func_800358DC(&this->actor, &effSpawnPos, &this->actor.world.rot, flamesParams, 20, flamesUnused, globalCtx, -1, 0); EnMb_SetupClubWaitAfterAttack(this); } } else { if (this->timer3 != 0 && this->skelAnime.curFrame == 6.0f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MONBLIN_HAM_UP); } else if (this->timer3 == 0 && this->skelAnime.curFrame == 3.0f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MONBLIN_HAM_DOWN); } } } /** * Prepare charge (animation), then charge to the end of the floor collision. */ void EnMb_SpearPatrolPrepareAndCharge(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); s32 prevFrame; s32 hasHitPlayer = false; s32 endCharge = !Actor_TestFloorInDirection(&this->actor, globalCtx, 110.0f, this->actor.world.rot.y); prevFrame = (s32)this->skelAnime.curFrame; if (SkelAnime_Update(&this->skelAnime)) { Animation_PlayLoop(&this->skelAnime, &gEnMbSpearChargeAnim); Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_ATTACK); } if (this->timer3 != 0) { this->timer3--; Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.world.rot.y, 1, 0x1F40, 0); endCharge = false; } else { this->actor.speedXZ = 10.0f; this->attack = ENMB_ATTACK_SPEAR; Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 5.0f, 3, 4.0f, 100, 15, false); if (prevFrame != (s32)this->skelAnime.curFrame && ((s32)this->skelAnime.curFrame == 2 || (s32)this->skelAnime.curFrame == 6)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DASH); } } if (this->attackCollider.base.atFlags & AT_HIT) { if (this->attackCollider.base.at == &player->actor) { if (!endCharge && !(player->stateFlags2 & 0x80)) { if (player->invincibilityTimer < 0) { if (player->invincibilityTimer < -39) { player->invincibilityTimer = 0; } else { player->invincibilityTimer = 0; globalCtx->damagePlayer(globalCtx, -8); } } if (!(this->attackCollider.base.atFlags & AT_BOUNCED)) { Audio_PlayActorSound2(&player->actor, NA_SE_PL_BODY_HIT); } if (globalCtx->grabPlayer(globalCtx, player)) { player->actor.parent = &this->actor; } } hasHitPlayer = true; } else { this->attackCollider.base.atFlags &= ~AT_HIT; } } if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->actor.world.pos.x = this->actor.world.pos.x + Math_CosS(this->actor.shape.rot.y) * 10.0f + Math_SinS(this->actor.shape.rot.y) * 89.0f; hasHitPlayer = true; player->actor.world.pos.z = this->actor.world.pos.z + Math_SinS(this->actor.shape.rot.y) * 10.0f + Math_CosS(this->actor.shape.rot.y) * 89.0f; player->unk_850 = 0; player->actor.speedXZ = 0.0f; player->actor.velocity.y = 0.0f; } if (endCharge) { if (hasHitPlayer || (player->stateFlags2 & 0x80)) { this->attackCollider.base.atFlags &= ~AT_HIT; if (player->stateFlags2 & 0x80) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.world.rot.y, 4.0f); } } this->attack = ENMB_ATTACK_NONE; this->actor.speedXZ = -10.0f; EnMb_SetupSpearPatrolEndCharge(this); } } /** * Charge and follow the path, until hitting the player or, after some time, reaching home. */ void EnMb_SpearPatrolImmediateCharge(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); s32 prevFrame; s32 hasHitPlayer = false; s32 endCharge = !Actor_TestFloorInDirection(&this->actor, globalCtx, 110.0f, this->actor.world.rot.y); prevFrame = (s32)this->skelAnime.curFrame; SkelAnime_Update(&this->skelAnime); Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 5.0f, 3, 4.0f, 100, 15, false); if (prevFrame != (s32)this->skelAnime.curFrame && ((s32)this->skelAnime.curFrame == 2 || (s32)this->skelAnime.curFrame == 6)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DASH); } if (this->attackCollider.base.atFlags & AT_HIT) { if (this->attackCollider.base.at == &player->actor) { if (!endCharge && !(player->stateFlags2 & 0x80)) { if (player->invincibilityTimer < 0) { if (player->invincibilityTimer <= -40) { player->invincibilityTimer = 0; } else { player->invincibilityTimer = 0; globalCtx->damagePlayer(globalCtx, -8); } } if (!(this->attackCollider.base.atFlags & AT_BOUNCED)) { Audio_PlayActorSound2(&player->actor, NA_SE_PL_BODY_HIT); } if (globalCtx->grabPlayer(globalCtx, player)) { player->actor.parent = &this->actor; } } hasHitPlayer = true; } else { this->attackCollider.base.atFlags &= ~AT_HIT; } } if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->actor.world.pos.x = this->actor.world.pos.x + Math_CosS(this->actor.shape.rot.y) * 10.0f + Math_SinS(this->actor.shape.rot.y) * 89.0f; hasHitPlayer = true; player->actor.world.pos.z = this->actor.world.pos.z + Math_SinS(this->actor.shape.rot.y) * 10.0f + Math_CosS(this->actor.shape.rot.y) * 89.0f; player->unk_850 = 0; player->actor.speedXZ = 0.0f; player->actor.velocity.y = 0.0f; } if (endCharge) { if (hasHitPlayer || (player->stateFlags2 & 0x80)) { this->attackCollider.base.atFlags &= ~AT_HIT; if (player->stateFlags2 & 0x80) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.world.rot.y, 4.0f); } this->attack = ENMB_ATTACK_NONE; this->actor.speedXZ = -10.0f; EnMb_SetupSpearPatrolEndCharge(this); this->timer3 = 1; } else { this->timer3--; EnMb_NextWaypoint(this, globalCtx); } } EnMb_FaceWaypoint(this, globalCtx); this->actor.shape.rot.y = this->actor.world.rot.y; if (this->timer3 == 0 && Math_Vec3f_DistXZ(&this->actor.home.pos, &this->actor.world.pos) < 80.0f) { this->attack = ENMB_ATTACK_NONE; EnMb_SetupSpearEndChargeQuick(this); } } void EnMb_ClubDamaged(EnMb* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime)) { if (this->timer3 != 0) { Animation_PlayOnce(&this->skelAnime, &gEnMbClubStandUpAnim); this->timer3 = 0; func_800AA000(this->actor.xzDistToPlayer, 0xFF, 0x14, 0x96); Camera_AddQuake(&globalCtx->mainCamera, 2, 25, 5); } else { EnMb_SetupClubWaitPlayerNear(this); } } } void EnMb_ClubDamagedWhileKneeling(EnMb* this, GlobalContext* globalCtx) { s32 pad; if (SkelAnime_Update(&this->skelAnime)) { if (this->timer3 != 0) { this->timer3--; if (this->timer3 == 0) { if (this->timer1 == 0) { Animation_Change(&this->skelAnime, &gEnMbClubStandUpAnim, 3.0f, 0.0f, Animation_GetLastFrame(&gEnMbClubStandUpAnim), ANIMMODE_ONCE_INTERP, 0.0f); this->timer1 = 1; this->timer3 = 6; } else { Animation_Change(&this->skelAnime, &gEnMbClubStandUpAnim, 3.0f, 0.0f, Animation_GetLastFrame(&gEnMbClubStandUpAnim), ANIMMODE_ONCE_INTERP, 0.0f); } } } else { EnMb_SetupClubWaitPlayerNear(this); } } } void EnMb_ClubDead(EnMb* this, GlobalContext* globalCtx) { Vec3f effPos; Vec3f effPosBase; effPos = this->actor.world.pos; effPos.x += Math_SinS(this->actor.shape.rot.y) * -70.0f; effPos.z += Math_CosS(this->actor.shape.rot.y) * -70.0f; Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); effPosBase = effPos; if (SkelAnime_Update(&this->skelAnime)) { if (this->timer1 > 0) { Vec3f effZeroVec = { 0.0f, 0.0f, 0.0f }; s32 i; this->timer1--; for (i = 4; i >= 0; i--) { effPos.x = Rand_CenteredFloat(240.0f) + effPosBase.x; effPos.y = Rand_CenteredFloat(15.0f) + (effPosBase.y + 20.0f); effPos.z = Rand_CenteredFloat(240.0f) + effPosBase.z; EffectSsDeadDb_Spawn(globalCtx, &effPos, &effZeroVec, &effZeroVec, 230, 7, 255, 255, 255, 255, 0, 255, 0, 1, 9, true); } } else { Item_DropCollectibleRandom(globalCtx, &this->actor, &effPos, 0xC0); Actor_Kill(&this->actor); } } else if ((s32)this->skelAnime.curFrame == 15 || (s32)this->skelAnime.curFrame == 22) { func_800AA000(this->actor.xzDistToPlayer, 0xFF, 0x14, 0x96); Actor_SpawnFloorDustRing(globalCtx, &this->actor, &effPos, 50.0f, 10, 3.0f, 400, 60, false); Audio_PlayActorSound2(&this->actor, NA_SE_EN_RIZA_DOWN); Camera_AddQuake(&globalCtx->mainCamera, 2, 25, 5); } } /** * Walk around the home point, face and charge the player if close. */ void EnMb_SpearGuardWalk(EnMb* this, GlobalContext* globalCtx) { s32 prevFrame; s32 beforeCurFrame; s32 pad1; s32 pad2; Player* player = GET_PLAYER(globalCtx); s16 relYawTowardsPlayer = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; s16 yawTowardsHome; f32 playSpeedAbs; relYawTowardsPlayer = ABS(relYawTowardsPlayer); Math_SmoothStepToF(&this->actor.speedXZ, 0.59999996f, 0.1f, 1.0f, 0.0f); this->skelAnime.playSpeed = this->actor.speedXZ; prevFrame = this->skelAnime.curFrame; SkelAnime_Update(&this->skelAnime); playSpeedAbs = ABS(this->skelAnime.playSpeed); beforeCurFrame = this->skelAnime.curFrame - playSpeedAbs; playSpeedAbs = ABS(this->skelAnime.playSpeed); if (this->timer3 == 0 && Math_Vec3f_DistXZ(&this->actor.home.pos, &player->actor.world.pos) < this->playerDetectionRange) { Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 1, 0x2EE, 0); this->actor.flags |= ACTOR_FLAG_0; if (this->actor.xzDistToPlayer < 500.0f && relYawTowardsPlayer < 0x1388) { EnMb_SetupSpearPrepareAndCharge(this); } } else { this->actor.flags &= ~ACTOR_FLAG_0; if (Math_Vec3f_DistXZ(&this->actor.world.pos, &this->actor.home.pos) > this->maxHomeDist || this->timer2 != 0) { yawTowardsHome = Math_Vec3f_Yaw(&this->actor.world.pos, &this->actor.home.pos); Math_SmoothStepToS(&this->actor.world.rot.y, yawTowardsHome, 1, 0x2EE, 0); } if (this->timer2 != 0) { this->timer2--; } if (this->timer3 != 0) { this->timer3--; } if (this->timer2 == 0) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_VOICE); } this->timer1--; if (this->timer1 == 0) { if (Rand_ZeroOne() > 0.7f) { this->timer1 = Rand_S16Offset(50, 70); this->timer2 = Rand_S16Offset(15, 40); } else { EnMb_SetupSpearGuardLookAround(this); } } } if (prevFrame != (s32)this->skelAnime.curFrame) { if ((beforeCurFrame <= 1 && prevFrame + (s32)playSpeedAbs >= 1) || (beforeCurFrame <= 20 && prevFrame + (s32)playSpeedAbs >= 20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_WALK); } } this->actor.shape.rot.y = this->actor.world.rot.y; } void EnMb_SpearPatrolWalkTowardsWaypoint(EnMb* this, GlobalContext* globalCtx) { s32 prevFrame; s32 beforeCurFrame; s16 relYawTowardsPlayer; f32 yDistToPlayerAbs; f32 playSpeedABS; if (Math_Vec3f_DistXZ(&this->waypointPos, &this->actor.world.pos) <= 8.0f || (Rand_ZeroOne() < 0.1f && Math_Vec3f_DistXZ(&this->actor.home.pos, &this->actor.world.pos) <= 4.0f)) { EnMb_SetupSpearPatrolTurnTowardsWaypoint(this, globalCtx); } else { Math_SmoothStepToF(&this->actor.speedXZ, 0.59999996f, 0.1f, 1.0f, 0.0f); this->skelAnime.playSpeed = 2.0f * this->actor.speedXZ; } this->yawToWaypoint = Math_Vec3f_Yaw(&this->actor.world.pos, &this->waypointPos); Math_SmoothStepToS(&this->actor.world.rot.y, this->yawToWaypoint, 1, 0x5DC, 0); yDistToPlayerAbs = (this->actor.yDistToPlayer >= 0.0f) ? this->actor.yDistToPlayer : -this->actor.yDistToPlayer; if (yDistToPlayerAbs <= 20.0f && EnMb_IsPlayerInCorridor(this, globalCtx)) { relYawTowardsPlayer = (this->actor.shape.rot.y - this->actor.yawTowardsPlayer); if (ABS(relYawTowardsPlayer) <= 0x4000 || (func_8002DDE4(globalCtx) && this->actor.xzDistToPlayer < 160.0f)) { EnMb_FindWaypointTowardsPlayer(this, globalCtx); Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_VOICE); EnMb_SetupSpearPrepareAndCharge(this); return; } } if (this->timer2 != 0) { this->timer2--; } if (this->timer3 != 0) { this->timer3--; } if (this->timer2 == 0) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_VOICE); this->timer2 = Rand_S16Offset(30, 70); } prevFrame = this->skelAnime.curFrame; SkelAnime_Update(&this->skelAnime); playSpeedABS = ABS(this->skelAnime.playSpeed); beforeCurFrame = this->skelAnime.curFrame - playSpeedABS; playSpeedABS = (this->skelAnime.playSpeed >= 0.0f) ? this->skelAnime.playSpeed : -this->skelAnime.playSpeed; if (prevFrame != (s32)this->skelAnime.curFrame) { if ((beforeCurFrame <= 1 && (s32)playSpeedABS + prevFrame >= 1) || (beforeCurFrame <= 20 && (s32)playSpeedABS + prevFrame >= 20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_WALK); } } this->actor.shape.rot.y = this->actor.world.rot.y; } void EnMb_ClubWaitPlayerNear(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); s32 pad; s16 relYawFromPlayer = this->actor.world.rot.y - this->actor.yawTowardsPlayer; SkelAnime_Update(&this->skelAnime); if (Math_Vec3f_DistXZ(&this->actor.home.pos, &player->actor.world.pos) < this->playerDetectionRange && !(player->stateFlags1 & 0x4000000) && ABS(relYawFromPlayer) < 0x3E80) { EnMb_SetupClubAttack(this); } } void EnMb_SetupSpearDamaged(EnMb* this) { s16 relYawTowardsPlayer = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; if (ABS(relYawTowardsPlayer) <= 0x4000) { Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbSpearDamagedFromFrontAnim, -4.0f); this->actor.speedXZ = -8.0f; } else { Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbSpearDamagedFromBehindAnim, -4.0f); this->actor.speedXZ = 8.0f; } this->timer1 = 30; this->state = ENMB_STATE_SPEAR_SPEARPATH_DAMAGED; this->actor.shape.rot.y = this->actor.world.rot.y; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DEAD); EnMb_SetupAction(this, EnMb_SpearDamaged); } void EnMb_SpearDamaged(EnMb* this, GlobalContext* globalCtx) { Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); if (SkelAnime_Update(&this->skelAnime)) { if (this->actor.params <= ENMB_TYPE_SPEAR_GUARD) { EnMb_SetupSpearGuardLookAround(this); } else { EnMb_SetupSpearPatrolImmediateCharge(this); } } } void EnMb_SetupSpearDead(EnMb* this) { s16 relYawTowardsPlayer = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; if (ABS(relYawTowardsPlayer) <= 0x4000) { Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbSpearFallOnItsBackAnim, -4.0f); this->actor.speedXZ = -8.0f; } else { /* The gEnMbSpearFallFaceDownAnim animation was probably meant to be used here */ Animation_MorphToPlayOnce(&this->skelAnime, &gEnMbSpearFallOnItsBackAnim, -4.0f); this->actor.speedXZ = 8.0f; } this->actor.world.rot.y = this->actor.shape.rot.y; this->timer1 = 30; this->state = ENMB_STATE_SPEAR_SPEARPATH_DAMAGED; Audio_PlayActorSound2(&this->actor, NA_SE_EN_MORIBLIN_DEAD); this->actor.flags &= ~ACTOR_FLAG_0; EnMb_SetupAction(this, EnMb_SpearDead); } void EnMb_SpearDead(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 4.0f, this->actor.world.rot.y, 4.0f); this->attack = ENMB_ATTACK_NONE; } if (SkelAnime_Update(&this->skelAnime)) { if (this->timer1 > 0) { Vec3f zeroVec = { 0.0f, 0.0f, 0.0f }; s32 i; Vec3f effPos; this->actor.shape.shadowScale = 0.0f; this->timer1--; for (i = 4; i >= 0; i--) { effPos.x = Rand_CenteredFloat(110.0f) + this->actor.world.pos.x; effPos.y = Rand_CenteredFloat(15.0f) + (this->actor.world.pos.y + 20.0f); effPos.z = Rand_CenteredFloat(110.0f) + this->actor.world.pos.z; EffectSsDeadDb_Spawn(globalCtx, &effPos, &zeroVec, &zeroVec, 100, 7, 255, 255, 255, 255, 0, 255, 0, 1, 9, true); } } else { Item_DropCollectibleRandom(globalCtx, &this->actor, &this->actor.world.pos, 0xE0); Actor_Kill(&this->actor); } } } void EnMb_SpearUpdateAttackCollider(Actor* thisx, GlobalContext* globalCtx) { Vec3f quadModel0 = { 1000.0f, 1500.0f, 0.0f }; Vec3f quadModel1 = { -1000.0f, 1500.0f, 0.0f }; Vec3f quadModel2 = { 1000.0f, 1500.0f, 4500.0f }; Vec3f quadModel3 = { -1000.0f, 1500.0f, 4500.0f }; EnMb* this = (EnMb*)thisx; if (this->actor.params >= ENMB_TYPE_SPEAR_PATROL) { quadModel0.x += 2000.0f; quadModel0.z = -4000.0f; quadModel1.z = -4000.0f; quadModel2.x += 2000.0f; quadModel1.x -= 2000.0f; quadModel3.x -= 2000.0f; quadModel2.z += 4000.0f; quadModel3.z += 4000.0f; } Matrix_MultVec3f(&quadModel0, &this->attackCollider.dim.quad[1]); Matrix_MultVec3f(&quadModel1, &this->attackCollider.dim.quad[0]); Matrix_MultVec3f(&quadModel2, &this->attackCollider.dim.quad[3]); Matrix_MultVec3f(&quadModel3, &this->attackCollider.dim.quad[2]); Collider_SetQuadVertices(&this->attackCollider, &this->attackCollider.dim.quad[0], &this->attackCollider.dim.quad[1], &this->attackCollider.dim.quad[2], &this->attackCollider.dim.quad[3]); } void EnMb_ClubUpdateAttackCollider(Actor* thisx, GlobalContext* globalCtx) { static Vec3f quadModel[] = { { 1000.0f, 0.0f, 0.0f }, { 1000.0f, 0.0f, 0.0f }, { 1000.0f, -8000.0f, -1500.0f }, { 1000.0f, -9000.0f, 2000.0f } }; EnMb* this = (EnMb*)thisx; Matrix_MultVec3f(&quadModel[0], &this->attackCollider.dim.quad[1]); Matrix_MultVec3f(&quadModel[1], &this->attackCollider.dim.quad[0]); Matrix_MultVec3f(&quadModel[2], &this->attackCollider.dim.quad[3]); Matrix_MultVec3f(&quadModel[3], &this->attackCollider.dim.quad[2]); Collider_SetQuadVertices(&this->attackCollider, &this->attackCollider.dim.quad[0], &this->attackCollider.dim.quad[1], &this->attackCollider.dim.quad[2], &this->attackCollider.dim.quad[3]); } void EnMb_CheckColliding(EnMb* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); if (this->frontShielding.base.acFlags & AC_HIT) { this->frontShielding.base.acFlags &= ~(AC_HIT | AC_BOUNCED); this->hitbox.base.acFlags &= ~AC_HIT; } else if ((this->hitbox.base.acFlags & AC_HIT) && this->state >= ENMB_STATE_STUNNED) { this->hitbox.base.acFlags &= ~AC_HIT; if (this->actor.colChkInfo.damageEffect != ENMB_DMGEFF_IGNORE && this->actor.colChkInfo.damageEffect != ENMB_DMGEFF_FREEZE) { if ((player->stateFlags2 & 0x80) && player->actor.parent == &this->actor) { player->stateFlags2 &= ~0x80; player->actor.parent = NULL; player->unk_850 = 200; func_8002F71C(globalCtx, &this->actor, 6.0f, this->actor.world.rot.y, 6.0f); } this->damageEffect = this->actor.colChkInfo.damageEffect; this->attack = ENMB_ATTACK_NONE; Actor_SetDropFlag(&this->actor, &this->hitbox.info, false); if (this->actor.colChkInfo.damageEffect == ENMB_DMGEFF_STUN || this->actor.colChkInfo.damageEffect == ENMB_DMGEFF_STUN_ICE) { if (this->state != ENMB_STATE_STUNNED) { Actor_ApplyDamage(&this->actor); EnMb_SetupStunned(this); } } else { Actor_ApplyDamage(&this->actor); Actor_SetColorFilter(&this->actor, 0x4000, 0xFA, 0, 0xC); if (this->actor.params == ENMB_TYPE_CLUB) { if (this->actor.colChkInfo.health == 0) { EnMb_SetupClubDead(this); } else if (this->state != ENMB_STATE_CLUB_KNEELING) { EnMb_SetupClubDamaged(this); } } else { if (this->actor.colChkInfo.health == 0) { EnMb_SetupSpearDead(this); } else { EnMb_SetupSpearDamaged(this); } } } } } } void EnMb_Update(Actor* thisx, GlobalContext* globalCtx) { EnMb* this = (EnMb*)thisx; s32 pad; EnMb_CheckColliding(this, globalCtx); if (thisx->colChkInfo.damageEffect != ENMB_DMGEFF_FREEZE) { this->actionFunc(this, globalCtx); Actor_MoveForward(thisx); Actor_UpdateBgCheckInfo(globalCtx, thisx, 40.0f, 40.0f, 70.0f, 0x1D); Actor_SetFocus(thisx, thisx->scale.x * 4500.0f); Collider_UpdateCylinder(thisx, &this->hitbox); if (thisx->colChkInfo.health <= 0) { this->hitbox.dim.pos.x += Math_SinS(thisx->shape.rot.y) * (-4400.0f * thisx->scale.y); this->hitbox.dim.pos.z += Math_CosS(thisx->shape.rot.y) * (-4400.0f * thisx->scale.y); } CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->hitbox.base); if (this->state >= ENMB_STATE_STUNNED && (thisx->params == ENMB_TYPE_CLUB || this->state != ENMB_STATE_ATTACK)) { CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->hitbox.base); } if (this->state >= ENMB_STATE_IDLE) { CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->frontShielding.base); } if (this->attack > ENMB_ATTACK_NONE) { CollisionCheck_SetAT(globalCtx, &globalCtx->colChkCtx, &this->attackCollider.base); } } } void EnMb_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { static Vec3f unused = { 1100.0f, -700.0f, 0.0f }; static Vec3f feetPos = { 0.0f, 0.0f, 0.0f }; static Vec3f effSpawnPosModel = { 0.0f, -8000.0f, 0.0f }; static Vec3f zeroVec = { 0.0f, 0.0f, 0.0f }; s32 bodyPart = -1; EnMb* this = (EnMb*)thisx; Vec3f bodyPartPos; if (this->actor.params == ENMB_TYPE_CLUB) { if (limbIndex == ENMB_LIMB_LHAND) { Matrix_MultVec3f(&effSpawnPosModel, &this->effSpawnPos); if (this->attack > ENMB_ATTACK_NONE) { EnMb_ClubUpdateAttackCollider(&this->actor, globalCtx); } } Actor_SetFeetPos(&this->actor, limbIndex, ENMB_LIMB_LFOOT, &feetPos, ENMB_LIMB_RFOOT, &feetPos); } if (this->iceEffectTimer != 0) { switch (limbIndex) { case ENMB_LIMB_HEAD: bodyPart = 0; break; case ENMB_LIMB_LHAND: bodyPart = 1; break; case ENMB_LIMB_RHAND: bodyPart = 2; break; case ENMB_LIMB_LSHOULDER: bodyPart = 3; break; case ENMB_LIMB_RSHOULDER: bodyPart = 4; break; case ENMB_LIMB_CHEST: bodyPart = 5; break; case ENMB_LIMB_LTHIGH: bodyPart = 6; break; case ENMB_LIMB_RTHIGH: bodyPart = 7; break; case ENMB_LIMB_LFOOT: bodyPart = 8; break; case ENMB_LIMB_RFOOT: bodyPart = 9; break; } if (bodyPart >= 0) { Matrix_MultVec3f(&zeroVec, &bodyPartPos); this->bodyPartsPos[bodyPart].x = bodyPartPos.x; this->bodyPartsPos[bodyPart].y = bodyPartPos.y; this->bodyPartsPos[bodyPart].z = bodyPartPos.z; } } } void EnMb_Draw(Actor* thisx, GlobalContext* globalCtx) { static Vec3f frontShieldingTriModel0[] = { { 4000.0f, 7000.0f, 3500.0f }, { 4000.0f, 0.0f, 3500.0f }, { -4000.0f, 7000.0f, 3500.0f }, }; static Vec3f frontShieldingTriModel1[] = { { -4000.0f, 7000.0f, 3500.0f }, { -4000.0f, 0.0f, 3500.0f }, { 4000.0f, 0.0f, 3500.0f }, }; s32 i; f32 scale; Vec3f frontShieldingTri0[3]; Vec3f frontShieldingTri1[3]; s32 bodyPartIdx; EnMb* this = (EnMb*)thisx; func_80093D18(globalCtx->state.gfxCtx); SkelAnime_DrawFlexOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, NULL, EnMb_PostLimbDraw, thisx); if (thisx->params != ENMB_TYPE_CLUB) { if (this->attack > ENMB_ATTACK_NONE) { EnMb_SpearUpdateAttackCollider(thisx, globalCtx); } for (i = 0; i < 3; i++) { Matrix_MultVec3f(&frontShieldingTriModel0[i], &frontShieldingTri0[i]); Matrix_MultVec3f(&frontShieldingTriModel1[i], &frontShieldingTri1[i]); } Collider_SetTrisVertices(&this->frontShielding, 0, &frontShieldingTri0[0], &frontShieldingTri0[1], &frontShieldingTri0[2]); Collider_SetTrisVertices(&this->frontShielding, 1, &frontShieldingTri1[0], &frontShieldingTri1[1], &frontShieldingTri1[2]); } if (this->iceEffectTimer != 0) { thisx->colorFilterTimer++; if (this->iceEffectTimer >= 0) { this->iceEffectTimer--; } if ((this->iceEffectTimer % 4) == 0) { scale = 2.5f; if (thisx->params == ENMB_TYPE_CLUB) { scale = 4.0f; } bodyPartIdx = this->iceEffectTimer >> 2; EffectSsEnIce_SpawnFlyingVec3s(globalCtx, thisx, &this->bodyPartsPos[bodyPartIdx], 150, 150, 150, 250, 235, 245, 255, scale); } } }