/* * File: z_en_floormas * Overlay: En_Floormas * Description: Floormaster */ #include "z_en_floormas.h" #include "objects/object_wallmaster/object_wallmaster.h" #define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_2 | ACTOR_FLAG_10) #define SPAWN_INVISIBLE 0x8000 #define SPAWN_SMALL 0x10 #define MERGE_MASTER 0x40 #define MERGE_SLAVE 0x20 void EnFloormas_Init(Actor* thisx, GlobalContext* globalCtx); void EnFloormas_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnFloormas_Update(Actor* thisx, GlobalContext* globalCtx); void EnFloormas_Draw(Actor* thisx, GlobalContext* globalCtx); void EnFloormas_GrabLink(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Split(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Recover(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_DrawHighlighted(Actor* this, GlobalContext* globalCtx); void EnFloormas_SmWait(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_SetupBigDecideAction(EnFloormas* this); void EnFloormas_Freeze(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_TakeDamage(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Merge(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_JumpAtLink(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_SmSlaveJumpAtMaster(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_SmShrink(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_SmDecideAction(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_SmWalk(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Land(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Hover(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Turn(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Run(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_BigStopWalk(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_BigWalk(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Stand(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_BigDecideAction(EnFloormas* this, GlobalContext* globalCtx); void EnFloormas_Charge(EnFloormas* this, GlobalContext* globalCtx); const ActorInit En_Floormas_InitVars = { ACTOR_EN_FLOORMAS, ACTORCAT_ENEMY, FLAGS, OBJECT_WALLMASTER, sizeof(EnFloormas), (ActorFunc)EnFloormas_Init, (ActorFunc)EnFloormas_Destroy, (ActorFunc)EnFloormas_Update, (ActorFunc)EnFloormas_Draw, NULL, }; static ColliderCylinderInit sCylinderInit = { { COLTYPE_HIT0, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_CYLINDER, }, { ELEMTYPE_UNK0, { 0xFFCFFFFF, 0x04, 0x10 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_HARD, BUMP_ON | BUMP_HOOKABLE, OCELEM_ON, }, { 25, 40, 0, { 0, 0, 0 } }, }; static CollisionCheckInfoInit sColChkInfoInit = { 4, 30, 40, 150 }; 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(0, 0x1), /* Normal arrow */ DMG_ENTRY(2, 0x0), /* Hammer swing */ DMG_ENTRY(2, 0x0), /* Hookshot */ DMG_ENTRY(0, 0x1), /* 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, 0x0), /* Light arrow */ DMG_ENTRY(4, 0x4), /* 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(4, 0x4), /* 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 InitChainEntry sInitChain[] = { ICHAIN_S8(naviEnemyId, 0x31, ICHAIN_CONTINUE), ICHAIN_F32(targetArrowOffset, 5500, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(gravity, -1000, ICHAIN_STOP), }; void EnFloormas_Init(Actor* thisx, GlobalContext* globalCtx2) { EnFloormas* this = (EnFloormas*)thisx; GlobalContext* globalCtx = globalCtx2; s32 invisble; s32 pad; Actor_ProcessInitChain(&this->actor, sInitChain); ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 50.0f); SkelAnime_InitFlex(globalCtx, &this->skelAnime, &gWallmasterSkel, &gWallmasterWaitAnim, this->jointTable, this->morphTable, 25); Collider_InitCylinder(globalCtx, &this->collider); Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit); CollisionCheck_SetInfo(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit); this->zOffset = -1600; invisble = this->actor.params & SPAWN_INVISIBLE; // s16 cast needed this->actor.params &= (s16) ~(SPAWN_INVISIBLE); if (invisble) { this->actor.flags |= ACTOR_FLAG_7; this->actor.draw = EnFloormas_DrawHighlighted; } if (this->actor.params == SPAWN_SMALL) { this->actor.draw = NULL; this->actor.flags &= ~ACTOR_FLAG_0; this->actionFunc = EnFloormas_SmWait; } else { // spawn first small floormaster this->actor.parent = Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_EN_FLOORMAS, this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, invisble + SPAWN_SMALL); if (this->actor.parent == NULL) { Actor_Kill(&this->actor); return; } // spawn 2nd small floormaster this->actor.child = Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_EN_FLOORMAS, this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, invisble + SPAWN_SMALL); if (this->actor.child == NULL) { Actor_Kill(this->actor.parent); Actor_Kill(&this->actor); return; } // link floormasters together this->actor.parent->child = &this->actor; this->actor.parent->parent = this->actor.child; this->actor.child->parent = &this->actor; this->actor.child->child = this->actor.parent; EnFloormas_SetupBigDecideAction(this); } } void EnFloormas_Destroy(Actor* thisx, GlobalContext* globalCtx) { EnFloormas* this = (EnFloormas*)thisx; ColliderCylinder* col = &this->collider; Collider_DestroyCylinder(globalCtx, col); } void EnFloormas_MakeInvulnerable(EnFloormas* this) { this->collider.base.colType = COLTYPE_HARD; this->collider.base.acFlags |= AC_HARD; this->actionTarget = 0x28; } void EnFloormas_MakeVulnerable(EnFloormas* this) { this->collider.base.colType = COLTYPE_HIT0; this->actionTarget = 0; this->collider.base.acFlags &= ~AC_HARD; } void EnFloormas_SetupBigDecideAction(EnFloormas* this) { Animation_PlayOnce(&this->skelAnime, &gWallmasterWaitAnim); this->actionFunc = EnFloormas_BigDecideAction; this->actor.speedXZ = 0.0f; } void EnFloormas_SetupStand(EnFloormas* this) { Animation_MorphToPlayOnce(&this->skelAnime, &gWallmasterStandUpAnim, -3.0f); this->actionFunc = EnFloormas_Stand; } void EnFloormas_SetupBigWalk(EnFloormas* this) { if (this->actionFunc != EnFloormas_Run) { Animation_PlayLoopSetSpeed(&this->skelAnime, &gWallmasterWalkAnim, 1.5f); } else { this->skelAnime.playSpeed = 1.5f; } this->actionTimer = Rand_S16Offset(2, 4); this->actionFunc = EnFloormas_BigWalk; this->actor.speedXZ = 1.5f; } void EnFloormas_SetupBigStopWalk(EnFloormas* this) { Animation_PlayOnce(&this->skelAnime, &gWallmasterStopWalkAnim); this->actionFunc = EnFloormas_BigStopWalk; this->actor.speedXZ = 0.0f; } void EnFloormas_SetupRun(EnFloormas* this) { this->actionTimer = 0; this->actionFunc = EnFloormas_Run; this->actor.speedXZ = 5.0f; this->skelAnime.playSpeed = 3.0f; } void EnFloormas_SetupTurn(EnFloormas* this) { s16 rotDelta = this->actionTarget - this->actor.shape.rot.y; this->actor.speedXZ = 0.0f; if (rotDelta > 0) { Animation_MorphToPlayOnce(&this->skelAnime, &gFloormasterTurnAnim, -3.0f); } else { Animation_Change(&this->skelAnime, &gFloormasterTurnAnim, -1.0f, Animation_GetLastFrame(&gFloormasterTurnAnim), 0.0f, ANIMMODE_ONCE, -3.0f); } if (this->actor.scale.x > 0.004f) { this->actionTarget = (rotDelta * (2.0f / 30.0f)); } else { this->skelAnime.playSpeed = this->skelAnime.playSpeed + this->skelAnime.playSpeed; this->actionTarget = rotDelta * (2.0f / 15.0f); } this->actionFunc = EnFloormas_Turn; } void EnFloormas_SetupHover(EnFloormas* this, GlobalContext* globalCtx) { Animation_Change(&this->skelAnime, &gWallmasterHoverAnim, 3.0f, 0, Animation_GetLastFrame(&gWallmasterHoverAnim), ANIMMODE_ONCE, -3.0f); this->actor.speedXZ = 0.0f; this->actor.gravity = 0.0f; EnFloormas_MakeInvulnerable(this); Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 15.0f, 6, 20.0f, 300, 100, true); Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_ATTACK); this->actionFunc = EnFloormas_Hover; } void EnFloormas_SetupCharge(EnFloormas* this) { this->actionTimer = 25; this->actor.gravity = -0.15f; this->actionFunc = EnFloormas_Charge; this->actor.speedXZ = 0.5f; } void EnFloormas_SetupLand(EnFloormas* this) { Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 1.0f, 41.0f, 42.0f, ANIMMODE_ONCE, 5.0f); if ((this->actor.speedXZ < 0.0f) || (this->actionFunc != EnFloormas_Charge)) { this->actionTimer = 30; } else { this->actionTimer = 45; } this->actor.gravity = -1.0f; this->actionFunc = EnFloormas_Land; } void EnFloormas_SetupSplit(EnFloormas* this) { Actor_SetScale(&this->actor, 0.004f); this->actor.flags |= ACTOR_FLAG_4; if (CHECK_FLAG_ALL(this->actor.flags, ACTOR_FLAG_7)) { this->actor.draw = EnFloormas_DrawHighlighted; } else { this->actor.draw = EnFloormas_Draw; } this->actor.shape.rot.y = this->actor.parent->shape.rot.y + 0x5555; this->actor.world.pos = this->actor.parent->world.pos; this->actor.params = 0x10; Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 1.0f, 41.0f, Animation_GetLastFrame(&gWallmasterJumpAnim), ANIMMODE_ONCE, 0.0f); this->collider.dim.radius = sCylinderInit.dim.radius * 0.6f; this->collider.dim.height = sCylinderInit.dim.height * 0.6f; this->collider.info.bumperFlags &= ~BUMP_HOOKABLE; this->actor.speedXZ = 4.0f; this->actor.velocity.y = 7.0f; // using div creates a signed check. this->actor.colChkInfo.health = sColChkInfoInit.health >> 1; this->actionFunc = EnFloormas_Split; } void EnFloormas_SetupSmWalk(EnFloormas* this) { Animation_PlayLoopSetSpeed(&this->skelAnime, &gWallmasterWalkAnim, 4.5f); this->actionFunc = EnFloormas_SmWalk; this->actor.speedXZ = 5.0f; } void EnFloormas_SetupSmDecideAction(EnFloormas* this) { if (this->actionFunc != EnFloormas_SmWalk) { Animation_PlayLoopSetSpeed(&this->skelAnime, &gWallmasterWalkAnim, 4.5f); } this->actionFunc = EnFloormas_SmDecideAction; this->actor.speedXZ = 5.0f; } void EnFloormas_SetupSmShrink(EnFloormas* this, GlobalContext* globalCtx) { static Vec3f velocity = { 0.0f, 0.0f, 0.0f }; static Vec3f accel = { 0.0f, 0.0f, 0.0f }; Vec3f pos; this->actor.speedXZ = 0.0f; this->actor.velocity.y = 0.0f; pos.x = this->actor.world.pos.x; pos.y = this->actor.world.pos.y + 15.0f; pos.z = this->actor.world.pos.z; EffectSsDeadDb_Spawn(globalCtx, &pos, &velocity, &accel, 150, -10, 255, 255, 255, 255, 0, 0, 255, 1, 9, true); this->actionFunc = EnFloormas_SmShrink; } void EnFloormas_SetupSlaveJumpAtMaster(EnFloormas* this) { Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 2.0f, 0.0f, 41.0f, ANIMMODE_ONCE, 0.0f); this->actionFunc = EnFloormas_SmSlaveJumpAtMaster; this->actor.speedXZ = 0.0f; } void EnFloormas_SetupJumpAtLink(EnFloormas* this) { Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 2.0f, 0.0f, 41.0f, ANIMMODE_ONCE, 0.0f); this->actionFunc = EnFloormas_JumpAtLink; this->actor.speedXZ = 0.0f; } void EnFloormas_SetupGrabLink(EnFloormas* this, Player* player) { f32 yDelta; f32 xzDelta; Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 1.0f, 36.0f, 45.0f, ANIMMODE_ONCE, -3.0f); this->actor.flags &= ~ACTOR_FLAG_0; this->actor.speedXZ = 0.0f; this->actor.velocity.y = 0.0f; EnFloormas_MakeInvulnerable(this); if (!LINK_IS_ADULT) { yDelta = CLAMP(-this->actor.yDistToPlayer, 20.0f, 30.0f); xzDelta = -10.0f; } else { yDelta = CLAMP(-this->actor.yDistToPlayer, 25.0f, 45.0f); xzDelta = -70.0f; } this->actor.world.pos.y = player->actor.world.pos.y + yDelta; this->actor.world.pos.x = Math_SinS(this->actor.shape.rot.y) * (xzDelta * 0.1f) + player->actor.world.pos.x; this->actor.world.pos.z = Math_CosS(this->actor.shape.rot.y) * (xzDelta * 0.1f) + player->actor.world.pos.z; this->actor.shape.rot.x = -0x4CE0; this->actionFunc = EnFloormas_GrabLink; } void EnFloormas_SetupMerge(EnFloormas* this) { Animation_PlayOnce(&this->skelAnime, &gWallmasterWaitAnim); this->actionTimer = 0; this->smActionTimer += 1500; EnFloormas_MakeInvulnerable(this); this->actionFunc = EnFloormas_Merge; } void EnFloormas_SetupSmWait(EnFloormas* this) { EnFloormas* parent = (EnFloormas*)this->actor.parent; EnFloormas* child = (EnFloormas*)this->actor.child; // if this is the last remaining small floor master, kill all. if ((parent->actionFunc == EnFloormas_SmWait) && (child->actionFunc == EnFloormas_SmWait)) { Actor_Kill(&parent->actor); Actor_Kill(&child->actor); Actor_Kill(&this->actor); return; } this->actor.draw = NULL; this->actionFunc = EnFloormas_SmWait; this->actor.flags &= ~(ACTOR_FLAG_0 | ACTOR_FLAG_4); } void EnFloormas_SetupTakeDamage(EnFloormas* this) { Animation_MorphToPlayOnce(&this->skelAnime, &gWallmasterDamageAnim, -3.0f); if (this->collider.info.acHitInfo->toucher.dmgFlags & 0x1F824) { this->actor.world.rot.y = this->collider.base.ac->world.rot.y; } else { this->actor.world.rot.y = Actor_WorldYawTowardActor(&this->actor, this->collider.base.ac) + 0x8000; } Actor_SetColorFilter(&this->actor, 0x4000, 0xFF, 0, 0x14); this->actionFunc = EnFloormas_TakeDamage; this->actor.speedXZ = 5.0f; this->actor.velocity.y = 5.5f; } void EnFloormas_SetupRecover(EnFloormas* this) { Animation_PlayOnce(&this->skelAnime, &gWallmasterRecoverFromDamageAnim); this->actor.velocity.y = this->actor.speedXZ = 0.0f; this->actor.world.rot.y = this->actor.shape.rot.y; this->actionFunc = EnFloormas_Recover; } void EnFloormas_SetupFreeze(EnFloormas* this) { Animation_Change(&this->skelAnime, &gWallmasterJumpAnim, 1.5f, 0, 20.0f, ANIMMODE_ONCE, -3.0f); this->actor.speedXZ = 0.0f; if (this->actor.colChkInfo.damageEffect == 4) { Actor_SetColorFilter(&this->actor, -0x8000, 0xFF, 0, 0x50); } else { Actor_SetColorFilter(&this->actor, 0, 0xFF, 0, 0x50); if (this->actor.scale.x > 0.004f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); } } this->actionTimer = 80; this->actionFunc = EnFloormas_Freeze; } void EnFloormas_Die(EnFloormas* this, GlobalContext* globalCtx) { //Originally was doing > 0.004f, better fix thanks Gary :D if (this->actor.scale.x > (f32)0.004f) { // split this->actor.shape.rot.y = this->actor.yawTowardsPlayer + 0x8000; EnFloormas_SetupSplit((EnFloormas*)this->actor.child); EnFloormas_SetupSplit((EnFloormas*)this->actor.parent); EnFloormas_SetupSplit(this); Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SPLIT); } else { // Die Item_DropCollectibleRandom(globalCtx, &this->actor, &this->actor.world.pos, 0x90); EnFloormas_SetupSmShrink(this, globalCtx); } } void EnFloormas_BigDecideAction(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime)) { // within 400 units of link and within 90 degrees rotation of him if (this->actor.xzDistToPlayer < 400.0f && !Actor_IsFacingPlayer(&this->actor, 0x4000)) { this->actionTarget = this->actor.yawTowardsPlayer; EnFloormas_SetupTurn(this); // within 280 units of link and within 45 degrees rotation of him } else if (this->actor.xzDistToPlayer < 280.0f && Actor_IsFacingPlayer(&this->actor, 0x2000)) { EnFloormas_SetupHover(this, globalCtx); } else { EnFloormas_SetupStand(this); } } } void EnFloormas_Stand(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime)) { if (this->actor.scale.x > 0.004f) { EnFloormas_SetupBigWalk(this); } else if (this->actor.params == MERGE_SLAVE) { EnFloormas_SetupSmDecideAction(this); } else { EnFloormas_SetupSmWalk(this); } } } void EnFloormas_BigWalk(EnFloormas* this, GlobalContext* globalCtx) { s32 animPastFrame; SkelAnime_Update(&this->skelAnime); animPastFrame = Animation_OnFrame(&this->skelAnime, 0.0f); if (animPastFrame) { if (this->actionTimer != 0) { this->actionTimer--; } } if (((animPastFrame || (Animation_OnFrame(&this->skelAnime, 12.0f))) || (Animation_OnFrame(&this->skelAnime, 24.0f) != 0)) || (Animation_OnFrame(&this->skelAnime, 36.0f) != 0)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_WALK); } if ((this->actor.xzDistToPlayer < 320.0f) && (Actor_IsFacingPlayer(&this->actor, 0x4000))) { EnFloormas_SetupRun(this); } else if (this->actor.bgCheckFlags & 8) { // set target rotation to the colliding wall's rotation this->actionTarget = this->actor.wallYaw; EnFloormas_SetupTurn(this); } else if ((this->actor.xzDistToPlayer < 400.0f) && !Actor_IsFacingPlayer(&this->actor, 0x4000)) { // set target rotation to link. this->actionTarget = this->actor.yawTowardsPlayer; EnFloormas_SetupTurn(this); } else if (this->actionTimer == 0) { EnFloormas_SetupBigStopWalk(this); } } void EnFloormas_BigStopWalk(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime)) { EnFloormas_SetupBigDecideAction(this); } } void EnFloormas_Run(EnFloormas* this, GlobalContext* globalCtx) { SkelAnime_Update(&this->skelAnime); if (Animation_OnFrame(&this->skelAnime, 0.0f) || Animation_OnFrame(&this->skelAnime, 12.0f) || Animation_OnFrame(&this->skelAnime, 24.0f) || Animation_OnFrame(&this->skelAnime, 36.0f)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_WALK); } Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 3, 0x71C); if ((this->actor.xzDistToPlayer < 280.0f) && Actor_IsFacingPlayer(&this->actor, 0x2000) && !(this->actor.bgCheckFlags & 8)) { EnFloormas_SetupHover(this, globalCtx); } else if (this->actor.xzDistToPlayer > 400.0f) { EnFloormas_SetupBigWalk(this); } } void EnFloormas_Turn(EnFloormas* this, GlobalContext* globalCtx) { char pad[4]; f32 sp30; f32 sp2C; if (SkelAnime_Update(&this->skelAnime)) { EnFloormas_SetupStand(this); } if (((this->skelAnime.playSpeed > 0.0f) && Animation_OnFrame(&this->skelAnime, 21.0f)) || ((this->skelAnime.playSpeed < 0.0f) && Animation_OnFrame(&this->skelAnime, 6.0f))) { if (this->actor.scale.x > 0.004f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_WALK); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_WALK); } } // Needed to match if (!this->skelAnime.curFrame) {} if (this->skelAnime.curFrame >= 7.0f && this->skelAnime.curFrame < 22.0f) { sp30 = Math_SinS(this->actor.shape.rot.y + 0x4268); sp2C = Math_CosS(this->actor.shape.rot.y + 0x4268); this->actor.shape.rot.y += this->actionTarget; this->actor.world.pos.x -= (this->actor.scale.x * 2700.0f) * (Math_SinS(this->actor.shape.rot.y + 0x4268) - sp30); this->actor.world.pos.z -= (this->actor.scale.x * 2700.0f) * (Math_CosS(this->actor.shape.rot.y + 0x4268) - sp2C); } } void EnFloormas_Hover(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime)) { EnFloormas_SetupCharge(this); } this->actor.shape.rot.x += 0x140; this->actor.world.pos.y += 10.0f; Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 3, 2730); Math_StepToS(&this->zOffset, 1200, 100); } void EnFloormas_Slide(EnFloormas* this, GlobalContext* globalCtx) { static Vec3f accel = { 0.0f, 0.0f, 0.0f }; Vec3f pos; Vec3f velocity; pos.x = this->actor.world.pos.x; pos.z = this->actor.world.pos.z; pos.y = this->actor.floorHeight; velocity.y = 2.0f; velocity.x = Math_SinS(this->actor.shape.rot.y + 0x6000) * 7.0f; velocity.z = Math_CosS(this->actor.shape.rot.y + 0x6000) * 7.0f; func_800286CC(globalCtx, &pos, &velocity, &accel, 450, 100); velocity.x = Math_SinS(this->actor.shape.rot.y - 0x6000) * 7.0f; velocity.z = Math_CosS(this->actor.shape.rot.y - 0x6000) * 7.0f; func_800286CC(globalCtx, &pos, &velocity, &accel, 450, 100); func_8002F974(&this->actor, NA_SE_EN_FLOORMASTER_SLIDING); } void EnFloormas_Charge(EnFloormas* this, GlobalContext* globalCtx) { f32 distFromGround; if (this->actionTimer != 0) { this->actionTimer--; } Math_StepToF(&this->actor.speedXZ, 15.0f, SQ(this->actor.speedXZ) * (1.0f / 3.0f)); Math_ScaledStepToS(&this->actor.shape.rot.x, -0x1680, 0x140); distFromGround = this->actor.world.pos.y - this->actor.floorHeight; if (distFromGround < 10.0f) { this->actor.world.pos.y = this->actor.floorHeight + 10.0f; this->actor.gravity = 0.0f; this->actor.velocity.y = 0.0f; } if (distFromGround < 12.0f) { EnFloormas_Slide(this, globalCtx); } if ((this->actor.bgCheckFlags & 8) || (this->actionTimer == 0)) { EnFloormas_SetupLand(this); } } void EnFloormas_Land(EnFloormas* this, GlobalContext* globalCtx) { s32 isOnGround; isOnGround = this->actor.bgCheckFlags & 1; if (this->actor.bgCheckFlags & 2) { if (this->actor.params != MERGE_MASTER) { EnFloormas_MakeVulnerable(this); } if (this->actor.velocity.y < -4.0f) { if (this->actor.scale.x > 0.004f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_LAND); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_LAND); } } } if (this->actor.bgCheckFlags & 8) { this->actor.speedXZ = 0.0f; } if (isOnGround) { Math_StepToF(&this->actor.speedXZ, 0.0f, 2.0f); } if ((this->actor.speedXZ > 0.0f) && ((this->actor.world.pos.y - this->actor.floorHeight) < 12.0f)) { EnFloormas_Slide(this, globalCtx); } if (SkelAnime_Update(&this->skelAnime)) { if (this->actionTimer != 0) { this->actionTimer--; } if (this->actionTimer == 0 && isOnGround) { if (this->skelAnime.endFrame < 45.0f) { this->skelAnime.endFrame = Animation_GetLastFrame(&gWallmasterJumpAnim); } else if (this->actor.params == MERGE_MASTER) { EnFloormas_SetupMerge(this); } else { EnFloormas_SetupStand(this); this->smActionTimer = 50; } } } Math_ScaledStepToS(&this->actor.shape.rot.x, 0, 0x140); Math_StepToS(&this->zOffset, -1600, 100); } void EnFloormas_Split(EnFloormas* this, GlobalContext* globalCtx) { if (this->actor.bgCheckFlags & 1) { if (SkelAnime_Update(&this->skelAnime)) { this->actor.flags |= ACTOR_FLAG_0; this->smActionTimer = 50; EnFloormas_SetupStand(this); } Math_StepToF(&this->actor.speedXZ, 0.0f, 1.0f); } if (this->actor.bgCheckFlags & 2) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_LAND); } } void EnFloormas_SmWalk(EnFloormas* this, GlobalContext* globalCtx) { SkelAnime_Update(&this->skelAnime); DECR(this->smActionTimer); if (Animation_OnFrame(&this->skelAnime, 0.0f) || Animation_OnFrame(&this->skelAnime, 18.0f)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_WALK); } if (this->smActionTimer == 0) { EnFloormas_SetupSmDecideAction(this); } else if (this->actor.bgCheckFlags & 8) { this->actionTarget = this->actor.wallYaw; EnFloormas_SetupTurn(this); } else if (this->actor.xzDistToPlayer < 120.0f) { Math_ScaledStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer + 0x8000, 0x38E); } } void EnFloormas_SmDecideAction(EnFloormas* this, GlobalContext* globalCtx) { Actor* primaryFloormas; s32 isAgainstWall; SkelAnime_Update(&this->skelAnime); if (Animation_OnFrame(&this->skelAnime, 0.0f) || Animation_OnFrame(&this->skelAnime, 18.0f)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_WALK); } isAgainstWall = this->actor.bgCheckFlags & 8; if (isAgainstWall) { this->actionTarget = this->actor.wallYaw; EnFloormas_SetupTurn(this); return; } if (this->actor.params == MERGE_SLAVE) { if (this->actor.parent->params == MERGE_MASTER) { primaryFloormas = this->actor.parent; } else if (this->actor.child->params == MERGE_MASTER) { primaryFloormas = this->actor.child; } else { this->actor.params = 0x10; return; } Math_ScaledStepToS(&this->actor.shape.rot.y, Actor_WorldYawTowardActor(&this->actor, primaryFloormas), 0x38E); if (Actor_WorldDistXZToActor(&this->actor, primaryFloormas) < 80.0f) { EnFloormas_SetupSlaveJumpAtMaster(this); } } else { Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 3, 0x71C); if (this->actor.xzDistToPlayer < 80.0f) { EnFloormas_SetupJumpAtLink(this); } } } void EnFloormas_SmShrink(EnFloormas* this, GlobalContext* globalCtx) { if (Math_StepToF(&this->actor.scale.x, 0.0f, 0.0015f)) { EnFloormas_SetupSmWait(this); } this->actor.scale.z = this->actor.scale.x; this->actor.scale.y = this->actor.scale.x; } void EnFloormas_JumpAtLink(EnFloormas* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); SkelAnime_Update(&this->skelAnime); if (this->skelAnime.curFrame < 20.0f) { Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 2, 0xE38); } else if (Animation_OnFrame(&this->skelAnime, 20.0f)) { this->actor.speedXZ = 5.0f; this->actor.velocity.y = 7.0f; } else if (this->actor.bgCheckFlags & 2) { this->actionTimer = 0x32; this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_LAND); EnFloormas_SetupLand(this); } else if ((this->actor.yDistToPlayer < -10.0f) && (this->collider.base.ocFlags1 & OC1_HIT) && (&player->actor == this->collider.base.oc)) { globalCtx->grabPlayer(globalCtx, player); EnFloormas_SetupGrabLink(this, player); } } void EnFloormas_GrabLink(EnFloormas* this, GlobalContext* globalCtx) { Player* player = GET_PLAYER(globalCtx); EnFloormas* parent; EnFloormas* child; f32 yDelta; f32 xzDelta; if (SkelAnime_Update(&this->skelAnime)) { if (this->skelAnime.playSpeed > 0.0f) { this->skelAnime.playSpeed = -1.0f; this->skelAnime.endFrame = 36.0f; this->skelAnime.startFrame = 45.0f; } else { this->skelAnime.playSpeed = 1.0f; this->skelAnime.endFrame = 45.0f; this->skelAnime.startFrame = 36.0f; } } if (!LINK_IS_ADULT) { yDelta = CLAMP(-this->actor.yDistToPlayer, 20.0f, 30.0f); xzDelta = -10.0f; } else { yDelta = CLAMP(-this->actor.yDistToPlayer, 25.0f, 45.0f); xzDelta = -30.0f; } this->actor.world.pos.y = player->actor.world.pos.y + yDelta; this->actor.world.pos.x = Math_SinS(this->actor.shape.rot.y) * (xzDelta * 0.1f) + player->actor.world.pos.x; this->actor.world.pos.z = Math_CosS(this->actor.shape.rot.y) * (xzDelta * 0.1f) + player->actor.world.pos.z; // let go if (!(player->stateFlags2 & 0x80) || (player->invincibilityTimer < 0)) { parent = (EnFloormas*)this->actor.parent; child = (EnFloormas*)this->actor.child; if (((parent->actionFunc == EnFloormas_GrabLink) || parent->actionFunc == EnFloormas_SmWait) && (child->actionFunc == EnFloormas_GrabLink || child->actionFunc == EnFloormas_SmWait)) { parent->actor.params = MERGE_SLAVE; child->actor.params = MERGE_SLAVE; this->actor.params = MERGE_MASTER; } this->actor.shape.rot.x = 0; this->actor.velocity.y = 6.0f; this->actor.flags |= ACTOR_FLAG_0; this->actor.speedXZ = -3.0f; EnFloormas_SetupLand(this); } else { // Damage link every 20 frames if ((this->actionTarget % 20) == 0) { if (!LINK_IS_ADULT) { func_8002F7DC(&player->actor, NA_SE_VO_LI_DAMAGE_S_KID); } else { func_8002F7DC(&player->actor, NA_SE_VO_LI_DAMAGE_S); } globalCtx->damagePlayer(globalCtx, -8); } } Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_STICK); } void EnFloormas_SmSlaveJumpAtMaster(EnFloormas* this, GlobalContext* globalCtx) { Actor* primFloormas; SkelAnime_Update(&this->skelAnime); if (this->actor.parent->params == MERGE_MASTER) { primFloormas = this->actor.parent; } else if (this->actor.child->params == MERGE_MASTER) { primFloormas = this->actor.child; } else { if (this->actor.bgCheckFlags & 2) { this->actor.params = 0x10; EnFloormas_SetupLand(this); } return; } if (Animation_OnFrame(&this->skelAnime, 20.0f)) { this->actor.speedXZ = 5.0f; this->actor.velocity.y = 7.0f; } else if (this->skelAnime.curFrame < 20.0f) { Math_ApproachS(&this->actor.shape.rot.y, Actor_WorldYawTowardActor(&this->actor, primFloormas), 2, 0xE38); } else if ((((primFloormas->world.pos.y - this->actor.world.pos.y) < -10.0f) && (fabsf(this->actor.world.pos.x - primFloormas->world.pos.x) < 10.0f)) && (fabsf(this->actor.world.pos.z - primFloormas->world.pos.z) < 10.0f)) { EnFloormas_SetupSmWait(this); this->collider.base.ocFlags1 |= OC1_ON; } else if (this->actor.bgCheckFlags & 2) { this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_LAND); EnFloormas_SetupLand(this); } if (fabsf(this->actor.world.pos.x - primFloormas->world.pos.x) < 5.0f && fabsf(this->actor.world.pos.z - primFloormas->world.pos.z) < 5.0f) { Math_StepToF(&this->actor.speedXZ, 0, 2.0f); } } void EnFloormas_Merge(EnFloormas* this, GlobalContext* globalCtx) { EnFloormas* parent; EnFloormas* child; s32 mergeCnt; f32 prevScale; f32 curScale; mergeCnt = 0; DECR(this->smActionTimer); parent = (EnFloormas*)this->actor.parent; child = (EnFloormas*)this->actor.child; if (this->smActionTimer == 0) { if (parent->actionFunc != EnFloormas_SmWait) { EnFloormas_SetupSmShrink(parent, globalCtx); } if (child->actionFunc != EnFloormas_SmWait) { EnFloormas_SetupSmShrink(child, globalCtx); } } else { if ((parent->actionFunc != EnFloormas_SmWait) && (parent->actionFunc != EnFloormas_SmShrink)) { mergeCnt++; } if ((child->actionFunc != EnFloormas_SmWait) && (child->actionFunc != EnFloormas_SmShrink)) { mergeCnt++; } } prevScale = this->actor.scale.x; if (mergeCnt == 1) { Math_StepToF(&this->actor.scale.x, 0.007f, 0.0005f); } else if (mergeCnt == 0) { Math_StepToF(&this->actor.scale.x, 0.01f, 0.0005f); } curScale = this->actor.scale.x; this->actor.scale.y = this->actor.scale.z = curScale; if (((prevScale == 0.007f) || (prevScale == 0.004f)) && (prevScale != this->actor.scale.x)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_EXPAND); } this->collider.dim.radius = (sCylinderInit.dim.radius * 100.0f) * this->actor.scale.x; this->collider.dim.height = (sCylinderInit.dim.height * 100.0f) * this->actor.scale.x; if (SkelAnime_Update(&this->skelAnime) != 0) { if (this->actor.scale.x >= 0.01f) { this->actor.flags &= ~ACTOR_FLAG_4; EnFloormas_MakeVulnerable(this); this->actor.params = 0; this->collider.info.bumperFlags |= BUMP_HOOKABLE; this->actor.colChkInfo.health = sColChkInfoInit.health; EnFloormas_SetupStand(this); } else { if (this->actionTimer == 0) { Animation_PlayOnce(&this->skelAnime, &gFloormasterTapFingerAnim); this->actionTimer = 1; } else { Animation_PlayOnce(&this->skelAnime, &gWallmasterWaitAnim); this->actionTimer = 0; } } } func_8002F974(&this->actor, NA_SE_EN_FLOORMASTER_RESTORE - SFX_FLAG); } void EnFloormas_SmWait(EnFloormas* this, GlobalContext* globalCtx) { } void EnFloormas_TakeDamage(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime) != 0) { if (this->actor.colChkInfo.health == 0) { EnFloormas_Die(this, globalCtx); } else { EnFloormas_SetupRecover(this); } } if (Animation_OnFrame(&this->skelAnime, 13.0f)) { if (this->actor.scale.x > 0.004f) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } } Math_StepToF(&this->actor.speedXZ, 0.0f, 0.2f); } void EnFloormas_Recover(EnFloormas* this, GlobalContext* globalCtx) { if (SkelAnime_Update(&this->skelAnime) != 0) { EnFloormas_SetupStand(this); } } void EnFloormas_Freeze(EnFloormas* this, GlobalContext* globalCtx) { SkelAnime_Update(&this->skelAnime); if (this->actionTimer != 0) { this->actionTimer--; } if (this->actionTimer == 0) { if (this->actor.colChkInfo.health == 0) { EnFloormas_Die(this, globalCtx); return; } EnFloormas_SetupStand(this); } } void EnFloormas_ColliderCheck(EnFloormas* this, GlobalContext* globalCtx) { s32 pad; s32 isSmall; if ((this->collider.base.acFlags & AC_HIT) != 0) { this->collider.base.acFlags &= ~AC_HIT; Actor_SetDropFlag(&this->actor, &this->collider.info, 1); if ((this->actor.colChkInfo.damageEffect != 0) || (this->actor.colChkInfo.damage != 0)) { if (this->collider.base.colType != COLTYPE_HARD) { isSmall = 0; if (this->actor.scale.x < 0.01f) { isSmall = 1; } if (isSmall && this->collider.info.acHitInfo->toucher.dmgFlags & 0x80) { this->actor.colChkInfo.damage = 2; this->actor.colChkInfo.damageEffect = 0; } if (Actor_ApplyDamage(&this->actor) == 0) { if (isSmall) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FLOORMASTER_SM_DEAD); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_DEAD); } Enemy_StartFinishingBlow(globalCtx, &this->actor); this->actor.flags &= ~ACTOR_FLAG_0; } else if (this->actor.colChkInfo.damage != 0) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_FALL_DAMAGE); } if ((this->actor.colChkInfo.damageEffect == 4) || (this->actor.colChkInfo.damageEffect == 1)) { if (this->actionFunc != EnFloormas_Freeze) { EnFloormas_SetupFreeze(this); } } else { if (this->actor.colChkInfo.damageEffect == 2) { EffectSsFCircle_Spawn(globalCtx, &this->actor, &this->actor.world.pos, this->actor.scale.x * 4000.0f, this->actor.scale.x * 4000.0f); } EnFloormas_SetupTakeDamage(this); } } } } } void EnFloormas_Update(Actor* thisx, GlobalContext* globalCtx) { EnFloormas* this = (EnFloormas*)thisx; s32 pad; if (this->actionFunc != EnFloormas_SmWait) { if (this->collider.base.atFlags & AT_HIT) { this->collider.base.atFlags &= ~AT_HIT; this->actor.speedXZ *= -0.5f; if (-5.0f < this->actor.speedXZ) { this->actor.speedXZ = -5.0f; } else { this->actor.speedXZ = this->actor.speedXZ; } this->actor.velocity.y = 5.0f; EnFloormas_SetupLand(this); } EnFloormas_ColliderCheck(this, globalCtx); this->actionFunc(this, globalCtx); if (this->actionFunc != EnFloormas_TakeDamage) { this->actor.world.rot.y = this->actor.shape.rot.y; } if (this->actionFunc != EnFloormas_GrabLink) { Actor_MoveForward(&this->actor); } Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 20.0f, this->actor.scale.x * 3000.0f, 0.0f, 0x1D); Collider_UpdateCylinder(&this->actor, &this->collider); if (this->actionFunc == EnFloormas_Charge) { this->actor.flags |= ACTOR_FLAG_24; CollisionCheck_SetAT(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } if (this->actionFunc != EnFloormas_GrabLink) { if (this->actionFunc != EnFloormas_Split && this->actionFunc != EnFloormas_TakeDamage && this->actor.freezeTimer == 0) { CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } if ((this->actionFunc != EnFloormas_SmSlaveJumpAtMaster) || (this->skelAnime.curFrame < 20.0f)) { CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } } Actor_SetFocus(&this->actor, this->actor.scale.x * 2500.0f); if (this->collider.base.colType == COLTYPE_HARD) { if (this->actionTarget != 0) { this->actionTarget--; } if (this->actionTarget == 0) { this->actionTarget = 0x28; } } } } s32 EnFloormas_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx, Gfx** gfx) { EnFloormas* this = (EnFloormas*)thisx; if (limbIndex == 1) { pos->z += this->zOffset; } return false; } void EnFloormas_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx, Gfx** gfx) { if (limbIndex == 2) { Matrix_Push(); Matrix_Translate(1600.0f, -700.0f, -1700.0f, MTXMODE_APPLY); Matrix_RotateY(DEGTORAD(60.0f), MTXMODE_APPLY); Matrix_RotateZ(DEGTORAD(15.0f), MTXMODE_APPLY); Matrix_Scale(2.0f, 2.0f, 2.0f, MTXMODE_APPLY); gSPMatrix((*gfx)++, MATRIX_NEWMTX(globalCtx->state.gfxCtx), G_MTX_LOAD); gSPDisplayList((*gfx)++, gWallmasterFingerDL); Matrix_Pop(); } } static Color_RGBA8 sMergeColor = { 0, 255, 0, 0 }; void EnFloormas_Draw(Actor* thisx, GlobalContext* globalCtx) { EnFloormas* this = (EnFloormas*)thisx; OPEN_DISPS(globalCtx->state.gfxCtx); func_80093D18(globalCtx->state.gfxCtx); if (this->collider.base.colType == COLTYPE_HARD) { func_80026230(globalCtx, &sMergeColor, this->actionTarget % 0x28, 0x28); } POLY_OPA_DISP = SkelAnime_DrawFlex(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, EnFloormas_OverrideLimbDraw, EnFloormas_PostLimbDraw, this, POLY_OPA_DISP); if (this->collider.base.colType == COLTYPE_HARD) { func_80026608(globalCtx); } CLOSE_DISPS(globalCtx->state.gfxCtx); } void EnFloormas_DrawHighlighted(Actor* thisx, GlobalContext* globalCtx) { EnFloormas* this = (EnFloormas*)thisx; OPEN_DISPS(globalCtx->state.gfxCtx); func_80093D84(globalCtx->state.gfxCtx); if (this->collider.base.colType == COLTYPE_HARD) { func_80026690(globalCtx, &sMergeColor, this->actionTarget % 0x28, 0x28); } POLY_XLU_DISP = SkelAnime_DrawFlex(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, EnFloormas_OverrideLimbDraw, EnFloormas_PostLimbDraw, this, POLY_XLU_DISP); if (this->collider.base.colType == COLTYPE_HARD) { func_80026A6C(globalCtx); } CLOSE_DISPS(globalCtx->state.gfxCtx); }