Shipwright/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c

5486 lines
220 KiB
C
Raw Normal View History

#include "z_boss_tw.h"
#include "objects/gameplay_keep/gameplay_keep.h"
#include "objects/object_tw/object_tw.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/frame_interpolation.h"
#include <string.h>
#define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_2 | ACTOR_FLAG_4 | ACTOR_FLAG_5)
typedef enum {
/* 0x00 */ TW_KOTAKE,
/* 0x01 */ TW_KOUME,
/* 0x02 */ TW_TWINROVA,
/* 0x64 */ TW_FIRE_BLAST = 0x64,
/* 0x65 */ TW_FIRE_BLAST_GROUND,
/* 0x66 */ TW_ICE_BLAST,
/* 0x67 */ TW_ICE_BLAST_GROUND,
/* 0x68 */ TW_DEATHBALL_KOTAKE,
/* 0x69 */ TW_DEATHBALL_KOUME
} TwinrovaType;
void BossTw_Init(Actor* thisx, GlobalContext* globalCtx);
void BossTw_Destroy(Actor* thisx, GlobalContext* globalCtx);
void BossTw_Update(Actor* thisx, GlobalContext* globalCtx);
void BossTw_Draw(Actor* thisx, GlobalContext* globalCtx);
void BossTw_Reset(void);
void BossTw_TwinrovaDamage(BossTw* this, GlobalContext* globalCtx, u8 arg2);
void BossTw_TwinrovaSetupFly(BossTw* this, GlobalContext* globalCtx);
void BossTw_DrawEffects(GlobalContext* globalCtx);
void BossTw_TwinrovaLaugh(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaFly(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaGetUp(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupGetUp(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupLaugh(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaDoneBlastShoot(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupDoneBlastShoot(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupShootBlast(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupChargeBlast(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaArriveAtTarget(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaDeathCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaIntroCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_CSWait(BossTw* this, GlobalContext* globalCtx);
void BossTw_DeathCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaMergeCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupMergeCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_MergeCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_Spin(BossTw* this, GlobalContext* globalCtx);
void BossTw_Laugh(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupLaugh(BossTw* this, GlobalContext* globalCtx);
void BossTw_FinishBeamShoot(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupFinishBeamShoot(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupHitByBeam(BossTw* this, GlobalContext* globalCtx);
void BossTw_HitByBeam(BossTw* this, GlobalContext* globalCtx);
void BossTw_Wait(BossTw* this, GlobalContext* globalCtx);
void BossTw_ShootBeam(BossTw* this, GlobalContext* globalCtx);
void BossTw_FlyTo(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupShootBeam(BossTw* this, GlobalContext* globalCtx);
void BossTw_TurnToPlayer(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaUpdate(Actor* thisx, GlobalContext* globalCtx);
void BossTw_TwinrovaDraw(Actor* thisx, GlobalContext* globalCtx);
void BossTw_SetupWait(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupIntroCS(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupFlyTo(BossTw* this, GlobalContext* globalCtx);
void BossTw_SetupCSWait(BossTw* this, GlobalContext* globalCtx);
void BossTw_BlastUpdate(Actor* thisx, GlobalContext* globalCtx);
void BossTw_BlastDraw(Actor* thisx, GlobalContext* globalCtx);
void BossTw_BlastFire(BossTw* this, GlobalContext* globalCtx);
void BossTw_BlastIce(BossTw* this, GlobalContext* globalCtx);
void BossTw_DeathBall(BossTw* this, GlobalContext* globalCtx);
void BossTw_DrawDeathBall(Actor* thisx, GlobalContext* globalCtx);
void BossTw_TwinrovaStun(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSpin(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaShootBlast(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaChargeBlast(BossTw* this, GlobalContext* globalCtx);
void BossTw_TwinrovaSetupSpin(BossTw* this, GlobalContext* globalCtx);
void BossTw_UpdateEffects(GlobalContext* globalCtx);
const ActorInit Boss_Tw_InitVars = {
ACTOR_BOSS_TW,
ACTORCAT_BOSS,
FLAGS,
OBJECT_TW,
sizeof(BossTw),
(ActorFunc)BossTw_Init,
(ActorFunc)BossTw_Destroy,
(ActorFunc)BossTw_Update,
(ActorFunc)BossTw_Draw,
(ActorResetFunc)BossTw_Reset,
};
static Vec3f D_8094A7D0 = { 0.0f, 0.0f, 1000.0f };
static Vec3f sZeroVector = { 0.0f, 0.0f, 0.0f };
static ColliderCylinderInit sCylinderInitBlasts = {
{
COLTYPE_NONE,
AT_ON | AT_TYPE_ALL,
AC_ON | AC_TYPE_PLAYER,
OC1_ON | OC1_TYPE_PLAYER,
OC2_TYPE_1,
COLSHAPE_CYLINDER,
},
{
ELEMTYPE_UNK0,
{ 0xFFCFFFFF, 0x00, 0x30 },
{ 0x00100000, 0x00, 0x00 },
TOUCH_ON | TOUCH_SFX_NORMAL,
BUMP_ON,
OCELEM_ON,
},
{ 25, 35, -17, { 0, 0, 0 } },
};
static ColliderCylinderInit sCylinderInitKoumeKotake = {
{
COLTYPE_HIT3,
AT_ON | AT_TYPE_ENEMY,
AC_ON | AC_TYPE_PLAYER,
OC1_ON | OC1_TYPE_PLAYER,
OC2_TYPE_1,
COLSHAPE_CYLINDER,
},
{
ELEMTYPE_UNK0,
{ 0xFFCFFFFF, 0x00, 0x20 },
{ 0xFFCDFFFE, 0x00, 0x00 },
TOUCH_ON | TOUCH_SFX_NORMAL,
BUMP_ON,
OCELEM_ON,
},
{ 45, 120, -30, { 0, 0, 0 } },
};
static ColliderCylinderInit sCylinderInitTwinrova = {
{
COLTYPE_HIT3,
AT_ON | AT_TYPE_ENEMY,
AC_ON | AC_TYPE_PLAYER,
OC1_ON | OC1_TYPE_ALL,
OC2_TYPE_1,
COLSHAPE_CYLINDER,
},
{
ELEMTYPE_UNK0,
{ 0xFFCFFFFF, 0x00, 0x20 },
{ 0xFFCDFFFE, 0x00, 0x00 },
TOUCH_ON | TOUCH_SFX_NORMAL,
BUMP_ON | BUMP_HOOKABLE,
OCELEM_ON,
},
{ 45, 120, -30, { 0, 0, 0 } },
};
static Vec3f sTwinrovaPillarPos[] = {
{ 580.0f, 380.0f, 0.0f },
{ 0.0f, 380.0f, 580.0f },
{ -580.0f, 380.0f, 0.0f },
{ 0.0f, 380.0f, -580.0f },
};
2022-05-12 13:28:24 -04:00
u8 sTwInitalized = false;
static InitChainEntry sInitChain[] = {
ICHAIN_U8(targetMode, 5, ICHAIN_CONTINUE),
ICHAIN_F32_DIV1000(gravity, 0, ICHAIN_CONTINUE),
ICHAIN_F32(targetArrowOffset, 0, ICHAIN_STOP),
};
static s8 sEnvType;
static u8 sGroundBlastType;
static BossTw* sKotakePtr;
static BossTw* sKoumePtr;
static BossTw* sTwinrovaPtr;
static u8 sShieldFireCharge;
static u8 sShieldIceCharge;
static f32 D_8094C854;
static f32 D_8094C858;
static u8 sTwinrovaBlastType;
static u8 sFixedBlastType;
static u8 sFixedBlatSeq;
static u8 sFreezeState;
static Vec3f sShieldHitPos;
static s16 sShieldHitYaw;
static u8 sBeamDivertTimer;
static u8 D_8094C86F;
static u8 D_8094C870;
static s16 D_8094C872;
static s16 D_8094C874;
static s16 D_8094C876;
static u8 D_8094C878;
static s16 D_8094C87A;
static s16 D_8094C87C;
static u8 D_8094C87E;
2022-05-12 13:28:24 -04:00
BossTwEffect sTwEffects[150];
void BossTw_AddDotEffect(GlobalContext* globalCtx, Vec3f* initalPos, Vec3f* initalSpeed, Vec3f* accel, f32 scale,
s16 args, s16 countLimit) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < countLimit; i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_DOT;
eff->pos = *initalPos;
eff->curSpeed = *initalSpeed;
eff->accel = *accel;
eff->workf[EFF_SCALE] = scale / 1000.0f;
eff->alpha = 255;
eff->frame = (s16)Rand_ZeroFloat(10.0f);
eff->work[EFF_ARGS] = args;
break;
}
}
}
void BossTw_AddDmgCloud(GlobalContext* globalCtx, s16 type, Vec3f* initialPos, Vec3f* initalSpeed, Vec3f* accel,
f32 scale, s16 alpha, s16 args, s16 countLimit) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < countLimit; i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = type;
eff->pos = *initialPos;
eff->curSpeed = *initalSpeed;
eff->accel = *accel;
eff->workf[EFF_SCALE] = scale / 1000.0f;
eff->work[EFF_ARGS] = args;
eff->alpha = alpha;
eff->frame = (s16)Rand_ZeroFloat(100.0f);
break;
}
}
}
void BossTw_AddRingEffect(GlobalContext* globalCtx, Vec3f* initalPos, f32 scale, f32 arg3, s16 alpha, s16 args,
s16 arg6, s16 arg7) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < arg7; i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_RING;
eff->pos = *initalPos;
eff->curSpeed = sZeroVector;
eff->accel = sZeroVector;
eff->workf[EFF_SCALE] = scale * 0.0025f;
eff->workf[EFF_DIST] = arg3 * 0.0025f;
eff->work[EFF_ARGS] = args;
eff->work[EFF_UNKS1] = arg6;
eff->alpha = alpha;
eff->workf[EFF_ROLL] = Rand_ZeroFloat(M_PI);
eff->frame = 0;
break;
}
}
}
void BossTw_AddPlayerFreezeEffect(GlobalContext* globalCtx, Actor* target) {
BossTwEffect* eff;
s16 i;
for (eff = globalCtx->specialEffects, i = 0; i < ARRAY_COUNT(sTwEffects); i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_PLYR_FRZ;
eff->curSpeed = sZeroVector;
eff->accel = sZeroVector;
eff->frame = 0;
eff->target = target;
eff->workf[EFF_DIST] = 0.0f;
eff->workf[EFF_SCALE] = 0.0f;
eff->workf[EFF_ROLL] = 0.0f;
if (target == NULL) {
eff->work[EFF_ARGS] = 100;
} else {
eff->work[EFF_ARGS] = 20;
}
break;
}
}
}
void BossTw_AddFlameEffect(GlobalContext* globalCtx, Vec3f* initalPos, Vec3f* initalSpeed, Vec3f* accel, f32 scale,
s16 args) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < ARRAY_COUNT(sTwEffects); i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_FLAME;
eff->pos = *initalPos;
eff->curSpeed = *initalSpeed;
eff->accel = *accel;
eff->workf[EFF_SCALE] = scale / 1000.0f;
eff->work[EFF_ARGS] = args;
eff->work[EFF_UNKS1] = 0;
eff->alpha = 0;
eff->frame = (s16)Rand_ZeroFloat(1000.0f);
break;
}
}
}
void BossTw_AddMergeFlameEffect(GlobalContext* globalCtx, Vec3f* initialPos, f32 scale, f32 dist, s16 args) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < ARRAY_COUNT(sTwEffects); i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_MERGEFLAME;
eff->pos = *initialPos;
eff->curSpeed = sZeroVector;
eff->accel = sZeroVector;
eff->workf[EFF_SCALE] = scale / 1000.0f;
eff->work[EFF_ARGS] = args;
eff->work[EFF_UNKS1] = 0;
eff->workf[EFF_DIST] = dist;
eff->workf[EFF_ROLL] = Rand_ZeroFloat(2.0f * M_PI);
eff->alpha = 0;
eff->frame = (s16)Rand_ZeroFloat(1000.0f);
break;
}
}
}
void BossTw_AddShieldBlastEffect(GlobalContext* globalCtx, Vec3f* initalPos, Vec3f* initalSpeed, Vec3f* accel,
f32 scale, f32 arg5, s16 alpha, s16 args) {
s16 i;
BossTwEffect* eff;
for (i = 0, eff = globalCtx->specialEffects; i < ARRAY_COUNT(sTwEffects); i++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_SHLD_BLST;
eff->pos = *initalPos;
eff->curSpeed = *initalSpeed;
eff->accel = *accel;
eff->workf[EFF_SCALE] = scale / 1000.0f;
eff->workf[EFF_DIST] = arg5 / 1000.0f;
eff->work[EFF_ARGS] = args;
eff->work[EFF_UNKS1] = 0;
eff->alpha = alpha;
eff->frame = (s16)Rand_ZeroFloat(1000.0f);
break;
}
}
}
void BossTw_AddShieldDeflectEffect(GlobalContext* globalCtx, f32 arg1, s16 arg2) {
s16 i;
s16 j;
BossTwEffect* eff;
Player* player = GET_PLAYER(globalCtx);
sShieldHitPos = player->bodyPartsPos[15];
sShieldHitYaw = player->actor.shape.rot.y;
for (i = 0; i < 8; i++) {
for (eff = globalCtx->specialEffects, j = 0; j < ARRAY_COUNT(sTwEffects); j++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_SHLD_DEFL;
eff->pos = sShieldHitPos;
eff->curSpeed = sZeroVector;
eff->accel = sZeroVector;
eff->workf[EFF_ROLL] = i * (M_PI / 4.0f);
eff->workf[EFF_YAW] = M_PI / 2.0f;
eff->workf[EFF_DIST] = 0.0f;
eff->workf[EFF_SCALE] = arg1 / 1000.0f;
eff->work[EFF_ARGS] = arg2;
eff->work[EFF_UNKS1] = 0;
eff->alpha = 255;
eff->frame = (s16)Rand_ZeroFloat(1000.0f);
break;
}
}
}
}
void BossTw_AddShieldHitEffect(GlobalContext* globalCtx, f32 arg1, s16 arg2) {
s16 i;
s16 j;
BossTwEffect* eff;
Player* player = GET_PLAYER(globalCtx);
sShieldHitPos = player->bodyPartsPos[15];
sShieldHitYaw = player->actor.shape.rot.y;
for (i = 0; i < 8; i++) {
for (eff = globalCtx->specialEffects, j = 0; j < ARRAY_COUNT(sTwEffects); j++, eff++) {
if (eff->type == TWEFF_NONE) {
eff->type = TWEFF_SHLD_HIT;
eff->pos = sShieldHitPos;
eff->curSpeed = sZeroVector;
eff->accel = sZeroVector;
eff->workf[EFF_ROLL] = i * (M_PI / 4.0f);
eff->workf[EFF_YAW] = M_PI / 2.0f;
eff->workf[EFF_DIST] = 0.0f;
eff->workf[EFF_SCALE] = arg1 / 1000.0f;
eff->work[EFF_ARGS] = arg2;
eff->work[EFF_UNKS1] = 0;
eff->alpha = 255;
eff->frame = (s16)Rand_ZeroFloat(1000.0f);
break;
}
}
}
}
void BossTw_Init(Actor* thisx, GlobalContext* globalCtx2) {
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
s16 i;
Actor_ProcessInitChain(&this->actor, sInitChain);
ActorShape_Init(&this->actor.shape, 0.0f, NULL, 0.0f);
if (this->actor.params >= TW_FIRE_BLAST) {
// Blasts
Actor_SetScale(&this->actor, 0.01f);
this->actor.update = BossTw_BlastUpdate;
this->actor.draw = BossTw_BlastDraw;
this->actor.flags &= ~ACTOR_FLAG_0;
Collider_InitCylinder(globalCtx, &this->collider);
Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInitBlasts);
if (this->actor.params == TW_FIRE_BLAST || this->actor.params == TW_FIRE_BLAST_GROUND) {
this->actionFunc = BossTw_BlastFire;
this->collider.info.toucher.effect = 1;
} else if (this->actor.params == TW_ICE_BLAST || this->actor.params == TW_ICE_BLAST_GROUND) {
this->actionFunc = BossTw_BlastIce;
} else if (this->actor.params >= TW_DEATHBALL_KOTAKE) {
this->actionFunc = BossTw_DeathBall;
this->actor.draw = BossTw_DrawDeathBall;
this->workf[TAIL_ALPHA] = 128.0f;
if (thisx->params == TW_DEATHBALL_KOTAKE) {
thisx->world.rot.y = sTwinrovaPtr->actor.world.rot.y + 0x4000;
} else {
thisx->world.rot.y = sTwinrovaPtr->actor.world.rot.y - 0x4000;
}
}
this->timers[1] = 150;
return;
}
Actor_SetScale(&this->actor, 2.5 * 0.01f);
this->actor.colChkInfo.mass = 255;
this->actor.colChkInfo.health = 0;
Collider_InitCylinder(globalCtx, &this->collider);
if (!sTwInitalized) {
sTwInitalized = true;
globalCtx->envCtx.unk_BF = 1;
globalCtx->envCtx.unk_BE = 1;
globalCtx->envCtx.unk_BD = 1;
globalCtx->envCtx.unk_D8 = 0.0f;
D_8094C874 = D_8094C876 = D_8094C878 = D_8094C87A = D_8094C87C = D_8094C87E = D_8094C870 = D_8094C86F =
D_8094C872 = sBeamDivertTimer = sEnvType = sGroundBlastType = sFreezeState = sTwinrovaBlastType =
sFixedBlatSeq = sShieldFireCharge = sShieldIceCharge = 0;
D_8094C858 = D_8094C854 = 0.0f;
sFixedBlastType = Rand_ZeroFloat(1.99f);
globalCtx->specialEffects = sTwEffects;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
sTwEffects[i].type = TWEFF_NONE;
sTwEffects[i].epoch++;
}
}
if (this->actor.params == TW_KOTAKE) {
Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInitKoumeKotake);
this->actor.naviEnemyId = 0x33;
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &object_tw_Skel_0070E0, &object_tw_Anim_006F28, NULL, NULL, 0);
if (gSaveContext.eventChkInf[7] & 0x20) {
// began twinrova battle
BossTw_SetupFlyTo(this, globalCtx);
this->actor.world.pos.x = -600.0f;
this->actor.world.pos.y = 400.0f;
this->actor.world.pos.z = 0.0f;
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS);
} else {
BossTw_SetupCSWait(this, globalCtx);
}
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_006F28, -3.0f);
this->visible = true;
} else if (this->actor.params == TW_KOUME) {
Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInitKoumeKotake);
this->actor.naviEnemyId = 0x32;
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &object_tw_Skel_01F888, &object_tw_Anim_006F28, NULL, NULL, 0);
if (gSaveContext.eventChkInf[7] & 0x20) {
// began twinrova battle
BossTw_SetupFlyTo(this, globalCtx);
this->actor.world.pos.x = 600.0f;
this->actor.world.pos.y = 400.0f;
this->actor.world.pos.z = 0.0f;
} else {
BossTw_SetupCSWait(this, globalCtx);
}
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_006F28, -3.0f);
this->visible = true;
} else {
// Twinrova
Collider_SetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInitTwinrova);
this->actor.naviEnemyId = 0x5B;
this->actor.colChkInfo.health = 24;
this->actor.update = BossTw_TwinrovaUpdate;
this->actor.draw = BossTw_TwinrovaDraw;
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &object_tw_Skel_032020, &object_tw_Anim_0244B4, NULL, NULL, 0);
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_0244B4, -3.0f);
if (gSaveContext.eventChkInf[7] & 0x20) {
// began twinrova battle
BossTw_SetupWait(this, globalCtx);
} else {
BossTw_TwinrovaSetupIntroCS(this, globalCtx);
this->actor.world.pos.x = 0.0f;
this->actor.world.pos.y = 1000.0f;
this->actor.world.pos.z = 0.0f;
}
this->actor.params = TW_TWINROVA;
sTwinrovaPtr = this;
if (Flags_GetClear(globalCtx, globalCtx->roomCtx.curRoom.num)) {
// twinrova has been defeated.
Actor_Kill(&this->actor);
Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_DOOR_WARP1, 600.0f, 230.0f, 0.0f, 0,
0, 0, WARP_DUNGEON_ADULT);
Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_ITEM_B_HEART, -600.0f, 230.0f, 0.0f, 0, 0, 0, 0);
} else {
sKotakePtr = (BossTw*)Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->actor.world.pos.x, this->actor.world.pos.y,
this->actor.world.pos.z, 0, 0, 0, TW_KOTAKE);
sKoumePtr = (BossTw*)Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->actor.world.pos.x, this->actor.world.pos.y,
this->actor.world.pos.z, 0, 0, 0, TW_KOUME);
sKotakePtr->actor.parent = &sKoumePtr->actor;
sKoumePtr->actor.parent = &sKotakePtr->actor;
}
}
this->fogR = globalCtx->lightCtx.fogColor[0];
this->fogG = globalCtx->lightCtx.fogColor[1];
this->fogB = globalCtx->lightCtx.fogColor[2];
this->fogNear = globalCtx->lightCtx.fogNear;
this->fogFar = 1000.0f;
}
void BossTw_Destroy(Actor* thisx, GlobalContext* globalCtx) {
BossTw* this = (BossTw*)thisx;
Collider_DestroyCylinder(globalCtx, &this->collider);
if (thisx->params < TW_FIRE_BLAST) {
SkelAnime_Free(&this->skelAnime, globalCtx);
}
if (thisx->params == TW_TWINROVA) {
sTwInitalized = false;
}
}
void BossTw_SetupTurnToPlayer(BossTw* this, GlobalContext* globalCtx) {
BossTw* otherTw = (BossTw*)this->actor.parent;
this->actionFunc = BossTw_TurnToPlayer;
if ((otherTw != NULL) && (otherTw->actionFunc == BossTw_ShootBeam)) {
this->timers[0] = 40;
} else {
this->timers[0] = 60;
}
this->rotateSpeed = 0.0f;
}
void BossTw_TurnToPlayer(BossTw* this, GlobalContext* globalCtx) {
BossTw* otherTw = (BossTw*)this->actor.parent;
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->actor.speedXZ, 0.0f, 1.0f, 1.0f);
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, this->rotateSpeed);
Math_ApproachS(&this->actor.shape.rot.x, 0, 5, this->rotateSpeed);
Math_ApproachF(&this->rotateSpeed, 4096.0f, 1.0f, 200.0f);
func_8002D908(&this->actor);
func_8002D7EC(&this->actor);
if (this->timers[0] == 0) {
if ((otherTw->actionFunc != BossTw_ShootBeam) && this->work[CAN_SHOOT]) {
this->work[CAN_SHOOT] = false;
BossTw_SetupShootBeam(this, globalCtx);
this->actor.speedXZ = 0.0f;
} else {
BossTw_SetupFlyTo(this, globalCtx);
}
}
}
void BossTw_SetupFlyTo(BossTw* this, GlobalContext* globalCtx) {
static Vec3f sPillarPositions[] = {
{ 600.0f, 400.0f, 0.0f }, { 0.0f, 400.0f, 600.0f }, { -600.0f, 400.0f, 0.0f }, { 0.0f, 400.0f, -600.0f }
};
BossTw* otherTw = (BossTw*)this->actor.parent;
this->unk_5F8 = 1;
this->actor.flags |= ACTOR_FLAG_0;
this->actionFunc = BossTw_FlyTo;
this->rotateSpeed = 0.0f;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_006F28, -10.0f);
if ((Rand_ZeroOne() < 0.5f) && (otherTw != NULL && otherTw->actionFunc == BossTw_ShootBeam)) {
// Other Sister is shooting a beam, go near them.
this->targetPos.x = otherTw->actor.world.pos.x + Rand_CenteredFloat(200.0f);
this->targetPos.y = Rand_ZeroFloat(200.0f) + 340.0f;
this->targetPos.z = otherTw->actor.world.pos.z + Rand_CenteredFloat(200.0f);
this->timers[0] = (s16)Rand_ZeroFloat(50.0f) + 50;
} else if (Rand_ZeroOne() < 0.5f) {
// Fly to a random spot.
this->targetPos.x = Rand_CenteredFloat(800.0f);
this->targetPos.y = Rand_ZeroFloat(200.0f) + 340.0f;
this->targetPos.z = Rand_CenteredFloat(800.0f);
this->timers[0] = (s16)Rand_ZeroFloat(50.0f) + 50;
} else {
// fly to a random pillar.
s16 idx = Rand_ZeroFloat(ARRAY_COUNT(sPillarPositions) - 0.01f);
this->targetPos = sPillarPositions[idx];
this->timers[0] = 200;
this->work[CAN_SHOOT] = true;
}
}
void BossTw_FlyTo(BossTw* this, GlobalContext* globalCtx) {
f32 xDiff;
f32 yDiff;
f32 zDiff;
f32 pitchTarget;
f32 yawTarget;
f32 xzDist;
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
Math_ApproachF(&this->scepterAlpha, 0.0f, 1.0f, 10.0f);
SkelAnime_Update(&this->skelAnime);
xDiff = this->targetPos.x - this->actor.world.pos.x;
yDiff = this->targetPos.y - this->actor.world.pos.y;
zDiff = this->targetPos.z - this->actor.world.pos.z;
yawTarget = (s16)(Math_FAtan2F(xDiff, zDiff) * (32768.0f / M_PI));
xzDist = sqrtf(SQ(xDiff) + SQ(zDiff));
pitchTarget = (s16)(Math_FAtan2F(yDiff, xzDist) * (32768.0f / M_PI));
Math_ApproachS(&this->actor.world.rot.x, pitchTarget, 0xA, this->rotateSpeed);
Math_ApproachS(&this->actor.world.rot.y, yawTarget, 0xA, this->rotateSpeed);
Math_ApproachS(&this->actor.shape.rot.y, yawTarget, 0xA, this->rotateSpeed);
Math_ApproachS(&this->actor.shape.rot.x, pitchTarget, 0xA, this->rotateSpeed);
Math_ApproachF(&this->rotateSpeed, 4096.0f, 1.0f, 100.0f);
Math_ApproachF(&this->actor.speedXZ, 10.0f, 1.0f, 1.0f);
func_8002D908(&this->actor);
func_8002D7EC(&this->actor);
if ((this->timers[0] == 0) || (xzDist < 70.0f)) {
BossTw_SetupTurnToPlayer(this, globalCtx);
}
}
void BossTw_SetupShootBeam(BossTw* this, GlobalContext* globalCtx) {
Player* player = GET_PLAYER(globalCtx);
this->actionFunc = BossTw_ShootBeam;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_007688, -5.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_007688);
this->timers[1] = 70;
this->targetPos = player->actor.world.pos;
this->csState1 = 0;
this->beamDist = 0.0f;
this->beamReflectionDist = 0.0f;
this->beamShootState = -1;
this->beamScale = 0.01f;
this->beamReflectionOrigin = this->beamOrigin;
this->flameAlpha = 0.0f;
this->spawnPortalAlpha = 0.0f;
this->spawnPortalScale = 2000.0f;
this->updateRate1 = 0.0f;
this->portalRotation = 0.0f;
this->updateRate2 = 0.0f;
}
void BossTw_SpawnGroundBlast(BossTw* this, GlobalContext* globalCtx, s16 blastType) {
BossTw* groundBlast;
s16 i;
Vec3f pos;
Vec3f velocity;
Vec3f accel;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_ZeroFloat(10.0f);
velocity.z = Rand_CenteredFloat(20.0f);
accel.y = 0.2f;
accel.x = Rand_CenteredFloat(0.25f);
accel.z = Rand_CenteredFloat(0.25f);
pos = this->groundBlastPos;
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 8, blastType, 75);
}
if (blastType == 1) {
sGroundBlastType = 1;
groundBlast = (BossTw*)Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->groundBlastPos.x, this->groundBlastPos.y,
this->groundBlastPos.z, 0, 0, 0, TW_FIRE_BLAST_GROUND);
if (groundBlast != NULL) {
if (sTwinrovaPtr->actionFunc == BossTw_Wait) {
groundBlast->timers[0] = 100;
} else {
groundBlast->timers[0] = 50;
}
sKoumePtr->workf[KM_GD_FLM_A] = sKoumePtr->workf[KM_GD_SMOKE_A] = sKoumePtr->workf[KM_GRND_CRTR_A] = 255.0f;
sKoumePtr->workf[KM_GD_FLM_SCL] = 1.0f;
sKoumePtr->workf[KM_GD_CRTR_SCL] = 0.005f;
sKoumePtr->groundBlastPos2 = groundBlast->actor.world.pos;
sEnvType = 4;
}
} else {
sGroundBlastType = 2;
groundBlast = (BossTw*)Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->groundBlastPos.x, this->groundBlastPos.y,
this->groundBlastPos.z, 0, 0, 0, TW_ICE_BLAST_GROUND);
if (groundBlast != NULL) {
if (sTwinrovaPtr->actionFunc == BossTw_Wait) {
groundBlast->timers[0] = 100;
} else {
groundBlast->timers[0] = 50;
}
sKotakePtr->workf[UNK_F11] = 50.0f;
sKotakePtr->workf[UNK_F9] = 250.0f;
sKotakePtr->workf[UNK_F12] = 0.005f;
sKotakePtr->workf[UNK_F14] = 1.0f;
sKotakePtr->workf[UNK_F16] = 70.0f;
sKotakePtr->groundBlastPos2 = groundBlast->actor.world.pos;
sEnvType = 3;
}
}
}
s32 BossTw_BeamHitPlayerCheck(BossTw* this, GlobalContext* globalCtx) {
Vec3f offset;
Vec3f beamDistFromPlayer;
Player* player = GET_PLAYER(globalCtx);
s16 i;
offset.x = player->actor.world.pos.x - this->beamOrigin.x;
offset.y = player->actor.world.pos.y - this->beamOrigin.y;
offset.z = player->actor.world.pos.z - this->beamOrigin.z;
Matrix_RotateX(-this->beamPitch, MTXMODE_NEW);
Matrix_RotateY(-this->beamYaw, MTXMODE_APPLY);
Matrix_MultVec3f(&offset, &beamDistFromPlayer);
if (fabsf(beamDistFromPlayer.x) < 20.0f && fabsf(beamDistFromPlayer.y) < 50.0f && beamDistFromPlayer.z > 100.0f &&
beamDistFromPlayer.z <= this->beamDist) {
if (sTwinrovaPtr->timers[2] == 0) {
sTwinrovaPtr->timers[2] = 150;
this->beamDist = sqrtf(SQ(offset.x) + SQ(offset.y) + SQ(offset.z));
func_8002F6D4(globalCtx, &this->actor, 3.0f, this->actor.shape.rot.y, 0.0f, 0x20);
if (this->actor.params == 0) {
if (sFreezeState == 0) {
sFreezeState = 1;
}
} else if (!player->isBurning) {
for (i = 0; i < ARRAY_COUNT(player->flameTimers); i++) {
player->flameTimers[i] = Rand_S16Offset(0, 200);
}
player->isBurning = true;
func_8002F7DC(&player->actor, player->ageProperties->unk_92 + NA_SE_VO_LI_DEMO_DAMAGE);
}
}
return true;
}
return false;
}
/**
* Checks if the beam shot by `this` will be reflected
* returns 0 if the beam will not be reflected,
* returns 1 if the beam will be reflected,
* and returns 2 if the beam will be diverted backwards
*/
s32 BossTw_CheckBeamReflection(BossTw* this, GlobalContext* globalCtx) {
Vec3f offset;
Vec3f vec;
Player* player = GET_PLAYER(globalCtx);
if (player->stateFlags1 & 0x400000 &&
(s16)(player->actor.shape.rot.y - this->actor.shape.rot.y + 0x8000) < 0x2000 &&
(s16)(player->actor.shape.rot.y - this->actor.shape.rot.y + 0x8000) > -0x2000) {
// player is shielding and facing angles are less than 45 degrees in either direction
offset.x = 0.0f;
offset.y = 0.0f;
offset.z = 10.0f;
// set beam check point to 10 units in front of link.
Matrix_RotateY(player->actor.shape.rot.y / 32768.0f * M_PI, MTXMODE_NEW);
Matrix_MultVec3f(&offset, &vec);
// calculates a vector where the origin is at the beams origin,
// and the positive z axis is pointing in the direction the beam
// is shooting
offset.x = player->actor.world.pos.x + vec.x - this->beamOrigin.x;
offset.y = player->actor.world.pos.y + vec.y - this->beamOrigin.y;
offset.z = player->actor.world.pos.z + vec.z - this->beamOrigin.z;
Matrix_RotateX(-this->beamPitch, MTXMODE_NEW);
Matrix_RotateY(-this->beamYaw, MTXMODE_APPLY);
Matrix_MultVec3f(&offset, &vec);
if (fabsf(vec.x) < 30.0f && fabsf(vec.y) < 70.0f && vec.z > 100.0f && vec.z <= this->beamDist) {
// if the beam's origin is within 30 x units, 70 y units, is farther than 100 units
// and the distance from the beams origin to 10 units in front of link is less than the beams
// current distance (the distance of the beam is equal to or longer than the distance to 10 units
// in front of link)
if (Player_HasMirrorShieldEquipped(globalCtx)) {
// player has mirror shield equipped
this->beamDist = sqrtf(SQ(offset.x) + SQ(offset.y) + SQ(offset.z));
return 1;
}
if (sBeamDivertTimer > 10) {
return 0;
}
if (sBeamDivertTimer == 0) {
// beam hit the shield, normal shield equipped,
// divert the beam backwards from link's Y rotation
BossTw_AddShieldDeflectEffect(globalCtx, 10.0f, this->actor.params);
globalCtx->envCtx.unk_D8 = 1.0f;
this->timers[0] = 10;
func_80078884(NA_SE_IT_SHIELD_REFLECT_MG2);
}
sBeamDivertTimer++;
this->beamDist = sqrtf(SQ(offset.x) + SQ(offset.y) + SQ(offset.z));
return 2;
}
}
return 0;
}
s32 BossTw_BeamReflHitCheck(BossTw* this, Vec3f* pos) {
Vec3f offset;
Vec3f beamDistFromTarget;
offset.x = pos->x - this->beamReflectionOrigin.x;
offset.y = pos->y - this->beamReflectionOrigin.y;
offset.z = pos->z - this->beamReflectionOrigin.z;
Matrix_RotateX(-this->beamReflectionPitch, MTXMODE_NEW);
Matrix_RotateY(-this->beamReflectionYaw, MTXMODE_APPLY);
Matrix_MultVec3f(&offset, &beamDistFromTarget);
if (fabsf(beamDistFromTarget.x) < 50.0f && fabsf(beamDistFromTarget.y) < 50.0f && beamDistFromTarget.z > 100.0f &&
beamDistFromTarget.z <= this->beamReflectionDist) {
this->beamReflectionDist = sqrtf(SQ(offset.x) + SQ(offset.y) + SQ(offset.z)) * 1.1f;
return true;
} else {
return false;
}
}
f32 BossTw_GetFloorY(Vec3f* pos) {
Vec3f posRotated;
if (fabsf(pos->x) < 350.0f && fabsf(pos->z) < 350.0f && pos->y < 240.0f) {
if (pos->y > 200.0f) {
return 240.0f;
}
return 35.0f;
}
if (fabsf(pos->x) < 110.0f && ((fabsf(pos->z - 600.0f) < 110.0f) || (fabsf(pos->z + 600.0f) < 110.0f)) &&
(pos->y < 230.0f)) {
if (pos->y > 190.0f) {
return 230.0f;
}
return 35.0f;
}
if (fabsf(pos->z) < 110.0f && ((fabsf(pos->x - 600.0f) < 110.0f) || (fabsf(pos->x + 600.0f) < 110.0f)) &&
(pos->y < 230.0f)) {
if (pos->y > 190.0f) {
return 230.0f;
}
return 35.0f;
}
if (pos->y < -20.0f) {
return 0.0f;
}
if (fabsf(pos->x) > 1140.0f || fabsf(pos->z) > 1140.0f) {
return 35.0f;
}
Matrix_Push();
Matrix_RotateY((45.0f * (M_PI / 180.0f)), MTXMODE_NEW);
Matrix_MultVec3f(pos, &posRotated);
Matrix_Pop();
if (fabsf(posRotated.x) > 920.0f || fabsf(posRotated.z) > 920.0f) {
return 35.0f;
}
return -100.0f;
}
void BossTw_ShootBeam(BossTw* this, GlobalContext* globalCtx) {
s16 i;
f32 xDiff;
f32 yDiff;
f32 zDiff;
f32 floorY;
Vec3f sp130;
Vec3s sp128;
Player* player = GET_PLAYER(globalCtx);
BossTw* otherTw = (BossTw*)this->actor.parent;
Input* input = &globalCtx->state.input[0];
Math_ApproachF(&this->actor.world.pos.y, 400.0f, 0.05f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 5.0f, 1.0f, 0.25f);
SkelAnime_Update(&this->skelAnime);
this->beamRoll += -0.3f;
if (this->timers[1] != 0) {
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, this->rotateSpeed);
if ((player->stateFlags1 & 0x400000) &&
((s16)((player->actor.shape.rot.y - this->actor.shape.rot.y) + 0x8000) < 0x2000) &&
((s16)((player->actor.shape.rot.y - this->actor.shape.rot.y) + 0x8000) > -0x2000)) {
Math_ApproachF(&this->targetPos.x, player->bodyPartsPos[15].x, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.y, player->bodyPartsPos[15].y, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.z, player->bodyPartsPos[15].z, 1.0f, 400.0f);
} else {
Math_ApproachF(&this->targetPos.x, player->actor.world.pos.x, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.y, player->actor.world.pos.y + 30.0f, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.z, player->actor.world.pos.z, 1.0f, 400.0f);
}
this->timers[0] = 70;
this->groundBlastPos.x = this->groundBlastPos.y = this->groundBlastPos.z = 0.0f;
this->portalRotation += this->updateRate2 * 0.0025f;
Math_ApproachF(&this->spawnPortalAlpha, 255.0f, 1.0f, 10.0f);
Math_ApproachF(&this->updateRate2, 50.0f, 1.0f, 2.0f);
if (this->timers[1] < 50) {
if (this->timers[1] < 10) {
if (this->timers[1] == 9) {
globalCtx->envCtx.unk_D8 = 0.5f;
globalCtx->envCtx.unk_BD = 3 - this->actor.params;
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_MASIC_SET);
}
if (this->timers[1] == 5) {
this->scepterAlpha = 255;
}
if (this->timers[1] > 4) {
s16 j;
for (j = 0; j < 2; j++) {
for (i = 0; i < ARRAY_COUNT(this->scepterFlamePos); i++) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = this->scepterFlamePos[i].x;
pos.y = this->scepterFlamePos[i].y;
pos.z = this->scepterFlamePos[i].z;
velocity.x = Rand_CenteredFloat(10.0f);
velocity.y = Rand_CenteredFloat(10.0f);
velocity.z = Rand_CenteredFloat(10.0f);
accel.x = 0.0f;
accel.y = 0.0f;
accel.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &accel, Rand_ZeroFloat(10.0f) + 25.0f,
this->actor.params);
}
}
}
}
if (this->timers[1] < 20) {
Math_ApproachF(&this->flameAlpha, 0, 1.0f, 20.0f);
Math_ApproachF(&this->spawnPortalAlpha, 0, 1.0f, 30.0f);
} else {
Math_ApproachF(&this->flameAlpha, 255.0f, 1.0f, 10.0f);
if (this->actor.params == 1) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_MS_FIRE - SFX_FLAG);
} else {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_MS_FREEZE - SFX_FLAG);
}
}
this->flameRotation += this->updateRate1 * 0.0025f;
Math_ApproachF(&this->spawnPortalScale, 0.0f, 0.1f, this->updateRate1);
Math_ApproachF(&this->updateRate1, 50.0f, 1.0f, 2.0f);
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_009398, 0.0f);
this->workf[ANIM_SW_TGT] = 10000.0f;
}
if (this->timers[1] == 1) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_003614, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_003614);
this->unk_4DC = 0.0f;
this->spawnPortalAlpha = 0.0f;
this->flameAlpha = 0.0f;
sBeamDivertTimer = 0;
}
} else {
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_003E34, 0.0f);
this->workf[ANIM_SW_TGT] = 10000.0f;
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT] - 5.0f)) {
this->beamShootState = 0;
sEnvType = this->actor.params + 1;
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT] - 13.0f)) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_THROW_MASIC);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_SHOOT_VOICE);
}
xDiff = this->targetPos.x - this->beamOrigin.x;
yDiff = this->targetPos.y - this->beamOrigin.y;
zDiff = this->targetPos.z - this->beamOrigin.z;
this->beamYaw = Math_FAtan2F(xDiff, zDiff);
this->beamPitch = -Math_FAtan2F(yDiff, sqrtf(SQ(xDiff) + SQ(zDiff)));
switch (this->beamShootState) {
case -1:
break;
case 0:
if (this->timers[0] != 0) {
s32 beamReflection = BossTw_CheckBeamReflection(this, globalCtx);
if (beamReflection == 1) {
Vec3f pos;
Vec3f velocity;
Vec3f accel = { 0.0f, 0.0f, 0.0f };
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
velocity.x = Rand_CenteredFloat(15.0f);
velocity.y = Rand_CenteredFloat(15.0f);
velocity.z = Rand_CenteredFloat(15.0f);
pos = player->bodyPartsPos[15];
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 5,
this->actor.params, 150);
}
this->beamShootState = 1;
func_80078914(&player->actor.projectedPos, NA_SE_IT_SHIELD_REFLECT_MG);
Matrix_MtxFToYXZRotS(&player->shieldMf, &sp128, 0);
sp128.y += 0x8000;
sp128.x = -sp128.x;
this->magicDir.x = sp128.x;
this->magicDir.y = sp128.y;
this->groundBlastPos.x = 0.0f;
this->groundBlastPos.y = 0.0f;
this->groundBlastPos.z = 0.0f;
globalCtx->envCtx.unk_D8 = 1.0f;
func_800AA000(0.0f, 0x64, 5, 4);
} else if (beamReflection == 0) {
BossTw_BeamHitPlayerCheck(this, globalCtx);
if (this->csState1 == 0) {
Math_ApproachF(&this->beamDist, 2.0f * sqrtf(SQ(xDiff) + SQ(yDiff) + SQ(zDiff)), 1.0f,
40.0f);
}
}
}
SkinMatrix_Vec3fMtxFMultXYZW(&globalCtx->viewProjectionMtxF, &this->beamReflectionOrigin,
&this->unk_54C, &this->actor.projectedW);
if (this->actor.params == 1) {
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_SHOOT_FIRE - SFX_FLAG, &this->unk_54C, 4, &D_801333E0,
&D_801333E0, &D_801333E8);
} else {
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_SHOOT_FREEZE - SFX_FLAG, &this->unk_54C, 4, &D_801333E0,
&D_801333E0, &D_801333E8);
}
break;
case 1:
if (CHECK_BTN_ALL(input->cur.button, BTN_R)) {
Player* player = GET_PLAYER(globalCtx);
this->beamDist = sqrtf(SQ(xDiff) + SQ(yDiff) + SQ(zDiff));
Math_ApproachF(&this->beamReflectionDist, 2000.0f, 1.0f, 40.0f);
Math_ApproachF(&this->targetPos.x, player->bodyPartsPos[15].x, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.y, player->bodyPartsPos[15].y, 1.0f, 400.0f);
Math_ApproachF(&this->targetPos.z, player->bodyPartsPos[15].z, 1.0f, 400.0f);
if ((this->work[CS_TIMER_1] % 4) == 0) {
BossTw_AddRingEffect(globalCtx, &player->bodyPartsPos[15], 0.5f, 3.0f, 0xFF, this->actor.params,
1, 150);
}
} else {
this->beamShootState = 0;
this->beamReflectionDist = 0.0f;
}
SkinMatrix_Vec3fMtxFMultXYZW(&globalCtx->viewProjectionMtxF, &this->unk_530, &this->unk_558,
&this->actor.projectedW);
if (this->actor.params == 1) {
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_SHOOT_FIRE - SFX_FLAG, &this->unk_558, 4U, &D_801333E0,
&D_801333E0, &D_801333E8);
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_REFL_FIRE - SFX_FLAG, &this->unk_558, 4, &D_801333E0,
&D_801333E0, &D_801333E8);
} else {
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_SHOOT_FREEZE - SFX_FLAG, &this->unk_558, 4, &D_801333E0,
&D_801333E0, &D_801333E8);
Audio_PlaySoundGeneral(NA_SE_EN_TWINROBA_REFL_FREEZE - SFX_FLAG, &this->unk_558, 4, &D_801333E0,
&D_801333E0, &D_801333E8);
}
break;
}
if (this->timers[0] == 0 && (sEnvType == 1 || sEnvType == 2)) {
sEnvType = 0;
}
if (this->timers[0] == 0) {
Math_ApproachF(&this->beamScale, 0.0f, 1.0f, 0.0005f);
if (this->beamScale == 0.0f) {
BossTw_SetupFinishBeamShoot(this, globalCtx);
this->beamReflectionDist = 0.0f;
this->beamDist = 0.0f;
}
}
}
Matrix_Translate(this->beamOrigin.x, this->beamOrigin.y, this->beamOrigin.z, MTXMODE_NEW);
Matrix_RotateY(this->beamYaw, MTXMODE_APPLY);
Matrix_RotateX(this->beamPitch, MTXMODE_APPLY);
sp130.x = 0.0f;
sp130.y = 0.0f;
sp130.z = this->beamDist + -5.0f;
Matrix_MultVec3f(&sp130, &this->beamReflectionOrigin);
if ((this->csState1 == 0) && (this->beamShootState == 0) && (this->timers[0] != 0)) {
this->groundBlastPos.y = BossTw_GetFloorY(&this->beamReflectionOrigin);
if (this->groundBlastPos.y >= 0.0f) {
this->csState1 = 1;
this->groundBlastPos.x = this->beamReflectionOrigin.x;
this->groundBlastPos.z = this->beamReflectionOrigin.z;
BossTw_SpawnGroundBlast(this, globalCtx, this->actor.params);
this->timers[0] = 20;
}
}
if (this->beamShootState == 1) {
if (this->csState1 == 0) {
Matrix_MtxFToYXZRotS(&player->shieldMf, &sp128, 0);
sp128.y += 0x8000;
sp128.x = -sp128.x;
Math_ApproachS(&this->magicDir.x, sp128.x, 5, 0x2000);
Math_ApproachS(&this->magicDir.y, sp128.y, 5, 0x2000);
this->beamReflectionPitch = (this->magicDir.x / 32768.0f) * M_PI;
this->beamReflectionYaw = (this->magicDir.y / 32768.0f) * M_PI;
}
Matrix_Translate(this->beamReflectionOrigin.x, this->beamReflectionOrigin.y, this->beamReflectionOrigin.z,
MTXMODE_NEW);
Matrix_RotateY(this->beamReflectionYaw, MTXMODE_APPLY);
Matrix_RotateX(this->beamReflectionPitch, MTXMODE_APPLY);
sp130.x = 0.0f;
sp130.y = 0.0f;
sp130.z = this->beamReflectionDist + -170.0f;
Matrix_MultVec3f(&sp130, &this->unk_530);
if (this->csState1 == 0) {
sp130.z = 0.0f;
for (i = 0; i < 200; i++) {
Vec3f spBC;
Matrix_MultVec3f(&sp130, &spBC);
floorY = BossTw_GetFloorY(&spBC);
this->groundBlastPos.y = floorY;
if (floorY >= 0.0f) {
if ((this->groundBlastPos.y != 35.0f) && (0.0f < this->beamReflectionPitch) &&
(this->timers[0] != 0)) {
this->csState1 = 1;
this->groundBlastPos.x = spBC.x;
this->groundBlastPos.z = spBC.z;
BossTw_SpawnGroundBlast(this, globalCtx, this->actor.params);
this->timers[0] = 20;
} else {
for (i = 0; i < 5; i++) {
Vec3f velocity;
Vec3f accel;
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
accel.x = 0.0f;
accel.y = 0.0f;
accel.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &this->unk_530, &velocity, &accel,
Rand_ZeroFloat(10.0f) + 25.0f, this->actor.params);
}
this->beamReflectionDist = sp130.z;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 0.8f, 1.0f, 0.2f);
}
break;
}
sp130.z += 20.0f;
if (this->beamReflectionDist < sp130.z) {
break;
}
}
}
if (BossTw_BeamReflHitCheck(this, &this->actor.world.pos) && (this->work[CS_TIMER_1] % 4) == 0) {
BossTw_AddRingEffect(globalCtx, &this->unk_530, 0.5f, 3.0f, 255, this->actor.params, 1, 150);
}
if (BossTw_BeamReflHitCheck(this, &otherTw->actor.world.pos) && otherTw->actionFunc != BossTw_HitByBeam) {
for (i = 0; i < 50; i++) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = otherTw->actor.world.pos.x + Rand_CenteredFloat(50.0f);
pos.y = otherTw->actor.world.pos.y + Rand_CenteredFloat(50.0f);
pos.z = otherTw->actor.world.pos.z + Rand_CenteredFloat(50.0f);
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
accel.x = 0.0f;
accel.y = 0.0f;
accel.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &accel, Rand_ZeroFloat(10.0f) + 25.0f,
this->actor.params);
}
BossTw_SetupHitByBeam(otherTw, globalCtx);
Audio_PlayActorSound2(&otherTw->actor, NA_SE_EN_TWINROBA_DAMAGE_VOICE);
globalCtx->envCtx.unk_D8 = 1.0f;
otherTw->actor.colChkInfo.health++;
}
}
}
void BossTw_SetupFinishBeamShoot(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_FinishBeamShoot;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_004548, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_004548);
}
void BossTw_FinishBeamShoot(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->scepterAlpha, 0.0f, 1.0f, 10.0f);
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
if (sTwinrovaPtr->timers[2] == 0) {
BossTw_SetupFlyTo(this, globalCtx);
} else {
BossTw_SetupLaugh(this, globalCtx);
}
this->scepterAlpha = 0.0f;
}
}
void BossTw_SetupHitByBeam(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_HitByBeam;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_00578C, 0.0f);
this->timers[0] = 53;
this->actor.speedXZ = 0.0f;
if (this->actor.params == 0) {
this->work[FOG_TIMER] = 20;
}
}
void BossTw_HitByBeam(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
if ((this->work[CS_TIMER_1] % 4) == 0) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = this->actor.world.pos.x + Rand_CenteredFloat(80.0f);
pos.y = this->actor.world.pos.y + Rand_CenteredFloat(80.0f);
pos.z = this->actor.world.pos.z + Rand_CenteredFloat(80.0f);
velocity.x = 0.0f;
velocity.y = 0.0f;
velocity.z = 0.0f;
accel.x = 0.0f;
accel.y = 0.1f;
accel.z = 0.0f;
BossTw_AddDmgCloud(globalCtx, this->actor.params + 2, &pos, &velocity, &accel, Rand_ZeroFloat(10.0f) + 15.0f, 0,
0, 150);
}
if (this->actor.params == 1) {
Math_ApproachF(&this->fogR, 255.0f, 1.0f, 30.0f);
Math_ApproachF(&this->fogG, 255.0f, 1.0f, 30.0f);
Math_ApproachF(&this->fogB, 255.0f, 1.0f, 30.0f);
Math_ApproachF(&this->fogNear, 900.0f, 1.0f, 30.0f);
Math_ApproachF(&this->fogFar, 1099.0f, 1.0f, 30.0f);
}
Math_ApproachF(&this->actor.world.pos.y, ((Math_SinS(this->work[CS_TIMER_1] * 1500) * 20.0f) + 350.0f) + 50.0f,
0.1f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 5.0f, 1.0f, 1.0f);
this->actor.world.pos.y -= 50.0f;
Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 50.0f, 50.0f, 100.0f, 4);
this->actor.world.pos.y += 50.0f;
if (this->actor.bgCheckFlags & 1) {
this->actor.speedXZ = 0.0f;
}
if (this->timers[0] == 1) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_006530, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_006530);
}
if ((this->timers[0] == 0) && Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
BossTw_SetupFlyTo(this, globalCtx);
}
}
void BossTw_SetupLaugh(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_Laugh;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_0088C8, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_0088C8);
this->actor.speedXZ = 0.0f;
}
void BossTw_Laugh(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
if (Animation_OnFrame(&this->skelAnime, 10.0f)) {
if (this->actor.params == TW_KOUME) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_LAUGH);
} else {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_LAUGH2);
}
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
BossTw_SetupFlyTo(this, globalCtx);
}
}
void BossTw_SetupSpin(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_Spin;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_007CA8, -3.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_007CA8);
this->actor.speedXZ = 0.0f;
SkelAnime_Update(&this->skelAnime);
this->timers[0] = 20;
}
void BossTw_Spin(BossTw* this, GlobalContext* globalCtx) {
if (this->timers[0] != 0) {
this->collider.base.colType = COLTYPE_METAL;
this->actor.shape.rot.y -= 0x3000;
if ((this->timers[0] % 4) == 0) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_ROLL);
}
} else {
SkelAnime_Update(&this->skelAnime);
Math_ApproachS(&this->actor.shape.rot.y, this->actor.world.rot.y, 3, 0x2000);
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
BossTw_SetupFlyTo(this, globalCtx);
}
}
}
void BossTw_SetupMergeCS(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_MergeCS;
this->rotateSpeed = 0.0f;
this->actor.speedXZ = 0.0f;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_006F28, -10.0f);
}
void BossTw_MergeCS(BossTw* this, GlobalContext* globalCtx) {
Math_ApproachF(&this->scepterAlpha, 0.0f, 1.0f, 10.0f);
SkelAnime_Update(&this->skelAnime);
}
void BossTw_SetupWait(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_Wait;
this->visible = false;
this->actor.world.pos.y = -2000.0f;
this->actor.flags &= ~ACTOR_FLAG_0;
}
void BossTw_Wait(BossTw* this, GlobalContext* globalCtx) {
if ((this->actor.params == TW_TWINROVA) && (sKoumePtr->actionFunc == BossTw_FlyTo) &&
(sKotakePtr->actionFunc == BossTw_FlyTo) &&
((sKoumePtr->actor.colChkInfo.health + sKotakePtr->actor.colChkInfo.health) >= 4)) {
BossTw_TwinrovaSetupMergeCS(this, globalCtx);
BossTw_SetupMergeCS(sKotakePtr, globalCtx);
BossTw_SetupMergeCS(sKoumePtr, globalCtx);
}
}
void BossTw_TwinrovaSetupMergeCS(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaMergeCS;
this->csState2 = 0;
this->csState1 = 0;
}
void BossTw_TwinrovaMergeCS(BossTw* this, GlobalContext* globalCtx) {
s16 i;
Vec3f spB0;
Vec3f spA4;
Player* player = GET_PLAYER(globalCtx);
switch (this->csState2) {
case 0:
this->csState2 = 1;
func_80064520(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 0x39);
this->subCamId = Gameplay_CreateSubCamera(globalCtx);
Gameplay_ChangeCameraStatus(globalCtx, 0, CAM_STAT_WAIT);
Gameplay_ChangeCameraStatus(globalCtx, this->subCamId, CAM_STAT_ACTIVE);
this->subCamDist = 800.0f;
this->subCamYaw = M_PI;
sKoumePtr->actor.world.rot.x = 0;
sKoumePtr->actor.shape.rot.x = 0;
sKotakePtr->actor.world.rot.x = 0;
sKotakePtr->actor.shape.rot.x = 0;
this->workf[UNK_F9] = 0.0f;
this->workf[UNK_F10] = 0.0f;
this->workf[UNK_F11] = 600.0f;
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xC800FF);
this->work[CS_TIMER_2] = 0;
// fallthrough
case 1:
if (this->work[CS_TIMER_2] == 20) {
Message_StartTextbox(globalCtx, 0x6059, NULL);
}
if (this->work[CS_TIMER_2] == 80) {
Message_StartTextbox(globalCtx, 0x605A, NULL);
}
this->subCamAt.x = 0.0f;
this->subCamAt.y = 440.0f;
this->subCamAt.z = 0.0f;
spB0.x = 0.0f;
spB0.y = 0.0f;
spB0.z = this->subCamDist;
Matrix_RotateY(this->subCamYaw, MTXMODE_NEW);
Matrix_MultVec3f(&spB0, &spA4);
this->subCamEye.x = spA4.x;
this->subCamEye.y = 300.0f;
this->subCamEye.z = spA4.z;
Math_ApproachF(&this->subCamYaw, 0.3f, 0.02f, 0.03f);
Math_ApproachF(&this->subCamDist, 200.0f, 0.1f, 5.0f);
break;
case 2:
spB0.x = 0.0f;
spB0.y = 0.0f;
spB0.z = this->subCamDist;
Matrix_RotateY(this->subCamYaw, MTXMODE_NEW);
Matrix_MultVec3f(&spB0, &spA4);
this->subCamEye.x = spA4.x;
this->subCamEye.z = spA4.z;
Math_ApproachF(&this->subCamEye.y, 420.0f, 0.1f, this->subCamUpdateRate * 20.0f);
Math_ApproachF(&this->subCamAt.y, 470.0f, 0.1f, this->subCamUpdateRate * 6.0f);
Math_ApproachF(&this->subCamYaw, 0.3f, 0.02f, 0.03f);
Math_ApproachF(&this->subCamDist, 60.0f, 0.1f, this->subCamUpdateRate * 32.0f);
Math_ApproachF(&this->subCamUpdateRate, 1, 1, 0.1f);
break;
}
if (this->subCamId != 0) {
if (this->unk_5F9 == 0) {
Gameplay_CameraSetAtEye(globalCtx, this->subCamId, &this->subCamAt, &this->subCamEye);
} else {
Gameplay_CameraSetAtEye(globalCtx, this->subCamId, &this->subCamAt2, &this->subCamEye2);
}
}
switch (this->csState1) {
case 0:
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
spB0.x = this->workf[UNK_F11];
spB0.y = 400.0f;
spB0.z = 0.0f;
Matrix_RotateY(this->workf[UNK_F9], MTXMODE_NEW);
Matrix_MultVec3f(&spB0, &spA4);
sKoumePtr->actor.world.pos.x = spA4.x;
sKoumePtr->actor.world.pos.y = spA4.y;
sKoumePtr->actor.world.pos.z = spA4.z;
sKoumePtr->actor.shape.rot.y = (this->workf[UNK_F9] / M_PI) * 32768.0f;
sKotakePtr->actor.world.pos.x = -spA4.x;
sKotakePtr->actor.world.pos.y = spA4.y;
sKotakePtr->actor.world.pos.z = -spA4.z;
sKotakePtr->actor.shape.rot.y = ((this->workf[UNK_F9] / M_PI) * 32768.0f) + 32768.0f;
Math_ApproachF(&this->workf[UNK_F11], 0.0f, 0.1f, 7.0f);
this->workf[UNK_F9] -= this->workf[UNK_F10];
Math_ApproachF(&this->workf[UNK_F10], 0.5f, 1, 0.0039999997f);
if (this->workf[UNK_F11] < 10.0f) {
if (!this->work[PLAYED_CHRG_SFX]) {
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_POWERUP);
this->work[PLAYED_CHRG_SFX] = true;
}
Math_ApproachF(&sKoumePtr->actor.scale.x, 0.005000001f, 1, 0.0003750001f);
for (i = 0; i < 4; i++) {
Vec3f pos;
f32 yOffset;
f32 xScale;
xScale = sKoumePtr->actor.scale.x * 3000.0f;
yOffset = Rand_CenteredFloat(xScale * 2.0f);
pos.x = 3000.0f;
pos.y = 400.0f + yOffset;
pos.z = 0.0f;
BossTw_AddMergeFlameEffect(globalCtx, &pos, Rand_ZeroFloat(5.0f) + 10.0f,
sqrtf(SQ(xScale) - SQ(yOffset)), Rand_ZeroFloat(1.99f));
}
if (sKoumePtr->actor.scale.x <= 0.0051f) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
this->actor.world.pos.y = 400.0f;
for (i = 0; i < 50; i++) {
pos = this->actor.world.pos;
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
pos.x += velocity.x;
pos.y += velocity.y;
pos.z += velocity.z;
accel.z = accel.y = accel.x = 0.0f;
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &accel, Rand_ZeroFloat(10.0f) + 25.0f,
velocity.x < 0.0f);
}
this->csState1 = 1;
this->visible = true;
this->actor.flags |= ACTOR_FLAG_0;
this->actor.shape.rot.y = 0;
BossTw_SetupWait(sKotakePtr, globalCtx);
BossTw_SetupWait(sKoumePtr, globalCtx);
Actor_SetScale(&this->actor, 0.0f);
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_038E2C, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_038E2C);
this->timers[0] = 50;
func_8002DF54(globalCtx, &this->actor, 2);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_TRANSFORM);
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS);
}
}
sKotakePtr->actor.scale.x = sKotakePtr->actor.scale.y = sKotakePtr->actor.scale.z =
sKoumePtr->actor.scale.y = sKoumePtr->actor.scale.z = sKoumePtr->actor.scale.x;
break;
case 1:
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, -15.0f);
}
sEnvType = -1;
globalCtx->envCtx.unk_BD = 4;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1, 1, 0.1f);
// fallthrough
case 2:
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->actor.scale.x, 0.0069999993f, 1, 0.0006999999f);
this->actor.scale.y = this->actor.scale.z = this->actor.scale.x;
if (this->timers[0] == 1) {
this->csState2 = 2;
this->subCamUpdateRate = 0.0f;
this->timers[1] = 65;
this->timers[2] = 90;
this->timers[3] = 50;
player->actor.world.pos.x = 0.0f;
player->actor.world.pos.y = 240.0f;
player->actor.world.pos.z = 270.0f;
player->actor.world.rot.y = player->actor.shape.rot.y = -0x8000;
this->subCamEye2.x = 0.0f;
this->subCamEye2.y = 290.0f;
this->subCamEye2.z = 222.0f;
this->subCamAt2.x = player->actor.world.pos.x;
this->subCamAt2.y = player->actor.world.pos.y + 54.0f;
this->subCamAt2.z = player->actor.world.pos.z;
}
if (this->timers[3] == 19) {
func_8002DF54(globalCtx, &this->actor, 5);
}
if (this->timers[3] == 16) {
func_8002F7DC(&player->actor, player->ageProperties->unk_92 + NA_SE_VO_LI_SURPRISE);
}
if ((this->timers[3] != 0) && (this->timers[3] < 20)) {
this->unk_5F9 = 1;
Math_ApproachF(&this->subCamEye2.z, 242.0f, 0.2f, 100.0f);
} else {
this->unk_5F9 = 0;
}
if (this->timers[1] == 8) {
this->work[TW_BLINK_IDX] = 8;
func_80078884(NA_SE_EN_TWINROBA_YOUNG_WINK);
}
if (this->timers[2] == 4) {
sEnvType = 0;
globalCtx->envCtx.unk_BE = 5;
}
if (this->timers[2] == 1) {
Camera* cam = Gameplay_GetCamera(globalCtx, MAIN_CAM);
cam->eye = this->subCamEye;
cam->eyeNext = this->subCamEye;
cam->at = this->subCamAt;
func_800C08AC(globalCtx, this->subCamId, 0);
this->subCamId = 0;
this->csState2 = this->subCamId;
func_80064534(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 7);
this->work[TW_PLLR_IDX] = 0;
this->targetPos = sTwinrovaPillarPos[0];
BossTw_TwinrovaSetupFly(this, globalCtx);
}
break;
}
}
void BossTw_SetupDeathCS(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_DeathCS;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_0004A4, -3.0f);
this->unk_5F8 = 0;
this->work[CS_TIMER_2] = Rand_ZeroFloat(20.0f);
}
void BossTw_DeathCS(BossTw* this, GlobalContext* globalCtx) {
if (this->timers[0] == 0) {
SkelAnime_Update(&this->skelAnime);
}
Math_ApproachS(&this->actor.shape.rot.y, this->work[YAW_TGT], 5, this->rotateSpeed);
Math_ApproachF(&this->rotateSpeed, 20480.0f, 1.0f, 1000.0f);
if (sTwinrovaPtr->work[CS_TIMER_2] > 140) {
Math_ApproachF(&this->fogR, 100.0f, 1.0f, 15.0f);
Math_ApproachF(&this->fogG, 255.0f, 1.0f, 15.0f);
Math_ApproachF(&this->fogB, 255.0f, 1.0f, 15.0f);
Math_ApproachF(&this->fogNear, 850.0f, 1.0f, 15.0f);
Math_ApproachF(&this->fogFar, 1099.0f, 1.0f, 15.0f);
}
}
void BossTw_SetupCSWait(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_CSWait;
this->visible = false;
this->actor.world.pos.y = -2000.0f;
this->actor.flags &= ~ACTOR_FLAG_0;
}
/**
* Do nothing while waiting for the inital cutscene to start
*/
void BossTw_CSWait(BossTw* this, GlobalContext* globalCtx) {
}
void BossTw_TwinrovaSetupIntroCS(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaIntroCS;
this->visible = false;
this->actor.world.pos.y = -2000.0f;
this->actor.flags &= ~ACTOR_FLAG_0;
}
void BossTw_TwinrovaIntroCS(BossTw* this, GlobalContext* globalCtx) {
u8 updateCam = 0;
s16 i;
Vec3f sp90;
Vec3f sp84;
Player* player = GET_PLAYER(globalCtx);
if (this->csSfxTimer > 220 && this->csSfxTimer < 630) {
func_80078884(NA_SE_EN_TWINROBA_UNARI - SFX_FLAG);
}
if (this->csSfxTimer == 180) {
func_80078914(&D_8094A7D0, NA_SE_EN_TWINROBA_LAUGH);
func_80078914(&D_8094A7D0, NA_SE_EN_TWINROBA_LAUGH2);
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_KOTAKE_KOUME);
}
this->csSfxTimer++;
switch (this->csState2) {
case 0:
this->csSfxTimer = 0;
if (SQ(player->actor.world.pos.x) + SQ(player->actor.world.pos.z) < SQ(150.0f)) {
player->actor.world.pos.x = player->actor.world.pos.z = .0f;
this->csState2 = 1;
func_80064520(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 0x39);
this->subCamId = Gameplay_CreateSubCamera(globalCtx);
Gameplay_ChangeCameraStatus(globalCtx, 0, CAM_STAT_WAIT);
Gameplay_ChangeCameraStatus(globalCtx, this->subCamId, CAM_STAT_ACTIVE);
this->subCamEye.x = 0.0f;
this->subCamEye.y = 350;
this->subCamEye.z = 200;
this->subCamEyeTarget.x = 450;
this->subCamEyeTarget.y = 900;
this->subCamAt.x = 0;
this->subCamAt.y = 270;
this->subCamAt.z = 0;
this->subCamAtTarget.x = 0;
this->subCamAtTarget.y = 240;
this->subCamAtTarget.z = 140;
this->subCamEyeTarget.z = 530;
this->subCamEyeStep.x = fabsf(this->subCamEyeTarget.x - this->subCamEye.x);
this->subCamEyeStep.y = fabsf(this->subCamEyeTarget.y - this->subCamEye.y);
this->subCamEyeStep.z = fabsf(this->subCamEyeTarget.z - this->subCamEye.z);
this->subCamAtStep.x = fabsf(this->subCamAtTarget.x - this->subCamAt.x);
this->subCamAtStep.y = fabsf(this->subCamAtTarget.y - this->subCamAt.y);
this->subCamAtStep.z = fabsf(this->subCamAtTarget.z - this->subCamAt.z);
this->subCamDistStep = 0.05f;
this->work[CS_TIMER_1] = 0;
}
break;
case 1:
updateCam = 1;
if (this->work[CS_TIMER_1] == 30) {
Message_StartTextbox(globalCtx, 0x6048, NULL);
}
Math_ApproachF(&this->subCamUpdateRate, 0.01f, 1.0f, 0.0001f);
if (this->work[CS_TIMER_1] > 100) {
globalCtx->envCtx.unk_BD = 0;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1.0f, 1.0f, 0.03f);
}
if (this->work[CS_TIMER_1] == 180) {
func_80078884(NA_SE_EN_TWINROBA_APPEAR_MS);
}
if (this->work[CS_TIMER_1] > 180) {
this->spawnPortalScale = 0.05f;
Math_ApproachF(&this->spawnPortalAlpha, 255.0f, 1.0f, 5.f);
if (this->work[CS_TIMER_1] >= 236) {
this->csState2 = 2;
sKoumePtr->visible = 1;
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_0004A4, 0.0f);
sKoumePtr->actor.world.pos.x = 0.0f;
sKoumePtr->actor.world.pos.y = 80.0f;
sKoumePtr->actor.world.pos.z = 600.0f;
sKoumePtr->actor.shape.rot.y = sKoumePtr->actor.world.rot.y = -0x8000;
this->subCamEye.x = -30;
this->subCamEye.y = 260;
this->subCamEye.z = 470;
this->subCamAt.x = 0.0F;
this->subCamAt.y = 270;
this->subCamAt.z = 600.0F;
this->work[CS_TIMER_1] = 0;
Actor_SetScale(&sKoumePtr->actor, 0.014999999f);
}
}
break;
case 2:
SkelAnime_Update(&sKoumePtr->skelAnime);
Math_ApproachF(&sKoumePtr->actor.world.pos.y, 240.0f, 0.05f, 5.0f);
this->subCamEye.x -= 0.2f;
this->subCamEye.z += 0.2f;
if (this->work[CS_TIMER_1] > 50) {
this->csState2 = 3;
this->subCamEyeTarget.x = -30;
this->subCamEyeTarget.y = 260;
this->subCamEyeTarget.z = 530;
this->subCamAtTarget.x = 0.0f;
this->subCamAtTarget.y = 265;
this->subCamAtTarget.z = 580;
this->subCamEyeStep.x = fabsf(this->subCamEyeTarget.x - this->subCamEye.x);
this->subCamEyeStep.y = fabsf(this->subCamEyeTarget.y - this->subCamEye.y);
this->subCamEyeStep.z = fabsf(this->subCamEyeTarget.z - this->subCamEye.z);
this->subCamAtStep.x = fabsf(this->subCamAtTarget.x - this->subCamAt.x);
this->subCamAtStep.y = fabsf(this->subCamAtTarget.y - this->subCamAt.y);
this->subCamAtStep.z = fabsf(this->subCamAtTarget.z - this->subCamAt.z);
this->subCamUpdateRate = 0;
this->subCamDistStep = 0.1f;
this->work[CS_TIMER_1] = 0;
}
break;
case 3:
SkelAnime_Update(&sKoumePtr->skelAnime);
updateCam = 1;
Math_ApproachF(&sKoumePtr->actor.world.pos.y, 240.0f, 0.05f, 5.0f);
Math_ApproachF(&this->subCamUpdateRate, 1.0f, 1.0f, 0.02f);
if (this->work[CS_TIMER_1] == 30) {
Message_StartTextbox(globalCtx, 0x6049, NULL);
}
if (this->work[CS_TIMER_1] > 80) {
this->csState2 = 4;
this->actor.speedXZ = 0;
this->subCamEyeTarget.x = -80.0f;
this->subCamEyeTarget.y = 260.0f;
this->subCamEyeTarget.z = 430.0f;
this->subCamAtTarget.x = sKoumePtr->actor.world.pos.x;
this->subCamAtTarget.y = sKoumePtr->actor.world.pos.y + 20.0f;
this->subCamAtTarget.z = sKoumePtr->actor.world.pos.z;
this->subCamEyeStep.x = fabsf(this->subCamEyeTarget.x - this->subCamEye.x);
this->subCamEyeStep.y = fabsf(this->subCamEyeTarget.y - this->subCamEye.y);
this->subCamEyeStep.z = fabsf(this->subCamEyeTarget.z - this->subCamEye.z);
this->subCamAtStep.x = fabsf(this->subCamAtTarget.x - this->subCamAt.x);
this->subCamAtStep.y = fabsf(this->subCamAtTarget.y - this->subCamAt.y);
this->subCamAtStep.z = fabsf(this->subCamAtTarget.z - this->subCamAt.z);
this->subCamUpdateRate = 0.0f;
this->subCamDistStep = 0.05f;
Animation_MorphToPlayOnce(&sKoumePtr->skelAnime, &object_tw_Anim_000AAC, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_000AAC);
this->work[CS_TIMER_1] = 0;
}
break;
case 4:
updateCam = 1;
SkelAnime_Update(&sKoumePtr->skelAnime);
this->subCamAtTarget.y = 20.0f + sKoumePtr->actor.world.pos.y;
Math_ApproachF(&sKoumePtr->actor.world.pos.y, 350, 0.1f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 9.0f, 1.0f, 0.9f);
Math_ApproachF(&this->subCamUpdateRate, 1.0f, 1.0f, 0.02f);
if (this->work[CS_TIMER_1] >= 30) {
if (this->work[CS_TIMER_1] < 45) {
globalCtx->envCtx.unk_BE = 0;
globalCtx->envCtx.unk_BD = 2;
globalCtx->envCtx.unk_D8 = 1.0f;
} else {
Math_ApproachZeroF(&globalCtx->envCtx.unk_D8, 1.0f, 0.1f);
}
if (this->work[CS_TIMER_1] == 30) {
for (i = 0; i < 50; i++) {
Vec3f pos;
Vec3f velocity;
pos.x = sKoumePtr->actor.world.pos.x + Rand_CenteredFloat(50.0f);
pos.y = sKoumePtr->actor.world.pos.y + Rand_CenteredFloat(50.0f);
pos.z = sKoumePtr->actor.world.pos.z + Rand_CenteredFloat(50.0f);
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &sZeroVector, Rand_ZeroFloat(10.0f) + 25.0f,
1);
}
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_TRANSFORM);
globalCtx->envCtx.unk_D8 = 0;
}
if (this->work[CS_TIMER_1] >= 35) {
if (this->work[CS_TIMER_1] < 50) {
Math_ApproachF(&sKoumePtr->actor.scale.x,
((Math_SinS(this->work[CS_TIMER_1] * 0x4200) * 20.0f) / 10000.0f) + 0.024999999f,
1.0f, 0.005f);
} else {
if (this->work[CS_TIMER_1] == 50) {
Animation_MorphToPlayOnce(&sKoumePtr->skelAnime, &object_tw_Anim_0088C8, -5);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_0088C8);
}
if (this->work[CS_TIMER_1] == 60) {
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_LAUGH);
}
if (Animation_OnFrame(&sKoumePtr->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_006F28, 0.f);
this->workf[ANIM_SW_TGT] = 1000.0f;
}
Math_ApproachF(&sKoumePtr->actor.scale.x, 0.024999999f, 0.1f, 0.005f);
}
Actor_SetScale(&sKoumePtr->actor, sKoumePtr->actor.scale.x);
sKoumePtr->actor.shape.rot.y = -0x8000;
sKoumePtr->unk_5F8 = 1;
if (this->work[CS_TIMER_1] == 0x64) {
this->csState2 = 10;
this->work[CS_TIMER_1] = 0;
this->subCamYawStep = 0.0f;
sKotakePtr->visible = 1;
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_0004A4, 0.0f);
sKotakePtr->actor.world.pos.x = 0.0f;
sKotakePtr->actor.world.pos.y = 80.0f;
sKotakePtr->actor.world.pos.z = -600.0f;
sKotakePtr->actor.shape.rot.y = sKotakePtr->actor.world.rot.y = 0;
this->work[CS_TIMER_1] = 0;
this->subCamEye.x = -30.0f;
this->subCamEye.y = 260.0f;
this->subCamEye.z = -470.0f;
this->subCamAt.x = 0;
this->subCamAt.y = 270.0f;
this->subCamAt.z = -600.0f;
Actor_SetScale(&sKotakePtr->actor, 0.014999999f);
}
} else {
sKoumePtr->actor.shape.rot.y = sKoumePtr->actor.shape.rot.y + (s16)this->subCamYawStep;
}
} else {
if ((this->work[CS_TIMER_1] % 8) == 0) {
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_ROLL);
}
sKoumePtr->actor.shape.rot.y = sKoumePtr->actor.shape.rot.y + (s16)this->subCamYawStep;
Math_ApproachF(&this->subCamYawStep, 12288.0f, 1.0f, 384.0f);
if (Animation_OnFrame(&sKoumePtr->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_006F28, 0.0f);
this->workf[ANIM_SW_TGT] = 1000.0f;
}
}
break;
case 10:
SkelAnime_Update(&sKotakePtr->skelAnime);
Math_ApproachF(&sKotakePtr->actor.world.pos.y, 240.0f, 0.05f, 5.0f);
this->subCamEye.x -= 0.2f;
this->subCamEye.z -= 0.2f;
if (this->work[CS_TIMER_1] >= 0x33) {
this->csState2 = 11;
this->subCamEyeTarget.x = -30;
this->subCamEyeTarget.y = 260;
this->subCamEyeTarget.z = -530;
this->subCamAtTarget.x = 0;
this->subCamAtTarget.y = 265;
this->subCamAtTarget.z = -580;
this->subCamEyeStep.x = fabsf(this->subCamEyeTarget.x - this->subCamEye.x);
this->subCamEyeStep.y = fabsf(this->subCamEyeTarget.y - this->subCamEye.y);
this->subCamEyeStep.z = fabsf(this->subCamEyeTarget.z - this->subCamEye.z);
this->subCamAtStep.x = fabsf(this->subCamAtTarget.x - this->subCamAt.x);
this->subCamAtStep.y = fabsf(this->subCamAtTarget.y - this->subCamAt.y);
this->subCamAtStep.z = fabsf(this->subCamAtTarget.z - this->subCamAt.z);
this->subCamUpdateRate = 0;
this->subCamDistStep = 0.1f;
this->work[CS_TIMER_1] = 0;
}
break;
case 11:
SkelAnime_Update(&sKotakePtr->skelAnime);
updateCam = 1;
Math_ApproachF(&sKotakePtr->actor.world.pos.y, 240.0f, 0.05f, 5.0f);
Math_ApproachF(&this->subCamUpdateRate, 1.0f, 1.0f, 0.02f);
if (this->work[CS_TIMER_1] == 30) {
Message_StartTextbox(globalCtx, 0x604A, NULL);
}
if (this->work[CS_TIMER_1] > 80) {
this->csState2 = 12;
this->actor.speedXZ = 0;
this->subCamEyeTarget.y = 260.0f;
this->subCamEyeTarget.x = -80.0f;
this->subCamEyeTarget.z = -430.0f;
this->subCamAtTarget.x = sKotakePtr->actor.world.pos.x;
this->subCamAtTarget.y = sKotakePtr->actor.world.pos.y + 20.0f;
this->subCamAtTarget.z = sKotakePtr->actor.world.pos.z;
this->subCamEyeStep.x = fabsf(this->subCamEyeTarget.x - this->subCamEye.x);
this->subCamEyeStep.y = fabsf(this->subCamEyeTarget.y - this->subCamEye.y);
this->subCamEyeStep.z = fabsf(this->subCamEyeTarget.z - this->subCamEye.z);
this->subCamAtStep.x = fabsf(this->subCamAtTarget.x - this->subCamAt.x);
this->subCamAtStep.y = fabsf(this->subCamAtTarget.y - this->subCamAt.y);
this->subCamAtStep.z = fabsf(this->subCamAtTarget.z - this->subCamAt.z);
this->subCamUpdateRate = 0;
this->subCamDistStep = 0.05f;
Animation_MorphToPlayOnce(&sKotakePtr->skelAnime, &object_tw_Anim_000AAC, 0);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_000AAC);
this->work[CS_TIMER_1] = 0;
}
break;
case 12:
updateCam = 1;
SkelAnime_Update(&sKotakePtr->skelAnime);
this->subCamAtTarget.y = sKotakePtr->actor.world.pos.y + 20.0f;
Math_ApproachF(&sKotakePtr->actor.world.pos.y, 350, 0.1f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 9.0f, 1.0f, 0.9f);
Math_ApproachF(&this->subCamUpdateRate, 1.0f, 1.0f, 0.02f);
if (this->work[CS_TIMER_1] >= 30) {
if (this->work[CS_TIMER_1] < 45) {
globalCtx->envCtx.unk_BD = 3;
globalCtx->envCtx.unk_D8 = 1.0f;
} else {
Math_ApproachZeroF(&globalCtx->envCtx.unk_D8, 1.0f, 0.1f);
}
if (this->work[CS_TIMER_1] == 30) {
for (i = 0; i < 50; i++) {
Vec3f pos;
Vec3f velocity;
pos.x = sKotakePtr->actor.world.pos.x + Rand_CenteredFloat(50.0f);
pos.y = sKotakePtr->actor.world.pos.y + Rand_CenteredFloat(50.0f);
pos.z = sKotakePtr->actor.world.pos.z + Rand_CenteredFloat(50.0f);
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &sZeroVector, Rand_ZeroFloat(10.f) + 25.0f,
0);
}
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_TRANSFORM);
globalCtx->envCtx.unk_D8 = 0.0f;
}
if (this->work[CS_TIMER_1] >= 35) {
if (this->work[CS_TIMER_1] < 50) {
Math_ApproachF(&sKotakePtr->actor.scale.x,
((Math_SinS(this->work[CS_TIMER_1] * 0x4200) * 20.0f) / 10000.0f) + 0.024999999f,
1.0f, 0.005f);
} else {
if (this->work[CS_TIMER_1] == 50) {
Animation_MorphToPlayOnce(&sKotakePtr->skelAnime, &object_tw_Anim_0088C8, -5.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_0088C8);
}
if (this->work[CS_TIMER_1] == 60) {
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_LAUGH2);
}
if (Animation_OnFrame(&sKotakePtr->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_006F28, 0.0f);
this->workf[ANIM_SW_TGT] = 1000.0f;
}
Math_ApproachF(&sKotakePtr->actor.scale.x, 0.024999999f, 0.1f, 0.005f);
}
Actor_SetScale(&sKotakePtr->actor, sKotakePtr->actor.scale.x);
sKotakePtr->actor.shape.rot.y = 0;
sKotakePtr->unk_5F8 = 1;
if (this->work[CS_TIMER_1] == 100) {
this->csState2 = 20;
this->work[CS_TIMER_1] = 0;
this->workf[UNK_F11] = 600.0f;
this->subCamEye.x = 800.0f;
this->subCamEye.y = 300.0f;
this->subCamEye.z = 0;
this->subCamAt.x = 0.0f;
this->subCamAt.y = 400.0f;
this->subCamAt.z = 0;
this->workf[UNK_F9] = -M_PI / 2.0f;
this->workf[UNK_F10] = 0.0f;
this->subCamEyeStep.x = 0.0f;
this->spawnPortalAlpha = 0.0f;
}
} else {
sKotakePtr->actor.shape.rot.y = sKotakePtr->actor.shape.rot.y + (s16)this->subCamYawStep;
}
} else {
if ((this->work[CS_TIMER_1] % 8) == 0) {
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_ROLL);
}
sKotakePtr->actor.shape.rot.y = sKotakePtr->actor.shape.rot.y + (s16)this->subCamYawStep;
Math_ApproachF(&this->subCamYawStep, 12288.0f, 1.0f, 384.0f);
if (Animation_OnFrame(&sKotakePtr->skelAnime, this->workf[ANIM_SW_TGT])) {
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_006F28, 0.0f);
this->workf[ANIM_SW_TGT] = 1000.0f;
}
}
break;
case 20:
if (this->work[CS_TIMER_1] > 20 && this->work[CS_TIMER_1] < 120) {
globalCtx->envCtx.unk_BD = 1;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1.0f, 1.0f, 0.015f);
}
if (this->work[CS_TIMER_1] == 90) {
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x5A00FF);
}
if (this->work[CS_TIMER_1] == 120) {
sEnvType = 0;
globalCtx->envCtx.unk_BE = 1;
globalCtx->envCtx.unk_BD = 1;
globalCtx->envCtx.unk_D8 = 0.0f;
TitleCard_InitBossName(globalCtx, &globalCtx->actorCtx.titleCtx, SEGMENTED_TO_VIRTUAL(gTwinrovaTitleCardTex), 160, 180, 128, 40, true);
gSaveContext.eventChkInf[7] |= 0x20;
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS);
}
if (this->work[CS_TIMER_1] >= 160) {
if (this->work[CS_TIMER_1] == 160) {
this->subCamEyeStep.x = 0.0f;
}
Math_ApproachF(&this->subCamEye.x, 0.0f, 0.05f, this->subCamEyeStep.x * 0.5f);
Math_ApproachF(&this->subCamEye.z, 1000.0f, 0.05f, this->subCamEyeStep.x);
Math_ApproachF(&this->subCamEyeStep.x, 40.0f, 1.0f, 1);
} else {
Math_ApproachF(&this->subCamEye.x, 300.0f, 0.05f, this->subCamEyeStep.x);
Math_ApproachF(&this->subCamEyeStep.x, 5.0f, 1.0f, 0.5f);
}
if (this->work[CS_TIMER_1] < 200) {
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
sp90.x = this->workf[UNK_F11];
sp90.y = 400.0f;
sp90.z = 0.0f;
Matrix_RotateY(this->workf[UNK_F9], MTXMODE_NEW);
Matrix_MultVec3f(&sp90, &sp84);
sKoumePtr->actor.world.pos.x = sp84.x;
sKoumePtr->actor.world.pos.y = sp84.y;
sKoumePtr->actor.world.pos.z = sp84.z;
sKoumePtr->actor.world.rot.y = sKoumePtr->actor.shape.rot.y = (this->workf[UNK_F9] / M_PI) * 32768.0f;
sKotakePtr->actor.world.pos.x = -sp84.x;
sKotakePtr->actor.world.pos.y = sp84.y;
sKotakePtr->actor.world.pos.z = -sp84.z;
sKotakePtr->actor.shape.rot.y = sKotakePtr->actor.world.rot.y =
((this->workf[UNK_F9] / M_PI) * 32768.0f) + 32768.0f;
Math_ApproachF(&this->workf[UNK_F11], 80.0f, 0.1f, 5.0f);
this->workf[UNK_F9] -= this->workf[UNK_F10];
Math_ApproachF(&this->workf[UNK_F10], 0.19999999f, 1.0f, 0.0019999994f);
}
if (this->work[CS_TIMER_1] == 200) {
sKoumePtr->actionFunc = BossTw_FlyTo;
sKotakePtr->actionFunc = BossTw_FlyTo;
sKoumePtr->targetPos.x = 600.0f;
sKoumePtr->targetPos.y = 400.0f;
sKoumePtr->targetPos.z = 0.0f;
sKoumePtr->timers[0] = 100;
sKotakePtr->targetPos.x = -600.0f;
sKotakePtr->targetPos.y = 400.0f;
sKotakePtr->targetPos.z = 0.0f;
sKotakePtr->timers[0] = 100;
}
if (this->work[CS_TIMER_1] == 260) {
Camera* cam = Gameplay_GetCamera(globalCtx, MAIN_CAM);
cam->eye = this->subCamEye;
cam->eyeNext = this->subCamEye;
cam->at = this->subCamAt;
func_800C08AC(globalCtx, this->subCamId, 0);
this->subCamId = 0;
this->csState2 = this->subCamId;
func_80064534(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 7);
BossTw_SetupWait(this, globalCtx);
}
break;
}
if (this->subCamId != 0) {
if (updateCam) {
Math_ApproachF(&this->subCamEye.x, this->subCamEyeTarget.x, this->subCamDistStep,
this->subCamEyeStep.x * this->subCamUpdateRate);
Math_ApproachF(&this->subCamEye.y, this->subCamEyeTarget.y, this->subCamDistStep,
this->subCamEyeStep.y * this->subCamUpdateRate);
Math_ApproachF(&this->subCamEye.z, this->subCamEyeTarget.z, this->subCamDistStep,
this->subCamEyeStep.z * this->subCamUpdateRate);
Math_ApproachF(&this->subCamAt.x, this->subCamAtTarget.x, this->subCamDistStep,
this->subCamAtStep.x * this->subCamUpdateRate);
Math_ApproachF(&this->subCamAt.y, this->subCamAtTarget.y, this->subCamDistStep,
this->subCamAtStep.y * this->subCamUpdateRate);
Math_ApproachF(&this->subCamAt.z, this->subCamAtTarget.z, this->subCamDistStep,
this->subCamAtStep.z * this->subCamUpdateRate);
}
Gameplay_CameraSetAtEye(globalCtx, this->subCamId, &this->subCamAt, &this->subCamEye);
}
}
void BossTw_DeathBall(BossTw* this, GlobalContext* globalCtx) {
f32 xDiff;
f32 yDiff;
f32 zDiff;
s32 pad;
s16 i;
s16 yaw;
if ((this->work[CS_TIMER_1] % 16) == 0) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_FB_FLY);
}
if (sTwinrovaPtr->csState2 < 2) {
if (this->timers[0] == 0) {
this->timers[0] = 20;
this->targetPos.x = Rand_CenteredFloat(100.0f) + sTwinrovaPtr->actor.world.pos.x;
this->targetPos.y = Rand_CenteredFloat(50.0f) + 400.0f;
this->targetPos.z = Rand_CenteredFloat(100.0f) + sTwinrovaPtr->actor.world.pos.z;
}
this->timers[1] = 10;
this->rotateSpeed = 8192.0f;
this->actor.speedXZ = 5.0f;
} else {
if (this->timers[1] == 9) {
this->targetPos.y = 413.0f;
this->actor.world.pos.z = 0.0f;
this->actor.world.pos.x = 0.0f;
for (i = 0; i < ARRAY_COUNT(this->blastTailPos); i++) {
this->blastTailPos[i] = this->actor.world.pos;
}
}
if (this->actor.params == 0x69) {
this->targetPos.x = sKoumePtr->actor.world.pos.x;
this->targetPos.z = sKoumePtr->actor.world.pos.z;
} else {
this->targetPos.x = sKotakePtr->actor.world.pos.x;
this->targetPos.z = sKotakePtr->actor.world.pos.z;
}
Math_ApproachF(&this->targetPos.y, 263.0f, 1.0f, 2.0f);
if (this->targetPos.y == 263.0f) {
Math_ApproachF(&this->actor.speedXZ, 0.0f, 1.0f, 0.2f);
if (sTwinrovaPtr->csState2 == 3) {
Actor_Kill(&this->actor);
}
}
}
xDiff = this->targetPos.x - this->actor.world.pos.x;
yDiff = this->targetPos.y - this->actor.world.pos.y;
zDiff = this->targetPos.z - this->actor.world.pos.z;
yaw = Math_FAtan2F(xDiff, zDiff) * (32768 / M_PI);
Math_ApproachS(&this->actor.world.rot.x, Math_FAtan2F(yDiff, sqrtf(SQ(xDiff) + SQ(zDiff))) * (32768 / M_PI), 5,
this->rotateSpeed);
Math_ApproachS(&this->actor.world.rot.y, yaw, 5, this->rotateSpeed);
func_8002D908(&this->actor);
func_8002D7EC(&this->actor);
}
void BossTw_TwinrovaSetupDeathCS(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaDeathCS;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_024374, -3.0f);
this->actor.world.rot.y = this->actor.shape.rot.y;
this->actor.flags &= ~ACTOR_FLAG_0;
this->csState2 = this->csState1 = 0;
this->work[CS_TIMER_1] = this->work[CS_TIMER_2] = 0;
this->work[INVINC_TIMER] = 10000;
BossTw_SetupDeathCS(sKoumePtr, globalCtx);
BossTw_SetupDeathCS(sKotakePtr, globalCtx);
sKotakePtr->timers[0] = 8;
this->workf[UNK_F19] = 1.0f;
}
void BossTw_DeathCSMsgSfx(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
s32 pad2;
s32 pad3;
s16 msgId2;
s16 msgId1;
u8 kotakeAnim;
u8 koumeAnim;
u8 sp35;
msgId2 = 0;
msgId1 = 0;
kotakeAnim = 0;
koumeAnim = 0;
sp35 = 0;
testing out item replacement (#416) * skip learning song of storms * don't set flag when getting goron tunic as child * Initiates prelude check when master sword unloads. Not quite how N64 rando does it but so far it's the only way I've found to make it trigger without also triggering the time travel again. * Stops Shadow Temple lore prompts from appearing in rando. * Skips cutscene of royal tomb explosion in rando. Explosion sound doesn't play correctly and I think the debris appears in the wrong place, but the functionality is here. * Improves visual of exploding gravestone. * Adds some comments explaining the rando differences * Skip ruto text box in jabu blue warp For rando * skip intro cutscene in dodongo's cavern * load spoiler files on boot, fix spoilerfile existing check when making new saves * name entry dropped spoiler logic * make sure to actually init the cvar * no chime on load * uncomment * Skip ganondrof cutscene Skip to scream part of the death animation, skipping the text boxes etc. For rando * Update z_boss_ganondrof.c * skip owl flight cutscenes in rando * Fixes skipped text so it only applies to shadow temple. Earlier fix inadvertently applied to some other text as well, changed logic so that only specified sceneNums and textIds can have this enabled, and text skipped by sceneNum can have the skip overriden by textId if needed. Currently there are no overrides so the textId section of the logic is commented out to avoid compilation errors. * Adds a default to the switch case statements that leaves the randoSkipText variable unchanged, just in case. * TEST: Text for item * Adding ganon flavor text * ADD: AMMO Count * format ganon text/hint text * Autoskip the tower cutscene if settings call for tower collapse. * ganon hint text logic * Improved prelude after time travel fix * swapped the sizes between ganon hint text and ganon text, as they were set to the wrong things. * this is all i did * not the cleanest code ever but it's working * ADD: GS Count * ADD: Wallter (crash for now) * TWEAK: Wallet check * FIX: Use DrawItem instread of DrawUpgrade... b-baka! * Fixes some vanilla bugs introduced by rando code. * Added cutscene skip for zelda escaping Using the debug cutscene skipping function. Also added a conditional so the bridge doesn't spawn closed when cutscene is ready to trigger * ADD: X Spacing + Placeholders for song * ADD: default case for items * TWEAK: Spacing * FIX: Light Arrow * ADD: Ammo Option * use groups instead * ADD: More spacing logic * songs and names * TWEAK: Color on wallet * colors * Added flags cutscene before nabooru fight * ADD: ChromaKey text * First attempt skip cs after nabooru defeat * Better implementation for specific rando cutscene skips * use pulseaudio defaults * spaces/tabs * move color push/pop to stop crash * make the colors work again * the real bottle fix * pulseaudio values tuned for n64 audio at 44.1khz * update tlength * remove one hardcoded samplerate * Cleaned up and fixed zelda escape skip The if statement is a freaking monster, but unless we want to skip more cutscenes in the same way later, this is the most compact way of doing it that I know of. * Revert one line to match original nothing functional * another hint line that breaks autonewline logic * don't autospawn epona if we don't have the song/ocarina * Trying to use iron knuckle death effects not working yet * Streamlined OoT cutscene skip for future additions Also cleaned up if statement in general * Made if statement more readable Also added clarity for what cutscene was skipped * Fixed typo in comment * Janky nabooru defeat cs skip * altar text formatting (gonna need help shortening some of the french ones) * more altar text formatting * english altar text formatting complete * make gtg blocking guard check for card not bridge * FIX: Typo! * FIX: Uppercases * FIX: Typo * TWEAK: Alter + some names * TWEAK: More caps! * ADD: Missing string TWEAK more uppercases and namefixe s * Hide nabooru death by covering her in flames * bandaid fix for death crash issue * Twinrova defeat cs skip Skips the animation and manually calls the function to show the "beam" around the sisters * fix crash * fix caps to match * fix great fairy reward mashing/shielding issue * TWEAK : Typo clé to Clé * TWEAK: Some Altar hints TWEAK: Some capitals * TWEAK: Unmatching text + some cap again * TWEAK: More tweaks * fix build * remove extra json.hpp, add hint * Update randomizer_item_tracker.cpp * TWEAK: Double Defense with RedParticles instead of white * make sure we don't optimize out the check to ensure a spoilerfile exists * vanilla ganon boss key hint formatting * TWEAK: FR- better way of the hero text * fix * and again * Initializes dungeonsDone items in gSaveContext to 0. * Replaces sizeof calculation with a NUM_DUNGEONS constant. * Fixes Saria's Gift on the LW Bridge from getting killed when holding shield. * More airtight fix for Saria's Gift on the Bridge. * Lifts one of the conditions in the if statement a little higher to prevent unnecessary lookups of getItemId. * Invalidate text box icon before drawing * Fixes the case where Saria's gift is an Ice Trap. We still get the Ice Trap once, but never again. This does mean you can now hold R while walking in to avoid the ice trap, but everything else seems to work fine. * Initial commit Might need changing when we change the settings in the future * Fixes Door of Time opening cutscene after warping with prelude. * Initial waterfall skip Very rudimentary way of doing things but it seems to work so :shrug: * inital rework * fixed default rotation for 2D sprites * fix tab/space issues * 3d drops rando merge fix again * Allows Impa to appear in the Lullaby check post drawbridge escape. * Changes Ganon's Trials Count setting to a checkbox The checkbox is whether or not to skip all of them. Leaving the box unchecked will mean doing all of them. Eventually this will be switched back to a slider once we implement the logic for which trials start out completed. * Sets all Ganon's Trials to incomplete in new saves. Fixes https://github.com/briaguya-ai/rando-issue-tracker/issues/131 * fix castle guards when oot throw cutscene has already played in rando * Properly removes the beams when trials are cleared. * Removes Question Mark from Skip Ganon's Trials UI. * Adds a todo comment about when to change back to slider. * make deku seeds check for bullet bag * Various tweaks TWEAK: Altar Text TWEAK: Hint names TWEAK: Replace more problematic œ to oe * upgrade ocarina on both child and adult equips * FIX: Jabu Item * update equipped hookshot/longshot when obtained as other age * add hint * don't give the bgs check without the claim check * Skips Darunia Cutscene in Fire Temple * Added a TODO note about not skipping the cutscene. There is a setting we will want to have eventually that will require this cutscene to not be skipped since it is used during a glitch. * remove todo * restore fast ocarina option in imgui that was lost in merge * Fixes grey screen issue + tooltip for 2 handed shield * update to use dg instead of g for textures in item tracker * TWEAK: Default color for cosmetic RAND button was not the corect one * fix texture crash, remove unused item tracker code * don't open mask shop until we get zelda's letter * Update README.md * Prevents "correct" chime under incorrect conditions. * Fixes typo in conditional and adds "bonk" sound effect. "Bonk" sound is NA_SE_SY_OCARINA_ERROR and it plays when conditions for the Door of Time have not been met after playing Song of Time. This is only possible in rando's "Intended" Door of Time option, in which the Ocarina of Time and all 3 spritual stones are required to open the door, instead of the vanilla requirements of just having the song of time. * remove modify dpad equips toggle, replace with checks for dpad menu * remove extra check * add ability to hold c-up to assign to dpad when dpad menuing is enabled * disable d-pad navigation on item menu when holding c-up to equip * dpad+c-up stuff for equipment menu * ADD: Checbox for songs colors * TWEAK: RandoColors for normal songs * kind of quick and dirty but it works * TWEAK: Clarity of the tooltip Co-authored-by: briaguya <briaguya@alice> Co-authored-by: Christopher Leggett <chris@leggett.dev> Co-authored-by: aMannus <mannusmenting@gmail.com> Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com> Co-authored-by: Dog <5172592+Dog@users.noreply.github.com> Co-authored-by: Vague Rant <vaguerant@users.noreply.github.com> Co-authored-by: Baoulettes <perlouzerie@hotmail.fr> Co-authored-by: Ada <60364512+GreatArgorath@users.noreply.github.com>
2022-07-11 20:11:07 -04:00
// Skip ahead to last part of the cutscene in rando
if (this->work[CS_TIMER_2] == 10 && gSaveContext.n64ddFlag) {
this->work[CS_TIMER_2] = 860;
}
if (this->work[CS_TIMER_2] == 80) {
koumeAnim = 1;
}
testing out item replacement (#416) * skip learning song of storms * don't set flag when getting goron tunic as child * Initiates prelude check when master sword unloads. Not quite how N64 rando does it but so far it's the only way I've found to make it trigger without also triggering the time travel again. * Stops Shadow Temple lore prompts from appearing in rando. * Skips cutscene of royal tomb explosion in rando. Explosion sound doesn't play correctly and I think the debris appears in the wrong place, but the functionality is here. * Improves visual of exploding gravestone. * Adds some comments explaining the rando differences * Skip ruto text box in jabu blue warp For rando * skip intro cutscene in dodongo's cavern * load spoiler files on boot, fix spoilerfile existing check when making new saves * name entry dropped spoiler logic * make sure to actually init the cvar * no chime on load * uncomment * Skip ganondrof cutscene Skip to scream part of the death animation, skipping the text boxes etc. For rando * Update z_boss_ganondrof.c * skip owl flight cutscenes in rando * Fixes skipped text so it only applies to shadow temple. Earlier fix inadvertently applied to some other text as well, changed logic so that only specified sceneNums and textIds can have this enabled, and text skipped by sceneNum can have the skip overriden by textId if needed. Currently there are no overrides so the textId section of the logic is commented out to avoid compilation errors. * Adds a default to the switch case statements that leaves the randoSkipText variable unchanged, just in case. * TEST: Text for item * Adding ganon flavor text * ADD: AMMO Count * format ganon text/hint text * Autoskip the tower cutscene if settings call for tower collapse. * ganon hint text logic * Improved prelude after time travel fix * swapped the sizes between ganon hint text and ganon text, as they were set to the wrong things. * this is all i did * not the cleanest code ever but it's working * ADD: GS Count * ADD: Wallter (crash for now) * TWEAK: Wallet check * FIX: Use DrawItem instread of DrawUpgrade... b-baka! * Fixes some vanilla bugs introduced by rando code. * Added cutscene skip for zelda escaping Using the debug cutscene skipping function. Also added a conditional so the bridge doesn't spawn closed when cutscene is ready to trigger * ADD: X Spacing + Placeholders for song * ADD: default case for items * TWEAK: Spacing * FIX: Light Arrow * ADD: Ammo Option * use groups instead * ADD: More spacing logic * songs and names * TWEAK: Color on wallet * colors * Added flags cutscene before nabooru fight * ADD: ChromaKey text * First attempt skip cs after nabooru defeat * Better implementation for specific rando cutscene skips * use pulseaudio defaults * spaces/tabs * move color push/pop to stop crash * make the colors work again * the real bottle fix * pulseaudio values tuned for n64 audio at 44.1khz * update tlength * remove one hardcoded samplerate * Cleaned up and fixed zelda escape skip The if statement is a freaking monster, but unless we want to skip more cutscenes in the same way later, this is the most compact way of doing it that I know of. * Revert one line to match original nothing functional * another hint line that breaks autonewline logic * don't autospawn epona if we don't have the song/ocarina * Trying to use iron knuckle death effects not working yet * Streamlined OoT cutscene skip for future additions Also cleaned up if statement in general * Made if statement more readable Also added clarity for what cutscene was skipped * Fixed typo in comment * Janky nabooru defeat cs skip * altar text formatting (gonna need help shortening some of the french ones) * more altar text formatting * english altar text formatting complete * make gtg blocking guard check for card not bridge * FIX: Typo! * FIX: Uppercases * FIX: Typo * TWEAK: Alter + some names * TWEAK: More caps! * ADD: Missing string TWEAK more uppercases and namefixe s * Hide nabooru death by covering her in flames * bandaid fix for death crash issue * Twinrova defeat cs skip Skips the animation and manually calls the function to show the "beam" around the sisters * fix crash * fix caps to match * fix great fairy reward mashing/shielding issue * TWEAK : Typo clé to Clé * TWEAK: Some Altar hints TWEAK: Some capitals * TWEAK: Unmatching text + some cap again * TWEAK: More tweaks * fix build * remove extra json.hpp, add hint * Update randomizer_item_tracker.cpp * TWEAK: Double Defense with RedParticles instead of white * make sure we don't optimize out the check to ensure a spoilerfile exists * vanilla ganon boss key hint formatting * TWEAK: FR- better way of the hero text * fix * and again * Initializes dungeonsDone items in gSaveContext to 0. * Replaces sizeof calculation with a NUM_DUNGEONS constant. * Fixes Saria's Gift on the LW Bridge from getting killed when holding shield. * More airtight fix for Saria's Gift on the Bridge. * Lifts one of the conditions in the if statement a little higher to prevent unnecessary lookups of getItemId. * Invalidate text box icon before drawing * Fixes the case where Saria's gift is an Ice Trap. We still get the Ice Trap once, but never again. This does mean you can now hold R while walking in to avoid the ice trap, but everything else seems to work fine. * Initial commit Might need changing when we change the settings in the future * Fixes Door of Time opening cutscene after warping with prelude. * Initial waterfall skip Very rudimentary way of doing things but it seems to work so :shrug: * inital rework * fixed default rotation for 2D sprites * fix tab/space issues * 3d drops rando merge fix again * Allows Impa to appear in the Lullaby check post drawbridge escape. * Changes Ganon's Trials Count setting to a checkbox The checkbox is whether or not to skip all of them. Leaving the box unchecked will mean doing all of them. Eventually this will be switched back to a slider once we implement the logic for which trials start out completed. * Sets all Ganon's Trials to incomplete in new saves. Fixes https://github.com/briaguya-ai/rando-issue-tracker/issues/131 * fix castle guards when oot throw cutscene has already played in rando * Properly removes the beams when trials are cleared. * Removes Question Mark from Skip Ganon's Trials UI. * Adds a todo comment about when to change back to slider. * make deku seeds check for bullet bag * Various tweaks TWEAK: Altar Text TWEAK: Hint names TWEAK: Replace more problematic œ to oe * upgrade ocarina on both child and adult equips * FIX: Jabu Item * update equipped hookshot/longshot when obtained as other age * add hint * don't give the bgs check without the claim check * Skips Darunia Cutscene in Fire Temple * Added a TODO note about not skipping the cutscene. There is a setting we will want to have eventually that will require this cutscene to not be skipped since it is used during a glitch. * remove todo * restore fast ocarina option in imgui that was lost in merge * Fixes grey screen issue + tooltip for 2 handed shield * update to use dg instead of g for textures in item tracker * TWEAK: Default color for cosmetic RAND button was not the corect one * fix texture crash, remove unused item tracker code * don't open mask shop until we get zelda's letter * Update README.md * Prevents "correct" chime under incorrect conditions. * Fixes typo in conditional and adds "bonk" sound effect. "Bonk" sound is NA_SE_SY_OCARINA_ERROR and it plays when conditions for the Door of Time have not been met after playing Song of Time. This is only possible in rando's "Intended" Door of Time option, in which the Ocarina of Time and all 3 spritual stones are required to open the door, instead of the vanilla requirements of just having the song of time. * remove modify dpad equips toggle, replace with checks for dpad menu * remove extra check * add ability to hold c-up to assign to dpad when dpad menuing is enabled * disable d-pad navigation on item menu when holding c-up to equip * dpad+c-up stuff for equipment menu * ADD: Checbox for songs colors * TWEAK: RandoColors for normal songs * kind of quick and dirty but it works * TWEAK: Clarity of the tooltip Co-authored-by: briaguya <briaguya@alice> Co-authored-by: Christopher Leggett <chris@leggett.dev> Co-authored-by: aMannus <mannusmenting@gmail.com> Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com> Co-authored-by: Dog <5172592+Dog@users.noreply.github.com> Co-authored-by: Vague Rant <vaguerant@users.noreply.github.com> Co-authored-by: Baoulettes <perlouzerie@hotmail.fr> Co-authored-by: Ada <60364512+GreatArgorath@users.noreply.github.com>
2022-07-11 20:11:07 -04:00
if (this->work[CS_TIMER_2] == 80) {
msgId2 = 0x604B;
sp35 = 50;
}
if (this->work[CS_TIMER_2] == 140) {
kotakeAnim = koumeAnim = 2;
}
if (this->work[CS_TIMER_2] == 170) {
kotakeAnim = 3;
sKotakePtr->work[YAW_TGT] = -0x4000;
sKotakePtr->rotateSpeed = 0.0f;
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_SENSE);
msgId2 = 0x604C;
}
if (this->work[CS_TIMER_2] == 210) {
D_8094C874 = 30;
}
if (this->work[CS_TIMER_2] == 270) {
koumeAnim = 3;
sKoumePtr->work[YAW_TGT] = 0x4000;
sKoumePtr->rotateSpeed = 0.0f;
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_SENSE);
}
if (this->work[CS_TIMER_2] == 290) {
msgId2 = 0x604D;
sp35 = 35;
}
if (this->work[CS_TIMER_2] == 350) {
koumeAnim = kotakeAnim = 2;
sKoumePtr->work[YAW_TGT] = sKotakePtr->work[YAW_TGT] = 0;
sKoumePtr->rotateSpeed = sKotakePtr->rotateSpeed = 0.0f;
}
if (this->work[CS_TIMER_2] == 380) {
koumeAnim = kotakeAnim = 3;
}
if (this->work[CS_TIMER_2] == 400) {
koumeAnim = kotakeAnim = 2;
}
if (this->work[CS_TIMER_2] == 430) {
koumeAnim = 4;
D_8094C874 = 435;
D_8094C878 = 1;
}
if (this->work[CS_TIMER_2] > 440 && this->work[CS_TIMER_2] < 860) {
func_80078884(NA_SE_EN_TWINROBA_FIGHT - SFX_FLAG);
}
if (this->work[CS_TIMER_2] == 430) {
msgId2 = 0x604E;
}
if (this->work[CS_TIMER_2] == 480) {
kotakeAnim = 4;
sKotakePtr->work[YAW_TGT] = -0x4000;
}
if (this->work[CS_TIMER_2] == 500) {
koumeAnim = 2;
}
if (this->work[CS_TIMER_2] == 480) {
msgId1 = 0x604F;
}
if (this->work[CS_TIMER_2] == 530) {
koumeAnim = 4;
sKoumePtr->work[YAW_TGT] = 0x4000;
D_8094C87A = 335;
D_8094C87E = 1;
}
if (this->work[CS_TIMER_2] == 530) {
msgId2 = 0x6050;
}
if (this->work[CS_TIMER_2] == 580) {
msgId1 = 0x6051;
}
if (this->work[CS_TIMER_2] == 620) {
msgId2 = 0x6052;
}
if (this->work[CS_TIMER_2] == 660) {
msgId1 = 0x6053;
}
if (this->work[CS_TIMER_2] == 700) {
msgId2 = 0x6054;
}
if (this->work[CS_TIMER_2] == 740) {
msgId1 = 0x6055;
}
if (this->work[CS_TIMER_2] == 780) {
msgId2 = 0x6056;
}
if (this->work[CS_TIMER_2] == 820) {
msgId1 = 0x6057;
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x5000FF);
}
if (this->work[CS_TIMER_2] == 860) {
koumeAnim = kotakeAnim = 3;
}
if (this->work[CS_TIMER_2] == 900) {
Audio_PlayActorSound2(&sKoumePtr->actor, NA_SE_EN_TWINROBA_DIE);
Audio_PlayActorSound2(&sKotakePtr->actor, NA_SE_EN_TWINROBA_DIE);
}
if (this->work[CS_TIMER_2] == 930) {
msgId2 = 0x6058;
}
if (msgId2 != 0) {
Message_StartTextbox(globalCtx, msgId2, NULL);
if (sp35) {
D_8094C876 = 10;
D_8094C874 = sp35;
D_8094C878 = 0;
}
}
if (msgId1 != 0) {
Message_StartTextbox(globalCtx, msgId1, NULL);
}
switch (kotakeAnim) {
case 1:
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_00230C, -5.0f);
break;
case 2:
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_001D10, -5.0f);
break;
case 3:
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_0017E0, -5.0f);
break;
case 4:
Animation_MorphToLoop(&sKotakePtr->skelAnime, &object_tw_Anim_0012A4, -5.0f);
break;
}
switch (koumeAnim) {
case 1:
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_00230C, -5.0f);
break;
case 2:
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_001D10, -5.0f);
break;
case 3:
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_0017E0, -5.0f);
break;
case 4:
Animation_MorphToLoop(&sKoumePtr->skelAnime, &object_tw_Anim_0012A4, -5.0f);
break;
}
if (this->work[CS_TIMER_2] >= 120 && this->work[CS_TIMER_2] < 500) {
Math_ApproachF(&this->workf[UNK_F18], 255.0f, 0.1f, 5.0f);
}
testing out item replacement (#416) * skip learning song of storms * don't set flag when getting goron tunic as child * Initiates prelude check when master sword unloads. Not quite how N64 rando does it but so far it's the only way I've found to make it trigger without also triggering the time travel again. * Stops Shadow Temple lore prompts from appearing in rando. * Skips cutscene of royal tomb explosion in rando. Explosion sound doesn't play correctly and I think the debris appears in the wrong place, but the functionality is here. * Improves visual of exploding gravestone. * Adds some comments explaining the rando differences * Skip ruto text box in jabu blue warp For rando * skip intro cutscene in dodongo's cavern * load spoiler files on boot, fix spoilerfile existing check when making new saves * name entry dropped spoiler logic * make sure to actually init the cvar * no chime on load * uncomment * Skip ganondrof cutscene Skip to scream part of the death animation, skipping the text boxes etc. For rando * Update z_boss_ganondrof.c * skip owl flight cutscenes in rando * Fixes skipped text so it only applies to shadow temple. Earlier fix inadvertently applied to some other text as well, changed logic so that only specified sceneNums and textIds can have this enabled, and text skipped by sceneNum can have the skip overriden by textId if needed. Currently there are no overrides so the textId section of the logic is commented out to avoid compilation errors. * Adds a default to the switch case statements that leaves the randoSkipText variable unchanged, just in case. * TEST: Text for item * Adding ganon flavor text * ADD: AMMO Count * format ganon text/hint text * Autoskip the tower cutscene if settings call for tower collapse. * ganon hint text logic * Improved prelude after time travel fix * swapped the sizes between ganon hint text and ganon text, as they were set to the wrong things. * this is all i did * not the cleanest code ever but it's working * ADD: GS Count * ADD: Wallter (crash for now) * TWEAK: Wallet check * FIX: Use DrawItem instread of DrawUpgrade... b-baka! * Fixes some vanilla bugs introduced by rando code. * Added cutscene skip for zelda escaping Using the debug cutscene skipping function. Also added a conditional so the bridge doesn't spawn closed when cutscene is ready to trigger * ADD: X Spacing + Placeholders for song * ADD: default case for items * TWEAK: Spacing * FIX: Light Arrow * ADD: Ammo Option * use groups instead * ADD: More spacing logic * songs and names * TWEAK: Color on wallet * colors * Added flags cutscene before nabooru fight * ADD: ChromaKey text * First attempt skip cs after nabooru defeat * Better implementation for specific rando cutscene skips * use pulseaudio defaults * spaces/tabs * move color push/pop to stop crash * make the colors work again * the real bottle fix * pulseaudio values tuned for n64 audio at 44.1khz * update tlength * remove one hardcoded samplerate * Cleaned up and fixed zelda escape skip The if statement is a freaking monster, but unless we want to skip more cutscenes in the same way later, this is the most compact way of doing it that I know of. * Revert one line to match original nothing functional * another hint line that breaks autonewline logic * don't autospawn epona if we don't have the song/ocarina * Trying to use iron knuckle death effects not working yet * Streamlined OoT cutscene skip for future additions Also cleaned up if statement in general * Made if statement more readable Also added clarity for what cutscene was skipped * Fixed typo in comment * Janky nabooru defeat cs skip * altar text formatting (gonna need help shortening some of the french ones) * more altar text formatting * english altar text formatting complete * make gtg blocking guard check for card not bridge * FIX: Typo! * FIX: Uppercases * FIX: Typo * TWEAK: Alter + some names * TWEAK: More caps! * ADD: Missing string TWEAK more uppercases and namefixe s * Hide nabooru death by covering her in flames * bandaid fix for death crash issue * Twinrova defeat cs skip Skips the animation and manually calls the function to show the "beam" around the sisters * fix crash * fix caps to match * fix great fairy reward mashing/shielding issue * TWEAK : Typo clé to Clé * TWEAK: Some Altar hints TWEAK: Some capitals * TWEAK: Unmatching text + some cap again * TWEAK: More tweaks * fix build * remove extra json.hpp, add hint * Update randomizer_item_tracker.cpp * TWEAK: Double Defense with RedParticles instead of white * make sure we don't optimize out the check to ensure a spoilerfile exists * vanilla ganon boss key hint formatting * TWEAK: FR- better way of the hero text * fix * and again * Initializes dungeonsDone items in gSaveContext to 0. * Replaces sizeof calculation with a NUM_DUNGEONS constant. * Fixes Saria's Gift on the LW Bridge from getting killed when holding shield. * More airtight fix for Saria's Gift on the Bridge. * Lifts one of the conditions in the if statement a little higher to prevent unnecessary lookups of getItemId. * Invalidate text box icon before drawing * Fixes the case where Saria's gift is an Ice Trap. We still get the Ice Trap once, but never again. This does mean you can now hold R while walking in to avoid the ice trap, but everything else seems to work fine. * Initial commit Might need changing when we change the settings in the future * Fixes Door of Time opening cutscene after warping with prelude. * Initial waterfall skip Very rudimentary way of doing things but it seems to work so :shrug: * inital rework * fixed default rotation for 2D sprites * fix tab/space issues * 3d drops rando merge fix again * Allows Impa to appear in the Lullaby check post drawbridge escape. * Changes Ganon's Trials Count setting to a checkbox The checkbox is whether or not to skip all of them. Leaving the box unchecked will mean doing all of them. Eventually this will be switched back to a slider once we implement the logic for which trials start out completed. * Sets all Ganon's Trials to incomplete in new saves. Fixes https://github.com/briaguya-ai/rando-issue-tracker/issues/131 * fix castle guards when oot throw cutscene has already played in rando * Properly removes the beams when trials are cleared. * Removes Question Mark from Skip Ganon's Trials UI. * Adds a todo comment about when to change back to slider. * make deku seeds check for bullet bag * Various tweaks TWEAK: Altar Text TWEAK: Hint names TWEAK: Replace more problematic œ to oe * upgrade ocarina on both child and adult equips * FIX: Jabu Item * update equipped hookshot/longshot when obtained as other age * add hint * don't give the bgs check without the claim check * Skips Darunia Cutscene in Fire Temple * Added a TODO note about not skipping the cutscene. There is a setting we will want to have eventually that will require this cutscene to not be skipped since it is used during a glitch. * remove todo * restore fast ocarina option in imgui that was lost in merge * Fixes grey screen issue + tooltip for 2 handed shield * update to use dg instead of g for textures in item tracker * TWEAK: Default color for cosmetic RAND button was not the corect one * fix texture crash, remove unused item tracker code * don't open mask shop until we get zelda's letter * Update README.md * Prevents "correct" chime under incorrect conditions. * Fixes typo in conditional and adds "bonk" sound effect. "Bonk" sound is NA_SE_SY_OCARINA_ERROR and it plays when conditions for the Door of Time have not been met after playing Song of Time. This is only possible in rando's "Intended" Door of Time option, in which the Ocarina of Time and all 3 spritual stones are required to open the door, instead of the vanilla requirements of just having the song of time. * remove modify dpad equips toggle, replace with checks for dpad menu * remove extra check * add ability to hold c-up to assign to dpad when dpad menuing is enabled * disable d-pad navigation on item menu when holding c-up to equip * dpad+c-up stuff for equipment menu * ADD: Checbox for songs colors * TWEAK: RandoColors for normal songs * kind of quick and dirty but it works * TWEAK: Clarity of the tooltip Co-authored-by: briaguya <briaguya@alice> Co-authored-by: Christopher Leggett <chris@leggett.dev> Co-authored-by: aMannus <mannusmenting@gmail.com> Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com> Co-authored-by: Dog <5172592+Dog@users.noreply.github.com> Co-authored-by: Vague Rant <vaguerant@users.noreply.github.com> Co-authored-by: Baoulettes <perlouzerie@hotmail.fr> Co-authored-by: Ada <60364512+GreatArgorath@users.noreply.github.com>
2022-07-11 20:11:07 -04:00
// Add seperate timings for the "beam" that opens and closes around the sisters
// Needed because we skip ahead in cutscene timer value so it never gets called otherwise
if (gSaveContext.n64ddFlag) {
if (this->work[CS_TIMER_2] < 900) {
Math_ApproachF(&this->workf[UNK_F18], 255.0f, 0.1f, 5.0f);
} else if (this->work[CS_TIMER_2] > 910) {
Math_ApproachF(&this->workf[UNK_F18], 0.0f, 1.0f, 3.0f);
}
}
if (this->work[CS_TIMER_2] >= 150) {
Math_ApproachF(&sKoumePtr->workf[UNK_F17], (Math_SinS(this->work[CS_TIMER_1] * 2000) * 0.05f) + 0.4f, 0.1f,
0.01f);
Math_ApproachF(&sKotakePtr->workf[UNK_F17], (Math_CosS(this->work[CS_TIMER_1] * 1700) * 0.05f) + 0.4f, 0.1f,
0.01f);
if (this->work[CS_TIMER_2] >= 880) {
Math_ApproachF(&sKotakePtr->actor.world.pos.y, 2000.0f, 1.0f, this->actor.speedXZ);
Math_ApproachF(&sKoumePtr->actor.world.pos.y, 2000.0f, 1.0f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 10.0f, 1.0f, 0.25f);
if (this->work[CS_TIMER_2] >= 930) {
Math_ApproachF(&this->workf[UNK_F19], 5.0f, 1.0f, 0.05f);
Math_ApproachF(&this->workf[UNK_F18], 0.0f, 1.0f, 3.0f);
}
Audio_PlayActorSound2(&this->actor, NA_SE_EV_GOTO_HEAVEN - SFX_FLAG);
} else {
f32 yTarget = Math_CosS(this->work[CS_TIMER_2] * 1700) * 4.0f;
Math_ApproachF(&sKotakePtr->actor.world.pos.y, 20.0f + (263.0f + yTarget), 0.1f, this->actor.speedXZ);
yTarget = Math_SinS(this->work[CS_TIMER_2] * 1500) * 4.0f;
Math_ApproachF(&sKoumePtr->actor.world.pos.y, 20.0f + (263.0f + yTarget), 0.1f, this->actor.speedXZ);
Math_ApproachF(&this->actor.speedXZ, 1.0f, 1.0f, 0.05f);
}
}
}
void BossTw_TwinrovaDeathCS(BossTw* this, GlobalContext* globalCtx) {
s16 i;
Vec3f spD0;
Player* player = GET_PLAYER(globalCtx);
Camera* mainCam = Gameplay_GetCamera(globalCtx, MAIN_CAM);
SkelAnime_Update(&this->skelAnime);
this->work[UNK_S8] += 20;
if (this->work[UNK_S8] > 255) {
this->work[UNK_S8] = 255;
}
Math_ApproachF(&this->workf[UNK_F12], 0.0f, 1.0f, 0.05f);
this->unk_5F8 = 1;
switch (this->csState1) {
case 0:
if (this->work[CS_TIMER_1] == 15) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_0216DC, -3.0f);
}
if (this->work[CS_TIMER_1] >= 15) {
Math_ApproachF(&this->actor.world.pos.y, 400.0f, 0.05f, 10.0f);
}
if (this->work[CS_TIMER_1] >= 55) {
if (this->work[CS_TIMER_1] == 55) {
globalCtx->envCtx.unk_D8 = 0;
}
sEnvType = -1;
globalCtx->envCtx.unk_BE = 5;
globalCtx->envCtx.unk_BD = 0;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1.0f, 1.0f, 0.015f);
Math_ApproachF(&this->actor.scale.x, 0.00024999998f, 0.1f, 0.00005f);
this->actor.shape.rot.y += (s16)this->actor.speedXZ;
this->workf[UNK_F13] += this->actor.speedXZ;
if (this->workf[UNK_F13] > 65536.0f) {
this->workf[UNK_F13] -= 65536.0f;
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_ROLL);
}
Math_ApproachF(&this->actor.speedXZ, 12288.0f, 1.0f, 256.0f);
if (this->work[CS_TIMER_1] == 135) {
Vec3f spBC;
Vec3f spB0;
Vec3f spA4 = { 0.0f, 0.0f, 0.0f };
func_80078884(NA_SE_EN_TWINROBA_TRANSFORM);
for (i = 0; i < 100; i++) {
spB0.x = Rand_CenteredFloat(5.0f);
spB0.y = Rand_CenteredFloat(5.0f);
spB0.z = Rand_CenteredFloat(5.0f);
spBC = this->actor.world.pos;
spBC.x += spB0.x;
spBC.y += spB0.y;
spBC.z += spB0.z;
BossTw_AddFlameEffect(globalCtx, &spBC, &spB0, &spA4, Rand_ZeroFloat(2.0f) + 5,
Rand_ZeroFloat(1.99f));
}
this->csState1 = 1;
this->visible = false;
this->actor.scale.x = 0.0f;
Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0,
0, TW_DEATHBALL_KOUME);
Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW,
this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, 0, 0,
0, TW_DEATHBALL_KOTAKE);
this->actor.flags &= ~ACTOR_FLAG_0;
}
}
Actor_SetScale(&this->actor, this->actor.scale.x);
break;
case 1:
break;
}
switch (this->csState2) {
case 0:
this->csState2 = 1;
func_80064520(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 8);
this->subCamId = Gameplay_CreateSubCamera(globalCtx);
Gameplay_ChangeCameraStatus(globalCtx, 0, CAM_STAT_WAIT);
Gameplay_ChangeCameraStatus(globalCtx, this->subCamId, CAM_STAT_ACTIVE);
this->subCamEye = mainCam->eye;
this->subCamAt = mainCam->at;
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF);
break;
case 1:
spD0.x = Math_SinS(this->actor.world.rot.y) * 200.0f;
spD0.z = Math_CosS(this->actor.world.rot.y) * 200.0f;
Math_ApproachF(&this->subCamEye.x, spD0.x + this->actor.world.pos.x, 0.1f, 50.0f);
Math_ApproachF(&this->subCamEye.y, 300.0f, 0.1f, 50.0f);
Math_ApproachF(&this->subCamEye.z, spD0.z + this->actor.world.pos.z, 0.1f, 50.0f);
Math_ApproachF(&this->subCamAt.x, this->actor.world.pos.x, 0.1f, 50.0f);
Math_ApproachF(&this->subCamAt.y, this->actor.world.pos.y, 0.1f, 50.0f);
Math_ApproachF(&this->subCamAt.z, this->actor.world.pos.z, 0.1f, 50.0f);
if (this->work[CS_TIMER_1] == 170) {
this->csState2 = 2;
this->work[CS_TIMER_2] = 0;
this->subCamEye.z = 170.0f;
this->subCamDist = 170.0f;
this->subCamEye.x = 0.0f;
this->subCamAt.x = 0.0f;
this->subCamAt.z = 0.0f;
this->subCamEye.y = 260.0f;
player->actor.shape.rot.y = -0x8000;
player->actor.world.pos.x = -40.0f;
player->actor.world.pos.y = 240.0f;
player->actor.world.pos.z = 90.0f;
sKoumePtr->actor.world.pos.x = -37.0f;
sKotakePtr->actor.world.pos.x = 37.0f;
sKotakePtr->actor.world.pos.y = 263.0f;
sKoumePtr->actor.world.pos.y = sKotakePtr->actor.world.pos.y;
this->subCamAt.y = sKoumePtr->actor.world.pos.y + 17.0f;
sKotakePtr->actor.world.pos.z = 0.0f;
sKoumePtr->actor.world.pos.z = sKotakePtr->actor.world.pos.z;
sKoumePtr->work[YAW_TGT] = sKotakePtr->work[YAW_TGT] = sKoumePtr->actor.shape.rot.x =
sKotakePtr->actor.shape.rot.x = sKoumePtr->actor.shape.rot.y = sKotakePtr->actor.shape.rot.y = 0;
func_8002DF54(globalCtx, &sKoumePtr->actor, 1);
sKoumePtr->actor.flags |= ACTOR_FLAG_0;
}
break;
case 2:
if (this->work[CS_TIMER_2] == 100) {
Vec3f pos;
Vec3f velocity;
Vec3f accel = { 0.0f, 0.0f, 0.0f };
s32 zero = 0;
for (i = 0; i < 50; i++) {
velocity.x = Rand_CenteredFloat(3.0f);
velocity.y = Rand_CenteredFloat(3.0f);
velocity.z = Rand_CenteredFloat(3.0f);
pos = sKoumePtr->actor.world.pos;
pos.x += velocity.x * 2.0f;
pos.y += velocity.y * 2.0f;
pos.z += velocity.z * 2.0f;
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &accel, Rand_ZeroFloat(2.0f) + 5, 1);
// fake code needed to match, tricks the compiler into allocating more stack
if (zero) {
accel.x *= 2.0;
}
velocity.x = Rand_CenteredFloat(3.0f);
velocity.y = Rand_CenteredFloat(3.0f);
velocity.z = Rand_CenteredFloat(3.0f);
pos = sKotakePtr->actor.world.pos;
pos.x += velocity.x * 2.0f;
pos.y += velocity.y * 2.0f;
pos.z += velocity.z * 2.0f;
BossTw_AddFlameEffect(globalCtx, &pos, &velocity, &accel, Rand_ZeroFloat(2.0f) + 5, 0);
}
Actor_SetScale(&sKoumePtr->actor, 0.0f);
Actor_SetScale(&sKotakePtr->actor, 0.0f);
sKoumePtr->visible = 1;
sKotakePtr->visible = 1;
func_80078884(NA_SE_EN_TWINROBA_TRANSFORM);
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_KOTAKE_KOUME);
this->csState2 = 3;
this->work[CS_TIMER_2] = 0;
this->subCamYaw = this->subCamYawStep = this->actor.speedXZ = this->subCamDistStep = 0.0f;
}
break;
case 3:
BossTw_DeathCSMsgSfx(this, globalCtx);
if (this->work[CS_TIMER_2] < 150) {
globalCtx->envCtx.unk_BE = 1;
globalCtx->envCtx.unk_BD = 0;
Math_ApproachZeroF(&globalCtx->envCtx.unk_D8, 1.0f, 0.1f);
} else {
globalCtx->envCtx.unk_BE = 1;
globalCtx->envCtx.unk_BD = 6;
Math_ApproachF(&globalCtx->envCtx.unk_D8, (Math_SinS(this->work[CS_TIMER_2] * 4096) / 4.0f) + 0.75f,
1.0f, 0.1f);
}
Math_ApproachF(&this->subCamAt.y, sKoumePtr->actor.world.pos.y + 17.0f, 0.05f, 10.0f);
if (this->work[CS_TIMER_2] >= 50) {
Math_ApproachF(&this->subCamDist, 110.0f, 0.05f, this->subCamDistStep);
Math_ApproachF(&this->subCamDistStep, 1.0f, 1.0f, 0.025f);
this->subCamEye.x = this->subCamDist * sinf(this->subCamYaw);
this->subCamEye.z = this->subCamDist * cosf(this->subCamYaw);
if (this->work[CS_TIMER_2] >= 151) {
this->subCamYaw += this->subCamYawStep;
if (this->work[CS_TIMER_2] >= 800) {
Math_ApproachF(&this->subCamYawStep, 0.0f, 1.0f, 0.0001f);
} else {
Math_ApproachF(&this->subCamYawStep, 0.015f, 1.0f, 0.0001f);
}
}
}
Math_ApproachF(&sKoumePtr->actor.scale.x, 0.009999999f, 0.1f, 0.001f);
Actor_SetScale(&sKoumePtr->actor, sKoumePtr->actor.scale.x);
Actor_SetScale(&sKotakePtr->actor, sKoumePtr->actor.scale.x);
if (this->work[CS_TIMER_2] >= 1020) {
mainCam = Gameplay_GetCamera(globalCtx, MAIN_CAM);
mainCam->eye = this->subCamEye;
mainCam->eyeNext = this->subCamEye;
mainCam->at = this->subCamAt;
func_800C08AC(globalCtx, this->subCamId, 0);
this->csState2 = 4;
this->subCamId = 0;
func_80064534(globalCtx, &globalCtx->csCtx);
func_8002DF54(globalCtx, &this->actor, 7);
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS_CLEAR);
Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_DOOR_WARP1, 600.0f, 230.0f,
0.0f, 0, 0, 0, WARP_DUNGEON_ADULT);
Actor_Spawn(&globalCtx->actorCtx, globalCtx, ACTOR_ITEM_B_HEART, -600.0f, 230.f, 0.0f, 0, 0, 0, 0);
this->actor.world.pos.y = -2000.0f;
this->workf[UNK_F18] = 0.0f;
sKoumePtr->visible = sKotakePtr->visible = false;
if (&this->subCamEye) {} // fixes regalloc, may be fake
Flags_SetClear(globalCtx, globalCtx->roomCtx.curRoom.num);
}
break;
case 4:
sEnvType = 0;
break;
}
if (this->subCamId) {
Gameplay_CameraSetAtEye(globalCtx, this->subCamId, &this->subCamAt, &this->subCamEye);
}
}
static s16 D_8094A900[] = {
0, 1, 2, 2, 1,
};
static s16 D_8094A90C[] = {
0, 1, 2, 2, 2, 2, 2, 2, 1,
};
void BossTw_Update(Actor* thisx, GlobalContext* globalCtx) {
BossTw* this = (BossTw*)thisx;
Player* player = GET_PLAYER(globalCtx);
s16 i;
s32 pad;
this->collider.base.colType = COLTYPE_HIT3;
Math_ApproachF(&this->fogR, globalCtx->lightCtx.fogColor[0], 1.0f, 10.0f);
Math_ApproachF(&this->fogG, globalCtx->lightCtx.fogColor[1], 1.0f, 10.0f);
Math_ApproachF(&this->fogB, globalCtx->lightCtx.fogColor[2], 1.0f, 10.0f);
Math_ApproachF(&this->fogNear, globalCtx->lightCtx.fogNear, 1.0f, 10.0f);
Math_ApproachF(&this->fogFar, 1000.0f, 1.0f, 10.0f);
this->work[CS_TIMER_1]++;
this->work[CS_TIMER_2]++;
this->work[TAIL_IDX]++;
if (this->work[TAIL_IDX] >= ARRAY_COUNT(this->blastTailPos)) {
this->work[TAIL_IDX] = 0;
}
this->blastTailPos[this->work[TAIL_IDX]] = this->actor.world.pos;
for (i = 0; i < 5; i++) {
if (this->timers[i] != 0) {
this->timers[i]--;
}
}
if (this->work[INVINC_TIMER] != 0) {
this->work[INVINC_TIMER]--;
}
if (this->work[FOG_TIMER] != 0) {
this->work[FOG_TIMER]--;
}
if (this->actionFunc == BossTw_FlyTo || this->actionFunc == BossTw_Spin ||
this->actionFunc == BossTw_TurnToPlayer) {
if ((s16)(player->actor.shape.rot.y - this->actor.yawTowardsPlayer + 0x8000) < 0x1000 &&
(s16)(player->actor.shape.rot.y - this->actor.yawTowardsPlayer + 0x8000) > -0x1000 && player->unk_A73) {
BossTw_SetupSpin(this, globalCtx);
}
}
this->actionFunc(this, globalCtx);
if (this->actionFunc != BossTw_Wait) {
this->collider.dim.radius = 45;
if (this->actionFunc == BossTw_Spin) {
this->collider.dim.radius *= 2;
}
this->collider.dim.height = 120;
this->collider.dim.yShift = -30;
if (this->work[INVINC_TIMER] == 0) {
if (this->collider.base.acFlags & AC_HIT) {
this->collider.base.acFlags &= ~AC_HIT;
}
Collider_UpdateCylinder(&this->actor, &this->collider);
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
CollisionCheck_SetAT(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
}
if (this->actor.params == 0) {
this->workf[OUTR_CRWN_TX_X2] += 1.0f;
this->workf[OUTR_CRWN_TX_Y2] -= 7.0f;
this->workf[INNR_CRWN_TX_Y1] += 1.0f;
} else {
this->workf[OUTR_CRWN_TX_X2] += 0.0f;
this->workf[INNR_CRWN_TX_X2] += 0.0f;
this->workf[OUTR_CRWN_TX_Y2] += -15.0f;
this->workf[INNR_CRWN_TX_Y2] += -10.0f;
}
if (((this->work[CS_TIMER_2] % 32) == 0) && (Rand_ZeroOne() < 0.3f)) {
this->work[BLINK_IDX] = 4;
}
this->eyeTexIdx = D_8094A900[this->work[BLINK_IDX]];
if (this->work[BLINK_IDX] != 0) {
this->work[BLINK_IDX]--;
}
if (this->actionFunc != BossTw_MergeCS && this->unk_5F8 != 0) {
Vec3f pos;
Vec3f velocity = { 0.0f, 0.0f, 0.0f };
Vec3f accel = { 0.0f, 0.0f, 0.0f };
if (this->scepterAlpha > 0.0f) {
for (i = 0; i <= 0; i++) {
pos = this->scepterFlamePos[0];
pos.x += Rand_CenteredFloat(70.0f);
pos.y += Rand_CenteredFloat(70.0f);
pos.z += Rand_CenteredFloat(70.0f);
accel.y = 0.4f;
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 8,
this->actor.params, 37);
}
}
for (i = 0; i <= 0; i++) {
pos = this->crownPos;
pos.x += Rand_CenteredFloat(70.0f);
pos.y += Rand_CenteredFloat(70.0f);
pos.z += Rand_CenteredFloat(70.0f);
accel.y = 0.4f;
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 8,
this->actor.params, 37);
}
}
}
}
void BossTw_TwinrovaUpdate(Actor* thisx, GlobalContext* globalCtx2) {
s16 i;
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
Player* player = GET_PLAYER(globalCtx);
this->actor.flags &= ~ACTOR_FLAG_10;
this->unk_5F8 = 0;
this->collider.base.colType = COLTYPE_HIT3;
Math_ApproachF(&this->fogR, globalCtx->lightCtx.fogColor[0], 1.0f, 10.0f);
Math_ApproachF(&this->fogG, globalCtx->lightCtx.fogColor[1], 1.0f, 10.0f);
Math_ApproachF(&this->fogB, globalCtx->lightCtx.fogColor[2], 1.0f, 10.0f);
Math_ApproachF(&this->fogNear, globalCtx->lightCtx.fogNear, 1.0f, 10.0f);
Math_ApproachF(&this->fogFar, 1000.0f, 1.0f, 10.0f);
this->work[CS_TIMER_1]++;
this->work[CS_TIMER_2]++;
for (i = 0; i < 5; i++) {
if (this->timers[i] != 0) {
this->timers[i]--;
}
}
if (this->work[INVINC_TIMER] != 0) {
this->work[INVINC_TIMER]--;
}
if (this->work[FOG_TIMER] != 0) {
this->work[FOG_TIMER]--;
}
this->actionFunc(this, globalCtx);
if (this->actionFunc != BossTw_TwinrovaShootBlast && this->actionFunc != BossTw_TwinrovaChargeBlast &&
this->visible && this->unk_5F8 == 0 &&
(s16)(player->actor.shape.rot.y - this->actor.yawTowardsPlayer + 0x8000) < 0x1000 &&
(s16)(player->actor.shape.rot.y - this->actor.yawTowardsPlayer + 0x8000) > -0x1000 && player->unk_A73 != 0) {
BossTw_TwinrovaSetupSpin(this, globalCtx);
}
this->eyeTexIdx = D_8094A900[this->work[BLINK_IDX]];
if (this->work[BLINK_IDX] != 0) {
this->work[BLINK_IDX]--;
}
if ((this->work[CS_TIMER_2] % 32) == 0) {
if (this->actionFunc != BossTw_TwinrovaMergeCS) {
if (Rand_ZeroOne() < 0.3f) {
this->work[BLINK_IDX] = 4;
}
}
}
if (this->actionFunc == BossTw_TwinrovaMergeCS) {
this->leftEyeTexIdx = D_8094A90C[this->work[TW_BLINK_IDX]];
if (this->work[TW_BLINK_IDX] != 0) {
this->work[TW_BLINK_IDX]--;
}
} else {
if (this->actionFunc == BossTw_TwinrovaStun) {
this->eyeTexIdx = 1;
}
if (this->actionFunc == BossTw_TwinrovaDeathCS) {
this->eyeTexIdx = 2;
}
this->leftEyeTexIdx = this->eyeTexIdx;
}
if (this->visible && this->unk_5F8 == 0) {
Vec3f pos;
Vec3f velocity = { 0.0f, 0.0f, 0.0f };
Vec3f accel;
if (this->work[UNK_S8] != 0) {
this->work[UNK_S8] -= 20;
if (this->work[UNK_S8] < 0) {
this->work[UNK_S8] = 0;
}
}
Math_ApproachF(&this->workf[UNK_F12], 1.0f, 1.0f, 0.05f);
accel.y = 0.4f;
for (i = 0; i < 2; i++) {
pos = this->leftScepterPos;
pos.x += Rand_CenteredFloat(30.0f);
pos.y += Rand_CenteredFloat(30.0f);
pos.z += Rand_CenteredFloat(30.0f);
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 7, 0, 75);
}
for (i = 0; i < 2; i++) {
pos = this->rightScepterPos;
pos.x += Rand_CenteredFloat(30.0f);
pos.y += Rand_CenteredFloat(30.0f);
pos.z += Rand_CenteredFloat(30.0f);
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 7, 1, 75);
}
}
this->collider.dim.radius = 35;
if (this->actionFunc == BossTw_TwinrovaSpin) {
this->collider.dim.radius *= 2;
}
this->collider.dim.height = 150;
this->collider.dim.yShift = -60;
Collider_UpdateCylinder(&this->actor, &this->collider);
if (this->work[INVINC_TIMER] == 0) {
if (this->actionFunc != BossTw_TwinrovaStun) {
if (this->twinrovaStun != 0) {
this->twinrovaStun = 0;
this->work[FOG_TIMER] = 10;
BossTw_TwinrovaDamage(this, globalCtx, 0);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DAMAGE);
} else if (this->collider.base.acFlags & AC_HIT) {
ColliderInfo* info = this->collider.info.acHitInfo;
this->collider.base.acFlags &= ~AC_HIT;
if (info->toucher.dmgFlags & (DMG_SLINGSHOT | DMG_ARROW)) {}
}
} else if (this->collider.base.acFlags & AC_HIT) {
u8 damage;
u8 swordDamage;
ColliderInfo* info = this->collider.info.acHitInfo;
this->collider.base.acFlags &= ~AC_HIT;
swordDamage = false;
damage = CollisionCheck_GetSwordDamage(info->toucher.dmgFlags);
if (damage == 0) {
damage = 2;
} else {
swordDamage = true;
}
if (!(info->toucher.dmgFlags & DMG_HOOKSHOT)) {
if (((s8)this->actor.colChkInfo.health < 3) && !swordDamage) {
damage = 0;
}
BossTw_TwinrovaDamage(this, globalCtx, damage);
}
}
}
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
osSyncPrintf("OooooooooooooooooooooooooooooooooCC\n");
CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->collider.base);
globalCtx->envCtx.unk_DC = 2;
switch (sEnvType) {
case 0:
Math_ApproachZeroF(&globalCtx->envCtx.unk_D8, 1.0f, 0.02f);
break;
case 1:
globalCtx->envCtx.unk_BD = 3;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 0.5f, 1.0f, 0.05f);
break;
case 2:
globalCtx->envCtx.unk_BD = 2;
Math_ApproachF(&globalCtx->envCtx.unk_D8, (Math_SinS(this->work[CS_TIMER_1] * 0x3000) * 0.03f) + 0.5f, 1.0f,
0.05f);
break;
case 3:
globalCtx->envCtx.unk_BD = 3;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1.0f, 1.0f, 0.1f);
break;
case 4:
globalCtx->envCtx.unk_BD = 2;
Math_ApproachF(&globalCtx->envCtx.unk_D8, (Math_SinS(this->work[CS_TIMER_1] * 0x3E00) * 0.05f) + 0.95f,
1.0f, 0.1f);
break;
case 5:
globalCtx->envCtx.unk_BD = 0;
Math_ApproachF(&globalCtx->envCtx.unk_D8, 1.0f, 1.0f, 0.05f);
break;
case -1:
break;
}
BossTw_UpdateEffects(globalCtx);
if (sFreezeState == 1) {
sFreezeState = 2;
BossTw_AddPlayerFreezeEffect(globalCtx, NULL);
func_80078914(&player->actor.projectedPos, NA_SE_VO_LI_FREEZE);
func_80078914(&player->actor.projectedPos, NA_SE_PL_FREEZE);
if (sShieldFireCharge != 0) {
sShieldFireCharge = 4;
}
}
if (player->isBurning && sShieldIceCharge != 0) {
sShieldIceCharge = 4;
}
}
s32 BossTw_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) {
BossTw* this = (BossTw*)thisx;
if (limbIndex == 21) {
if (this->unk_5F8 == 0) {
if (this->actor.params == 0) {
*dList = object_tw_DL_012CE0;
} else {
*dList = object_tw_DL_0134B8;
}
}
}
if (limbIndex == 14) {
if (this->actionFunc == BossTw_DeathCS) {
*dList = NULL;
} else if (this->scepterAlpha == 0.0f) {
if (this->actor.params == 0) {
*dList = object_tw_DL_012B38;
} else {
*dList = object_tw_DL_013310;
}
}
}
return false;
}
void BossTw_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
static Vec3f D_8094A944 = { 0.0f, 0.0f, 0.0f };
static Vec3f D_8094A950 = { 0.0f, 2000.0f, -2000.0f };
static Vec3f D_8094A95C[] = {
{ 0.0f, 0.0f, -10000.0f }, { 0.0f, 0.0f, -8000.0f }, { 0.0f, 0.0f, -9000.0f },
{ 0.0f, 0.0f, -11000.0f }, { 0.0f, 0.0f, -12000.0f },
};
BossTw* this = (BossTw*)thisx;
OPEN_DISPS(globalCtx->state.gfxCtx);
switch (limbIndex) {
case 21:
Matrix_MultVec3f(&D_8094A944, &this->actor.focus.pos);
Matrix_MultVec3f(&D_8094A950, &this->crownPos);
if (this->unk_5F8 != 0) {
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
if (this->actor.params == 0) {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_013AE8));
} else {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_013D68));
}
}
break;
case 14:
Matrix_MultVec3f(&D_8094A95C[0], &this->scepterFlamePos[0]);
Matrix_MultVec3f(&D_8094A95C[1], &this->scepterFlamePos[1]);
Matrix_MultVec3f(&D_8094A95C[2], &this->scepterFlamePos[2]);
Matrix_MultVec3f(&D_8094A95C[3], &this->scepterFlamePos[3]);
Matrix_MultVec3f(&D_8094A95C[4], &this->scepterFlamePos[4]);
if (this->scepterAlpha > 0.0f) {
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
if (this->actor.params == 0) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 225, 255, (s16)this->scepterAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_013E98));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, (s16)this->scepterAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_013F98));
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 100, 20, 0, (s16)this->scepterAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_014070));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 70, 0, (s16)this->scepterAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_014158));
}
}
break;
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_80941BC0(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
func_80093D84(globalCtx->state.gfxCtx);
Matrix_Translate(this->groundBlastPos2.x, this->groundBlastPos2.y, this->groundBlastPos2.z, MTXMODE_NEW);
Matrix_Scale(this->workf[UNK_F12], this->workf[UNK_F12], this->workf[UNK_F12], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, (s16)this->workf[UNK_F11]);
gDPSetEnvColor(POLY_XLU_DISP++, 0, 40, 30, 80);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01BC00));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 215, 215, 215, (s16)this->workf[UNK_F11] * this->workf[UNK_F14]);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 255, 255, 128);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, (u32)this->workf[UNK_F16] & 0x3F,
(this->work[CS_TIMER_2] * 4) & 0x3F, 0x10, 0x10));
Matrix_Push();
Matrix_RotateY(this->workf[UNK_F15], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01C1C0));
Matrix_Pop();
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPSegment(POLY_XLU_DISP++, 0xD,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, this->work[CS_TIMER_2] & 0x7F,
(this->work[CS_TIMER_2] * 8) & 0xFF, 0x20, 0x40, 1,
(-this->work[CS_TIMER_2] * 2) & 0x3F, 0, 0x10, 0x10));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, (s16)this->workf[UNK_F9]);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 255, 255, 128);
gDPSetRenderMode(POLY_XLU_DISP++,
Z_CMP | IM_RD | CVG_DST_SAVE | ZMODE_DEC | FORCE_BL |
GBL_c1(G_BL_CLR_FOG, G_BL_A_SHADE, G_BL_CLR_IN, G_BL_1MA),
G_RM_ZB_OVL_SURF2);
gSPSetGeometryMode(POLY_XLU_DISP++, G_CULL_BACK | G_FOG);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A790));
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_80942180(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
func_80093D84(globalCtx->state.gfxCtx);
Matrix_Translate(this->groundBlastPos2.x, this->groundBlastPos2.y, this->groundBlastPos2.z, MTXMODE_NEW);
Matrix_Scale(this->workf[KM_GD_CRTR_SCL], this->workf[KM_GD_CRTR_SCL], this->workf[KM_GD_CRTR_SCL], MTXMODE_APPLY);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (-this->work[CS_TIMER_1]) & 0x7F, 0, 0x20, 0x20, 1,
(this->work[CS_TIMER_1] * 2) & 0x7F, 0, 0x20, 0x20));
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 100, 40, 00, (s16)this->workf[KM_GRND_CRTR_A]);
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 245, 255, 128);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_019D40));
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, this->work[CS_TIMER_1] & 0x7F,
(-this->work[CS_TIMER_1] * 6) & 0xFF, 0x20, 0x40, 1,
(this->work[CS_TIMER_1] * 2) & 0x7F, (-this->work[CS_TIMER_1] * 6) & 0xFF, 0x20, 0x40));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 80, 0, 0, (s16)this->workf[KM_GD_SMOKE_A]);
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 0, 0, 0, 100);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_018FC0));
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (-this->work[CS_TIMER_1] * 3) & 0x7F, 0, 0x20, 0x20, 1, 0,
(-this->work[CS_TIMER_1] * 10) & 0xFF, 0x20, 0x40));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 100, 50, 0, (s16)(this->workf[KM_GD_FLM_A] * 0.7f));
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 200, 235, 240, 128);
Matrix_Scale(this->workf[KM_GD_FLM_SCL], this->workf[KM_GD_FLM_SCL], this->workf[KM_GD_FLM_SCL], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_019938));
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_809426F0(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
s16 i;
OPEN_DISPS(globalCtx->state.gfxCtx);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, (u8)(-this->work[CS_TIMER_2] * 15), 0x20, 0x40, 1, 0, 0,
0x40, 0x40));
Matrix_Push();
Matrix_Translate(0.0f, 0.0f, 5000.0f, MTXMODE_APPLY);
Matrix_Scale(this->spawnPortalScale / 2000.0f, this->spawnPortalScale / 2000.0f, this->spawnPortalScale / 2000.0f,
MTXMODE_APPLY);
Matrix_RotateZ(this->portalRotation, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
if (this->actor.params == 0) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 135, 175, 165, (s16)this->spawnPortalAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01CEE0));
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 0, (s16)this->spawnPortalAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01DBE8));
}
Matrix_Pop();
if (this->actor.params == 0) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, (s16)this->flameAlpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A998));
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 20, 0, (s16)this->flameAlpha);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 215, 255, 128);
}
for (i = 0; i < 8; i++) {
FrameInterpolation_RecordOpenChild("Twinrova 809426F0", i);
Matrix_Push();
Matrix_Translate(0.0f, 0.0f, 5000.0f, MTXMODE_APPLY);
Matrix_RotateZ(((i * M_PI) * 2.0f * 0.125f) + this->flameRotation, MTXMODE_APPLY);
Matrix_Translate(0.0f, this->spawnPortalScale * 1.5f, 0.0f, MTXMODE_APPLY);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, ((this->work[CS_TIMER_2] * 3) + (i * 10)) & 0x7F,
(u8)((-this->work[CS_TIMER_2] * 15) + (i * 50)), 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
Matrix_Scale(0.4f, 0.4f, 0.4f, MTXMODE_APPLY);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A430));
Matrix_Pop();
FrameInterpolation_RecordCloseChild();
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_80942C70(Actor* thisx, GlobalContext* globalCtx) {
BossTw* this = (BossTw*)thisx;
s16 alpha;
OPEN_DISPS(globalCtx->state.gfxCtx);
if (this->beamDist != 0.0f) {
Matrix_Push();
gSPSegment(POLY_XLU_DISP++, 0xC,
Gfx_TexScroll(globalCtx->state.gfxCtx, 0, (u8)(this->work[CS_TIMER_1] * -0xF), 0x20, 0x40));
alpha = this->beamScale * 100.0f * 255.0f;
if (this->actor.params == 1) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 60, alpha);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 0, 128);
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, alpha);
gDPSetEnvColor(POLY_XLU_DISP++, 100, 100, 255, 128);
}
Matrix_Translate(this->beamOrigin.x, this->beamOrigin.y, this->beamOrigin.z, MTXMODE_NEW);
Matrix_RotateY(this->beamYaw, MTXMODE_APPLY);
Matrix_RotateX(this->beamPitch, MTXMODE_APPLY);
Matrix_RotateZ(this->beamRoll, MTXMODE_APPLY);
Matrix_Scale(this->beamScale, this->beamScale, (this->beamDist * 0.01f * 98.0f) / 20000.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01DDF0));
if (this->beamReflectionDist > 10.0f) {
Matrix_Translate(this->beamReflectionOrigin.x, this->beamReflectionOrigin.y, this->beamReflectionOrigin.z,
MTXMODE_NEW);
Matrix_RotateY(this->beamReflectionYaw, MTXMODE_APPLY);
Matrix_RotateX(this->beamReflectionPitch, MTXMODE_APPLY);
Matrix_RotateZ(this->beamRoll, MTXMODE_APPLY);
Matrix_Scale(this->beamScale, this->beamScale, (this->beamReflectionDist * 0.01f * 100.0f) / 20000.0f,
MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01DDF0));
}
Matrix_Pop();
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_80943028(Actor* thisx, GlobalContext* globalCtx) {
BossTw* this = (BossTw*)thisx;
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
Matrix_Translate(this->actor.world.pos.x, this->actor.world.pos.y + 57.0f, this->actor.world.pos.z, MTXMODE_NEW);
Matrix_Scale(this->workf[UNK_F17], this->workf[UNK_F17], this->workf[UNK_F17], MTXMODE_APPLY);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, 255);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01F608));
func_80094044(globalCtx->state.gfxCtx);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, 200);
Matrix_Translate(this->actor.world.pos.x, 240.0f, this->actor.world.pos.z, MTXMODE_NEW);
Matrix_Scale((this->actor.scale.x * 4000.0f) / 100.0f, 1.0f, (this->actor.scale.x * 4000.0f) / 100.0f,
MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(gCircleShadowDL));
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
static void* sEyeTextures[] = {
object_tw_Tex_00A438,
object_tw_Tex_00B238,
object_tw_Tex_00B638,
};
void BossTw_Draw(Actor* thisx, GlobalContext* globalCtx2) {
static Vec3f D_8094A9A4 = { 0.0f, 200.0f, 2000.0f };
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
Player* player = GET_PLAYER(globalCtx);
OPEN_DISPS(globalCtx->state.gfxCtx);
if (this->visible) {
gSPSegment(POLY_OPA_DISP++, 10, SEGMENTED_TO_VIRTUAL(sEyeTextures[this->eyeTexIdx]));
gSPSegment(POLY_XLU_DISP++, 10, SEGMENTED_TO_VIRTUAL(sEyeTextures[this->eyeTexIdx]));
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (s16)this->workf[OUTR_CRWN_TX_X1] & 0x7F,
(s16)this->workf[OUTR_CRWN_TX_Y1] & 0x7F, 0x20, 0x20, 1,
(s16)this->workf[OUTR_CRWN_TX_X2] & 0x7F, (s16)this->workf[OUTR_CRWN_TX_Y2] & 0xFF,
0x20, 0x40));
if (this->actor.params == TW_KOTAKE) {
gSPSegment(POLY_XLU_DISP++, 9,
Gfx_TexScroll(globalCtx->state.gfxCtx, (s16)this->workf[INNR_CRWN_TX_X1] & 0x7F,
(s16)this->workf[INNR_CRWN_TX_Y1] & 0xFF, 0x20, 0x40));
} else {
gSPSegment(POLY_XLU_DISP++, 9,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (s16)this->workf[INNR_CRWN_TX_X1] & 0x7F,
(s16)this->workf[INNR_CRWN_TX_Y1] & 0x7F, 0x20, 0x20, 1,
(s16)this->workf[INNR_CRWN_TX_X2] & 0x7F,
(s16)this->workf[INNR_CRWN_TX_Y2] & 0xFF, 0x20, 0x40));
}
func_80093D18(globalCtx->state.gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
if (this->work[FOG_TIMER] & 2) {
POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, 255, 50, 0, 0, 900, 1099);
} else {
POLY_OPA_DISP = Gfx_SetFog(POLY_OPA_DISP, (u32)this->fogR, (u32)this->fogG, (u32)this->fogB, 0,
this->fogNear, this->fogFar);
}
Matrix_Push();
SkelAnime_DrawFlexOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable,
this->skelAnime.dListCount, BossTw_OverrideLimbDraw, BossTw_PostLimbDraw, this);
Matrix_Pop();
POLY_OPA_DISP = Gameplay_SetFog(globalCtx, POLY_OPA_DISP);
}
if (this->actor.params == TW_KOTAKE) {
if (this->workf[UNK_F9] > 0.0f) {
if (this->workf[UNK_F11] > 0.0f) {
Vec3f diff;
diff.x = this->groundBlastPos2.x - player->actor.world.pos.x;
diff.y = this->groundBlastPos2.y - player->actor.world.pos.y;
diff.z = this->groundBlastPos2.z - player->actor.world.pos.z;
if ((fabsf(diff.y) < 10.0f) && (player->actor.bgCheckFlags & 1) &&
(sqrtf(SQ(diff.x) + SQ(diff.z)) < (this->workf[UNK_F12] * 4600.0f)) && (sFreezeState == 0) &&
(this->workf[UNK_F11] > 200.0f)) {
sFreezeState = 1;
sTwinrovaPtr->timers[2] = 100;
}
}
func_80941BC0(this, globalCtx);
}
} else {
func_80942180(this, globalCtx);
}
if (this->visible) {
if (this->actionFunc == BossTw_DeathCS) {
func_80943028(&this->actor, globalCtx);
} else {
func_809426F0(this, globalCtx);
Matrix_MultVec3f(&D_8094A9A4, &this->beamOrigin);
func_80942C70(&this->actor, globalCtx);
}
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void* D_8094A9B0[] = {
object_tw_Tex_02A9B0,
object_tw_Tex_02A070,
object_tw_Tex_02A470,
};
s32 BossTw_TwinrovaOverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot,
void* thisx) {
BossTw* this = (BossTw*)thisx;
OPEN_DISPS(globalCtx->state.gfxCtx);
switch (limbIndex) {
case 21:
gSPSegment(POLY_OPA_DISP++, 0xC,
Gfx_TexScroll(globalCtx->state.gfxCtx, 0, (s16)(f32)this->work[CS_TIMER_1], 8, 8));
gSPSegment(POLY_OPA_DISP++, 8, SEGMENTED_TO_VIRTUAL(D_8094A9B0[this->eyeTexIdx]));
gSPSegment(POLY_OPA_DISP++, 9, SEGMENTED_TO_VIRTUAL(D_8094A9B0[this->leftEyeTexIdx]));
gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 255, this->work[UNK_S8]);
break;
case 17:
case 41:
*dList = NULL;
gSPSegment(POLY_XLU_DISP++, 0xA,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, 0, 0x20, 0x20, 1, 0,
-this->work[CS_TIMER_1] * 0xF, 0x20, 0x40));
break;
case 18:
case 42:
*dList = NULL;
gSPSegment(POLY_XLU_DISP++, 0xB,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, 0, 0x20, 0x20, 1, 0,
-this->work[CS_TIMER_1] * 0xA, 0x20, 0x40));
break;
case 16:
case 32:
*dList = NULL;
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, 0, 0x20, 0x20, 1, this->work[CS_TIMER_1],
-this->work[CS_TIMER_1] * 7, 0x20, 0x40));
break;
case 15:
case 31:
*dList = NULL;
gSPSegment(POLY_XLU_DISP++, 9,
Gfx_TexScroll(globalCtx->state.gfxCtx, 0, this->work[CS_TIMER_1], 0x20, 0x40));
break;
case 19:
if (this->unk_5F8 != 0) {
*dList = object_tw_DL_02D940;
}
break;
case 20:
if (this->unk_5F8 != 0) {
*dList = object_tw_DL_02D890;
}
break;
}
if (this->unk_5F8 != 0 && ((limbIndex == 34) || (limbIndex == 40))) {
*dList = NULL;
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
return false;
}
void BossTw_TwinrovaPostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
static Vec3f D_8094A9BC = { 0.0f, 0.0f, 0.0f };
static Vec3f D_8094A9C8 = { 0.0f, 2000.0f, -2000.0f };
static Vec3f D_8094A9D4 = { 13000.0f, 0.0f, 0.0f };
static Vec3f D_8094A9E0 = { 13000.0f, 0.0f, 0.0f };
BossTw* this = (BossTw*)thisx;
OPEN_DISPS(globalCtx->state.gfxCtx);
switch (limbIndex) {
case 34:
Matrix_MultVec3f(&D_8094A9D4, &this->leftScepterPos);
break;
case 40:
Matrix_MultVec3f(&D_8094A9E0, &this->rightScepterPos);
break;
case 21:
Matrix_MultVec3f(&D_8094A9BC, &this->actor.focus.pos);
Matrix_MultVec3f(&D_8094A9C8, &this->crownPos);
break;
case 15:
case 16:
case 17:
case 18:
case 31:
case 32:
case 41:
case 42:
Matrix_Push();
Matrix_Scale(this->workf[UNK_F12], this->workf[UNK_F12], this->workf[UNK_F12], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
Matrix_Pop();
gSPDisplayList(POLY_XLU_DISP++, *dList);
break;
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_ShieldChargeDraw(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
Player* player = GET_PLAYER(globalCtx);
s16 temp_t0;
s16 temp_a0;
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
temp_t0 = sShieldFireCharge | sShieldIceCharge;
if (temp_t0 == 1) {
func_80078884(NA_SE_IT_SHIELD_CHARGE_LV1 & ~SFX_FLAG);
} else if (temp_t0 == 2) {
func_80078884(NA_SE_IT_SHIELD_CHARGE_LV2 & ~SFX_FLAG);
} else if (temp_t0 == 3) {
func_80078884(NA_SE_IT_SHIELD_CHARGE_LV3 & ~SFX_FLAG);
}
if (temp_t0 != 0 && temp_t0 < 4) {
Math_ApproachF(&D_8094C854, 255.0f, 1.0f, 20.0f);
if (temp_t0 == 3) {
temp_t0 *= 3;
}
} else if (temp_t0 == 0) {
D_8094C854 = 0.0f;
} else {
Math_ApproachF(&D_8094C854, 0.0f, 1.0f, 10.0f);
if (D_8094C854 == 0.0f) {
sShieldIceCharge = 0;
sShieldFireCharge = 0;
}
temp_t0 = 1;
}
if (Player_HasMirrorShieldEquipped(globalCtx)) {
if (temp_t0 != 0) {
Matrix_Mult(&player->shieldMf, MTXMODE_NEW);
Matrix_RotateX(M_PI / 2.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
temp_a0 = (Math_SinS(this->work[CS_TIMER_1] * 2730 * temp_t0) * D_8094C854 * 0.5f) + (D_8094C854 * 0.5f);
if (sShieldFireCharge != 0) {
gDPSetEnvColor(POLY_XLU_DISP++, 255, 245, 255, temp_a0);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01E0E0));
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (this->work[CS_TIMER_1] * 2) * temp_t0, 0, 0x20,
0x20, 1, (-this->work[CS_TIMER_1] * 2) * temp_t0, 0, 0x20, 0x20));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 100, 20, 0, (s16)D_8094C854);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01E020));
} else {
gDPSetEnvColor(POLY_XLU_DISP++, 225, 255, 255, temp_a0);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01E3A0));
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, (-this->work[CS_TIMER_1] * 5) * temp_t0,
0x20, 0x40, 1, (this->work[CS_TIMER_1] * 4) * temp_t0, 0, 0x20, 0x20));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 175, 205, 195, (s16)D_8094C854);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01E2C0));
}
}
}
if (D_8094C86F != 0) {
f32 step = D_8094C872 > 0 ? 100.0f : 60.0f;
D_8094C86F--;
Math_ApproachF(&D_8094C858, 255.0f, 1.0f, step);
} else {
f32 step = D_8094C872 > 0 ? 40.0f : 20.0f;
Math_ApproachF(&D_8094C858, 0.0f, 1.0f, step);
}
if (Player_HasMirrorShieldEquipped(globalCtx) && D_8094C858 > 0.0f) {
f32 scale = D_8094C872 > 0 ? 1.3f : 1.0f;
Matrix_Mult(&player->shieldMf, MTXMODE_NEW);
Matrix_RotateX(M_PI / 2.0f, MTXMODE_APPLY);
Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
if (sShieldFireCharge != 0) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 220, 20, (s16)D_8094C858);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 20, 110);
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, (s16)D_8094C858);
gDPSetEnvColor(POLY_XLU_DISP++, 185, 225, 205, 150);
}
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, this->work[CS_TIMER_1] * D_8094C872, 0x20, 0x40, 1,
0, this->work[CS_TIMER_1] * D_8094C872, 0x20, 0x20));
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01E9F0));
}
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_SpawnPortalDraw(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
OPEN_DISPS(globalCtx->state.gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
gSPSegment(
POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, -this->work[CS_TIMER_1] * 15, 0x20, 0x40, 1, 0, 0, 0x40, 0x40));
Matrix_Push();
Matrix_Translate(0.0f, 232.0f, -600.0f, MTXMODE_NEW);
Matrix_Scale(this->spawnPortalScale, this->spawnPortalScale, this->spawnPortalScale, MTXMODE_APPLY);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, (s16)this->spawnPortalAlpha);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01EC68));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 135, 175, 165, (s16)this->spawnPortalAlpha);
Matrix_Translate(0.0f, 2.0f, 0.0f, MTXMODE_APPLY);
Matrix_RotateX(M_PI / 2.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01CEE0));
Matrix_Translate(0.0f, 232.0f, 600.0f, MTXMODE_NEW);
Matrix_Scale(this->spawnPortalScale, this->spawnPortalScale, this->spawnPortalScale, MTXMODE_APPLY);
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 0, 0, (s16)this->spawnPortalAlpha);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01EC68));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 0, (s16)this->spawnPortalAlpha);
Matrix_Translate(0.0f, 2.0f, 0.0f, MTXMODE_APPLY);
Matrix_RotateX(M_PI / 2.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01DBE8));
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void func_80944C50(BossTw* this, GlobalContext* globalCtx) {
s32 pad;
f32 scale;
OPEN_DISPS(globalCtx->state.gfxCtx);
Matrix_Push();
Matrix_Translate(0.0f, 750.0f, 0.0f, MTXMODE_NEW);
Matrix_Scale(0.35f, 0.35f, 0.35f, MTXMODE_APPLY);
Matrix_Push();
Matrix_Scale(this->workf[UNK_F19], this->workf[UNK_F19], this->workf[UNK_F19], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01F390));
Matrix_Pop();
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, -sKoumePtr->work[CS_TIMER_1] * 2, 0, 0x20, 0x20, 1,
-sKoumePtr->work[CS_TIMER_1] * 2, 0, 0x20, 0x40));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, (s16)this->workf[UNK_F18] / 2);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01F238));
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, -sKoumePtr->work[CS_TIMER_1] * 5,
-sKoumePtr->work[CS_TIMER_1] * 2, 0x20, 0x40, 1, 0, -sKoumePtr->work[CS_TIMER_1] * 2,
0x10, 0x10));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 255, 255, (s16)(this->workf[UNK_F18] * 0.3f));
scale = this->workf[UNK_F18] / 150.0f;
scale = CLAMP_MAX(scale, 1.0f);
Matrix_Scale(scale, 1.0f, scale, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_LOAD | G_MTX_MODELVIEW | G_MTX_NOPUSH);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01EEB0));
Matrix_Pop();
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_TwinrovaDraw(Actor* thisx, GlobalContext* globalCtx2) {
static Vec3f D_8094A9EC = { 0.0f, 200.0f, 2000.0f };
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
OPEN_DISPS(globalCtx->state.gfxCtx);
if (this->visible) {
func_80093D18(globalCtx->state.gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
POLY_OPA_DISP = (this->work[FOG_TIMER] & 2) ? Gfx_SetFog2(POLY_OPA_DISP, 255, 50, 0, 0, 900, 1099)
: Gfx_SetFog2(POLY_OPA_DISP, (u32)this->fogR, (u32)this->fogG,
(u32)this->fogB, 0, this->fogNear, this->fogFar);
Matrix_Push();
SkelAnime_DrawFlexOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable,
this->skelAnime.dListCount, BossTw_TwinrovaOverrideLimbDraw, BossTw_TwinrovaPostLimbDraw,
thisx);
Matrix_Pop();
Matrix_MultVec3f(&D_8094A9EC, &this->beamOrigin);
POLY_OPA_DISP = Gfx_SetFog2(POLY_OPA_DISP, globalCtx->lightCtx.fogColor[0], globalCtx->lightCtx.fogColor[1],
globalCtx->lightCtx.fogColor[2], 0, globalCtx->lightCtx.fogNear, 1000);
}
BossTw_DrawEffects(globalCtx);
BossTw_ShieldChargeDraw(this, globalCtx);
if (this->spawnPortalAlpha > 0.0f) {
BossTw_SpawnPortalDraw(this, globalCtx);
}
if (this->workf[UNK_F18] > 0.0f) {
func_80944C50(this, globalCtx);
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_BlastFire(BossTw* this, GlobalContext* globalCtx) {
s16 i;
f32 xDiff;
f32 yDiff;
f32 zDiff;
f32 distXZ;
Player* player = GET_PLAYER(globalCtx);
Player* player2 = player;
switch (this->actor.params) {
case TW_FIRE_BLAST:
switch (this->csState1) {
case 0:
Actor_SetScale(&this->actor, 0.03f);
this->csState1 = 1;
xDiff = player->actor.world.pos.x - this->actor.world.pos.x;
yDiff = (player->actor.world.pos.y + 30.0f) - this->actor.world.pos.y;
zDiff = player->actor.world.pos.z - this->actor.world.pos.z;
// yaw
this->actor.world.rot.y = Math_FAtan2F(xDiff, zDiff) * (32768 / M_PI);
// pitch
distXZ = sqrtf(SQ(xDiff) + SQ(zDiff));
this->actor.world.rot.x = Math_FAtan2F(yDiff, distXZ) * (32768 / M_PI);
this->actor.speedXZ = 20.0f;
for (i = 0; i < 50; i++) {
this->blastTailPos[i] = this->actor.world.pos;
}
this->workf[TAIL_ALPHA] = 255.0f;
// fallthrough
case 1:
case 10:
this->blastActive = true;
if (this->timers[0] == 0) {
func_8002D908(&this->actor);
func_8002D7EC(&this->actor);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_SHOOT_FIRE & ~SFX_FLAG);
} else {
Vec3f velocity;
Vec3f velDir;
Vec3s blastDir;
s16 alpha;
this->actor.world.pos = player2->bodyPartsPos[15];
this->actor.world.pos.y = -2000.0f;
Matrix_MtxFToYXZRotS(&player2->shieldMf, &blastDir, 0);
blastDir.x = -blastDir.x;
blastDir.y = blastDir.y + 0x8000;
Math_ApproachS(&this->magicDir.x, blastDir.x, 0xA, 0x800);
Math_ApproachS(&this->magicDir.y, blastDir.y, 0xA, 0x800);
if (this->timers[0] == 50) {
D_8094C86F = 10;
D_8094C872 = 7;
globalCtx->envCtx.unk_D8 = 1.0f;
}
if (this->timers[0] <= 50) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_SHOOT_FIRE & ~SFX_FLAG);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_REFL_FIRE & ~SFX_FLAG);
Matrix_RotateY((this->magicDir.y / 32678.0f) * M_PI, MTXMODE_NEW);
Matrix_RotateX((this->magicDir.x / 32678.0f) * M_PI, MTXMODE_APPLY);
velDir.x = 0.0f;
velDir.y = 0.0f;
velDir.z = 50.0f;
Matrix_MultVec3f(&velDir, &velocity);
alpha = this->timers[0] * 10;
alpha = CLAMP_MAX(alpha, 255);
BossTw_AddShieldBlastEffect(globalCtx, &player2->bodyPartsPos[15], &velocity, &sZeroVector,
10.0f, 80.0f, alpha, 1);
}
if (this->timers[0] == 1) {
sEnvType = 0;
sShieldFireCharge++;
Actor_Kill(&this->actor);
}
return;
}
this->groundBlastPos.y = BossTw_GetFloorY(&this->actor.world.pos);
if (this->groundBlastPos.y >= 0.0f) {
if (this->groundBlastPos.y != 35.0f) {
this->groundBlastPos.x = this->actor.world.pos.x;
this->groundBlastPos.z = this->actor.world.pos.z;
BossTw_SpawnGroundBlast(this, globalCtx, 1);
} else {
Vec3f velocity;
Vec3f accel;
for (i = 0; i < 50; i++) {
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
accel.x = 0.0f;
accel.y = 0.0f;
accel.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &this->actor.world.pos, &velocity, &accel,
Rand_ZeroFloat(10.0f) + 25.0f, this->blastType);
}
globalCtx->envCtx.unk_D8 = 0.5f;
}
this->csState1 = 2;
this->timers[0] = 20;
} else {
Vec3f pos;
Vec3f velocity = { 0.0f, 0.0f, 0.0f };
Vec3f accel = { 0.0f, 0.0f, 0.0f };
for (i = 0; i < 10; i++) {
pos = this->blastTailPos[(s16)Rand_ZeroFloat(29.9f)];
pos.x += Rand_CenteredFloat(40.0f);
pos.y += Rand_CenteredFloat(40.0f);
pos.z += Rand_CenteredFloat(40.0f);
accel.y = 0.4f;
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 8, 1,
75);
}
}
break;
case 2:
Math_ApproachF(&this->workf[TAIL_ALPHA], 0.0f, 1.0f, 15.0f);
if (this->timers[0] == 0) {
Actor_Kill(&this->actor);
}
break;
}
break;
case TW_FIRE_BLAST_GROUND:
if (this->timers[0] != 0) {
if (this->timers[0] == 1) {
sEnvType = 0;
}
if (sGroundBlastType == 2) {
this->timers[0] = 0;
}
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_FIRE_EXP - SFX_FLAG);
xDiff = sKoumePtr->groundBlastPos2.x - player->actor.world.pos.x;
yDiff = sKoumePtr->groundBlastPos2.y - player->actor.world.pos.y;
zDiff = sKoumePtr->groundBlastPos2.z - player->actor.world.pos.z;
if (!player->isBurning && (player->actor.bgCheckFlags & 1) && (fabsf(yDiff) < 10.0f) &&
(sqrtf(SQ(xDiff) + SQ(zDiff)) < (sKoumePtr->workf[UNK_F13] * 4550.0f))) {
s16 j;
for (j = 0; j < 18; j++) {
player->flameTimers[j] = Rand_S16Offset(0, 200);
}
player->isBurning = 1;
if (this->work[BURN_TMR] == 0) {
func_8002F7DC(&player->actor, player->ageProperties->unk_92 + NA_SE_VO_LI_DEMO_DAMAGE);
this->work[BURN_TMR] = 40;
}
sTwinrovaPtr->timers[2] = 100;
}
Math_ApproachF(&sKoumePtr->workf[UNK_F13], 0.04f, 0.1f, 0.002f);
break;
}
{
f32 sp4C = sGroundBlastType == 2 ? 3.0f : 1.0f;
Math_ApproachF(&sKoumePtr->workf[UNK_F9], 0.0f, 1.0f, 10.0f * sp4C);
Math_ApproachF(&sKoumePtr->workf[UNK_F12], 0.0f, 1.0f, 0.03f * sp4C);
Math_ApproachF(&sKoumePtr->workf[TAIL_ALPHA], 0.0f, 1.0f, 3.0f * sp4C);
Math_ApproachF(&sKoumePtr->workf[UNK_F11], 0.0f, 1.0f, 6.0f * sp4C);
}
if (sKoumePtr->workf[TAIL_ALPHA] <= 0.0f) {
Actor_Kill(&this->actor);
}
break;
}
}
void BossTw_BlastIce(BossTw* this, GlobalContext* globalCtx) {
s16 i;
f32 xDiff;
f32 yDiff;
f32 zDiff;
f32 xzDist;
Player* player = GET_PLAYER(globalCtx);
Player* player2 = player;
switch (this->actor.params) {
case TW_ICE_BLAST:
switch (this->csState1) {
case 0:
Actor_SetScale(&this->actor, 0.03f);
this->csState1 = 1;
xDiff = player->actor.world.pos.x - this->actor.world.pos.x;
yDiff = (player->actor.world.pos.y + 30.0f) - this->actor.world.pos.y;
zDiff = player->actor.world.pos.z - this->actor.world.pos.z;
this->actor.world.rot.y = Math_FAtan2F(xDiff, zDiff) * (32768 / M_PI);
xzDist = sqrtf(SQ(xDiff) + SQ(zDiff));
this->actor.world.rot.x = Math_FAtan2F(yDiff, xzDist) * (32768 / M_PI);
this->actor.speedXZ = 20.0f;
for (i = 0; i < 50; i++) {
this->blastTailPos[i] = this->actor.world.pos;
}
this->workf[TAIL_ALPHA] = 255.0f;
// fallthrough
case 1:
case 10:
this->blastActive = true;
if (this->timers[0] == 0) {
func_8002D908(&this->actor);
func_8002D7EC(&this->actor);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_SHOOT_FREEZE - SFX_FLAG);
} else {
Vec3f velocity;
Vec3f spF4;
Vec3s reflDir;
s16 alpha;
this->actor.world.pos = player2->bodyPartsPos[15];
this->actor.world.pos.y = -2000.0f;
Matrix_MtxFToYXZRotS(&player2->shieldMf, &reflDir, 0);
reflDir.x = -reflDir.x;
reflDir.y += 0x8000;
Math_ApproachS(&this->magicDir.x, reflDir.x, 0xA, 0x800);
Math_ApproachS(&this->magicDir.y, reflDir.y, 0xA, 0x800);
if (this->timers[0] == 50) {
D_8094C86F = 10;
D_8094C872 = 7;
globalCtx->envCtx.unk_D8 = 1.0f;
}
if (this->timers[0] <= 50) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_SHOOT_FREEZE - SFX_FLAG);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_REFL_FREEZE - SFX_FLAG);
Matrix_RotateY((this->magicDir.y / 32678.0f) * M_PI, MTXMODE_NEW);
Matrix_RotateX((this->magicDir.x / 32678.0f) * M_PI, MTXMODE_APPLY);
spF4.x = 0.0f;
spF4.y = 0.0f;
spF4.z = 50.0f;
Matrix_MultVec3f(&spF4, &velocity);
alpha = this->timers[0] * 10;
alpha = CLAMP_MAX(alpha, 255);
BossTw_AddShieldBlastEffect(globalCtx, &player2->bodyPartsPos[15], &velocity, &sZeroVector,
10.0f, 80.0f, alpha, 0);
}
if (this->timers[0] == 1) {
sEnvType = 0;
sShieldIceCharge++;
Actor_Kill(&this->actor);
}
break;
}
this->groundBlastPos.y = BossTw_GetFloorY(&this->actor.world.pos);
if (this->groundBlastPos.y >= 0.0f) {
if (this->groundBlastPos.y != 35.0f) {
this->groundBlastPos.x = this->actor.world.pos.x;
this->groundBlastPos.z = this->actor.world.pos.z;
BossTw_SpawnGroundBlast(this, globalCtx, 0);
} else {
for (i = 0; i < 50; i++) {
Vec3f velocity;
Vec3f accel;
velocity.x = Rand_CenteredFloat(20.0f);
velocity.y = Rand_CenteredFloat(20.0f);
velocity.z = Rand_CenteredFloat(20.0f);
accel.x = 0.0f;
accel.y = 0.0f;
accel.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &this->actor.world.pos, &velocity, &accel,
Rand_ZeroFloat(10.0f) + 25.0f, this->blastType);
}
globalCtx->envCtx.unk_D8 = 0.5f;
}
this->csState1 = 2;
this->timers[0] = 20;
} else {
Vec3f pos;
Vec3f velocity = { 0.0f, 0.0f, 0.0f };
Vec3f accel = { 0.0f, 0.0f, 0.0f };
for (i = 0; i < 10; i++) {
pos = this->blastTailPos[(s16)Rand_ZeroFloat(29.9f)];
pos.x += Rand_CenteredFloat(40.0f);
pos.y += Rand_CenteredFloat(40.0f);
pos.z += Rand_CenteredFloat(40.0f);
accel.y = 0.4f;
accel.x = Rand_CenteredFloat(0.5f);
accel.z = Rand_CenteredFloat(0.5f);
BossTw_AddDotEffect(globalCtx, &pos, &velocity, &accel, ((s16)Rand_ZeroFloat(2.0f) + 8), 0,
75);
}
}
break;
case 2:
Math_ApproachF(&this->workf[TAIL_ALPHA], 0.0f, 1.0f, 15.0f);
if (this->timers[0] == 0) {
Actor_Kill(&this->actor);
}
break;
}
break;
case TW_ICE_BLAST_GROUND:
if (this->timers[0] != 0) {
if (this->timers[0] == 1) {
sEnvType = 0;
}
if (sGroundBlastType == 1) {
this->timers[0] = 0;
}
Audio_PlayActorSound2(&this->actor, NA_SE_EV_ICE_FREEZE - SFX_FLAG);
if (this->timers[0] > (sTwinrovaPtr->actionFunc == BossTw_Wait ? 70 : 20)) {
s32 pad;
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = sKotakePtr->groundBlastPos2.x + Rand_CenteredFloat(320.0f);
pos.z = sKotakePtr->groundBlastPos2.z + Rand_CenteredFloat(320.0f);
pos.y = sKotakePtr->groundBlastPos2.y;
velocity.x = 0.0f;
velocity.y = 0.0f;
velocity.z = 0.0f;
accel.x = 0.0f;
accel.y = 0.13f;
accel.z = 0.0f;
BossTw_AddDmgCloud(globalCtx, 3, &pos, &velocity, &accel, Rand_ZeroFloat(5.0f) + 20.0f, 0, 0, 80);
velocity.x = Rand_CenteredFloat(10.0f);
velocity.z = Rand_CenteredFloat(10.0f);
velocity.y = Rand_ZeroFloat(3.0f) + 3.0f;
pos.x = sKotakePtr->groundBlastPos2.x + (velocity.x * 0.5f);
pos.z = sKotakePtr->groundBlastPos2.z + (velocity.z * 0.5f);
BossTw_AddDmgCloud(globalCtx, 3, &pos, &velocity, &accel, Rand_ZeroFloat(5.0f) + 15.0f, 255, 2,
130);
}
Math_ApproachF(&sKotakePtr->workf[UNK_F9], 80.0f, 1.0f, 3.0f);
Math_ApproachF(&sKotakePtr->workf[UNK_F11], 255.0f, 1.0f, 10.0f);
Math_ApproachF(&sKotakePtr->workf[UNK_F12], 0.04f, 0.1f, 0.002f);
Math_ApproachF(&sKotakePtr->workf[UNK_F16], 70.0f, 1.0f, 5.0f);
if ((this->timers[0] == 70) || (this->timers[0] == 30)) {
sKotakePtr->workf[UNK_F16] = 10.0f;
}
if ((this->timers[0] % 4) == 0) {
sKotakePtr->workf[UNK_F15] = (2.0f * (s16)Rand_ZeroFloat(9.9f) * M_PI) / 10.0f;
}
} else {
f32 sp80;
if (sGroundBlastType == 1) {
if (sKotakePtr->workf[UNK_F11] > 1.0f) {
for (i = 0; i < 3; i++) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = Rand_CenteredFloat(280.0f) + sKotakePtr->groundBlastPos2.x;
pos.z = Rand_CenteredFloat(280.0f) + sKotakePtr->groundBlastPos2.z;
pos.y = sKotakePtr->groundBlastPos2.y + 30.0f;
velocity.x = 0.0f;
velocity.y = 0.0f;
velocity.z = 0.0f;
accel.x = 0.0f;
accel.y = 0.13f;
accel.z = 0.0f;
BossTw_AddDmgCloud(globalCtx, 3, &pos, &velocity, &accel, Rand_ZeroFloat(5.0f) + 20, 0, 0,
80);
}
}
sp80 = 3.0f;
} else {
sp80 = 1.0f;
}
Math_ApproachF(&sKotakePtr->workf[UNK_F14], 0.0f, 1.0f, 0.2f * sp80);
Math_ApproachF(&sKotakePtr->workf[UNK_F11], 0.0f, 1.0f, 5.0f * sp80);
Math_ApproachF(&sKotakePtr->workf[UNK_F9], 0.0f, 1.0f, sp80);
if (sKotakePtr->workf[UNK_F9] <= 0.0f) {
Actor_Kill(&this->actor);
}
}
break;
}
}
s32 BossTw_BlastShieldCheck(BossTw* this, GlobalContext* globalCtx) {
Player* player = GET_PLAYER(globalCtx);
s32 ret = false;
ColliderInfo* info;
if (this->csState1 == 1) {
if (this->collider.base.acFlags & AC_HIT) {
this->collider.base.acFlags &= ~AC_HIT;
this->collider.base.atFlags &= ~AT_HIT;
info = this->collider.info.acHitInfo;
if (info->toucher.dmgFlags & DMG_SHIELD) {
this->work[INVINC_TIMER] = 7;
globalCtx->envCtx.unk_D8 = 1.0f;
func_800AA000(0.0f, 100, 5, 4);
if (Player_HasMirrorShieldEquipped(globalCtx)) {
if (this->blastType == 1) {
if (sShieldIceCharge != 0) {
sShieldIceCharge = 0;
BossTw_AddShieldDeflectEffect(globalCtx, 10.0f, 1);
} else {
BossTw_AddShieldHitEffect(globalCtx, 10.0f, 1);
sShieldFireCharge++;
D_8094C86F = (sShieldFireCharge * 2) + 8;
D_8094C872 = -7;
}
} else {
if (sShieldFireCharge != 0) {
sShieldFireCharge = 0;
BossTw_AddShieldDeflectEffect(globalCtx, 10.0f, 0);
} else {
BossTw_AddShieldHitEffect(globalCtx, 10.0f, 0);
sShieldIceCharge++;
D_8094C86F = (sShieldIceCharge * 2) + 8;
D_8094C872 = -7;
}
}
if ((sShieldIceCharge >= 3) || (sShieldFireCharge >= 3)) {
this->timers[0] = 80;
this->csState1 = 10;
Matrix_MtxFToYXZRotS(&player->shieldMf, &this->magicDir, 0);
this->magicDir.y += 0x8000;
this->magicDir.x = -this->magicDir.x;
D_8094C86F = 8;
} else {
this->csState1 = 2;
this->timers[0] = 20;
sEnvType = 0;
}
} else {
BossTw_AddShieldDeflectEffect(globalCtx, 10.0f, this->blastType);
this->csState1 = 2;
this->timers[0] = 20;
sEnvType = 0;
sShieldIceCharge = 0;
sShieldFireCharge = 0;
func_80078884(NA_SE_IT_SHIELD_REFLECT_MG2);
}
ret = true;
}
}
}
return ret;
}
void BossTw_BlastUpdate(Actor* thisx, GlobalContext* globalCtx) {
BossTw* this = (BossTw*)thisx;
ColliderCylinder* collider;
s16 i;
this->work[CS_TIMER_1]++;
this->work[CS_TIMER_2]++;
this->work[TAIL_IDX]++;
if (this->work[TAIL_IDX] > 29) {
this->work[TAIL_IDX] = 0;
}
this->blastTailPos[this->work[TAIL_IDX]] = this->actor.world.pos;
this->actionFunc(this, globalCtx);
for (i = 0; i < 5; i++) {
if (this->timers[i] != 0) {
this->timers[i]--;
}
}
if (this->work[INVINC_TIMER] != 0) {
this->work[INVINC_TIMER]--;
}
if (this->work[BURN_TMR] != 0) {
this->work[BURN_TMR]--;
}
this->actor.focus.pos = this->actor.world.pos;
collider = &this->collider;
Collider_UpdateCylinder(&this->actor, collider);
if (this->blastActive && this->work[INVINC_TIMER] == 0 && !BossTw_BlastShieldCheck(this, globalCtx)) {
CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &collider->base);
CollisionCheck_SetAT(globalCtx, &globalCtx->colChkCtx, &collider->base);
}
this->blastActive = false;
}
void BossTw_BlastDraw(Actor* thisx, GlobalContext* globalCtx2) {
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
f32 scaleFactor;
s16 tailIdx;
s16 i;
static u32 epoch = 0;
epoch++;
OPEN_DISPS(globalCtx->state.gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
switch (this->actor.params) {
case TW_FIRE_BLAST:
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 20, 0, (s8)this->workf[TAIL_ALPHA]);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 215, 255, 128);
for (i = 9; i >= 0; i--) {
FrameInterpolation_RecordOpenChild("Twinrova Fire Blast", epoch + i * 25);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(
globalCtx->state.gfxCtx, 0, ((this->work[CS_TIMER_1] * 3) + (i * 10)) & 0x7F,
((-this->work[CS_TIMER_1] * 15) + (i * 50)) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
tailIdx = ((this->work[TAIL_IDX] - i) + 30) % 30;
Matrix_Translate(this->blastTailPos[tailIdx].x, this->blastTailPos[tailIdx].y,
this->blastTailPos[tailIdx].z, MTXMODE_NEW);
scaleFactor = 1.0f - (i * 0.09f);
Matrix_Scale(this->actor.scale.x * scaleFactor, this->actor.scale.y * scaleFactor,
this->actor.scale.z * scaleFactor, MTXMODE_APPLY);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A430));
FrameInterpolation_RecordCloseChild();
}
break;
case TW_FIRE_BLAST_GROUND:
break;
case TW_ICE_BLAST:
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, (s8)this->workf[TAIL_ALPHA]);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A998));
for (i = 9; i >= 0; i--) {
FrameInterpolation_RecordOpenChild("Twinrova Ice Blast", epoch + i * 25);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(
globalCtx->state.gfxCtx, 0, ((this->work[CS_TIMER_1] * 3) + (i * 0xA)) & 0x7F,
(u8)((-this->work[CS_TIMER_1] * 0xF) + (i * 50)), 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
tailIdx = ((this->work[TAIL_IDX] - i) + 30) % 30;
Matrix_Translate(this->blastTailPos[tailIdx].x, this->blastTailPos[tailIdx].y,
this->blastTailPos[tailIdx].z, MTXMODE_NEW);
scaleFactor = 1.0f - (i * 0.09f);
Matrix_Scale(this->actor.scale.x * scaleFactor, this->actor.scale.y * scaleFactor,
this->actor.scale.z * scaleFactor, MTXMODE_APPLY);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AB00));
FrameInterpolation_RecordCloseChild();
}
break;
case TW_ICE_BLAST_GROUND:
break;
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_DrawDeathBall(Actor* thisx, GlobalContext* globalCtx2) {
GlobalContext* globalCtx = globalCtx2;
BossTw* this = (BossTw*)thisx;
f32 scaleFactor;
s16 tailIdx;
s16 i;
OPEN_DISPS(globalCtx->state.gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
if (this->actor.params == TW_DEATHBALL_KOUME) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 20, 0, (s8)this->workf[TAIL_ALPHA]);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 215, 255, 128);
for (i = 9; i >= 0; i--) {
FrameInterpolation_RecordOpenChild("Twinrova Death Ball 0", i);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (((this->work[CS_TIMER_1] * 3) + (i * 0xA))) & 0x7F,
(u8)((-this->work[CS_TIMER_1] * 0xF) + (i * 50)), 0x20, 0x40, 1, 0, 0, 0x20,
0x20));
tailIdx = ((this->work[TAIL_IDX] - i) + 30) % 30;
Matrix_Translate(this->blastTailPos[tailIdx].x, this->blastTailPos[tailIdx].y,
this->blastTailPos[tailIdx].z, MTXMODE_NEW);
scaleFactor = (1.0f - (i * 0.09f));
Matrix_Scale(this->actor.scale.x * scaleFactor, this->actor.scale.y * scaleFactor,
this->actor.scale.z * scaleFactor, MTXMODE_APPLY);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A430));
FrameInterpolation_RecordCloseChild();
}
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, (s8)this->workf[TAIL_ALPHA]);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A998));
for (i = 9; i >= 0; i--) {
FrameInterpolation_RecordOpenChild("Twinrova Death Ball 1", i);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (((this->work[CS_TIMER_1] * 3) + (i * 0xA))) & 0x7F,
(u8)((-this->work[CS_TIMER_1] * 0xF) + (i * 50)), 0x20, 0x40, 1, 0, 0, 0x20,
0x20));
tailIdx = ((this->work[TAIL_IDX] - i) + 30) % 30;
Matrix_Translate(this->blastTailPos[tailIdx].x, this->blastTailPos[tailIdx].y,
this->blastTailPos[tailIdx].z, MTXMODE_NEW);
scaleFactor = (1.0f - (i * 0.09f));
Matrix_Scale(this->actor.scale.x * scaleFactor, this->actor.scale.y * scaleFactor,
this->actor.scale.z * scaleFactor, MTXMODE_APPLY);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(globalCtx->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AB00));
FrameInterpolation_RecordCloseChild();
}
}
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
void BossTw_UpdateEffects(GlobalContext* globalCtx) {
static Color_RGB8 sDotColors[] = {
{ 255, 128, 0 }, { 255, 0, 0 }, { 255, 255, 0 }, { 255, 0, 0 },
{ 100, 100, 100 }, { 255, 255, 255 }, { 150, 150, 150 }, { 255, 255, 255 },
};
Vec3f sp11C;
BossTwEffect* eff = globalCtx->specialEffects;
Player* player = GET_PLAYER(globalCtx);
u8 sp113 = 0;
s16 i;
s16 j;
s16 colorIdx;
Vec3f off;
Vec3f spF4;
Vec3f spE8;
Vec3f spDC;
Vec3f spD0;
f32 phi_f22;
Vec3f spC0;
Vec3f spB4;
Vec3f spA8;
s16 spA6;
f32 phi_f0;
Actor* unk44;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (eff->type != 0) {
eff->pos.x += eff->curSpeed.x;
eff->pos.y += eff->curSpeed.y;
eff->pos.z += eff->curSpeed.z;
eff->frame++;
eff->curSpeed.x += eff->accel.x;
eff->curSpeed.y += eff->accel.y;
eff->curSpeed.z += eff->accel.z;
if (eff->type == 1) {
colorIdx = eff->frame % 4;
if (eff->work[EFF_ARGS] == 0) {
colorIdx += 4;
}
eff->color.r = sDotColors[colorIdx].r;
eff->color.g = sDotColors[colorIdx].g;
eff->color.b = sDotColors[colorIdx].b;
eff->alpha -= 20;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
} else if ((eff->type == 3) || (eff->type == 2)) {
if (eff->work[EFF_ARGS] == 2) {
eff->alpha -= 20;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
} else if (eff->work[EFF_ARGS] == 0) {
eff->alpha += 10;
if (eff->alpha >= 100) {
eff->work[EFF_ARGS]++;
}
} else {
eff->alpha -= 3;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
} else if (eff->type == TWEFF_FLAME) {
if (eff->work[EFF_UNKS1] != 0) {
eff->alpha = (eff->alpha - (i & 7)) - 0xD;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
} else {
eff->alpha += 300;
if (eff->alpha >= 255) {
eff->alpha = 255;
eff->work[EFF_UNKS1]++;
}
}
} else if (eff->type == TWEFF_SHLD_BLST) {
D_8094C870 = 1;
eff->work[EFF_UNKS1]++;
if (eff->work[EFF_UNKS1] > 30) {
eff->alpha -= 10;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
Math_ApproachF(&eff->workf[EFF_SCALE], eff->workf[EFF_DIST], 0.1f, 0.003f);
off.x = sTwinrovaPtr->actor.world.pos.x - eff->pos.x;
off.y = (sTwinrovaPtr->actor.world.pos.y - eff->pos.y) * 0.5f;
off.z = sTwinrovaPtr->actor.world.pos.z - eff->pos.z;
if (sTwinrovaPtr->actionFunc != BossTw_TwinrovaStun) {
if ((SQ(off.x) + SQ(off.y) + SQ(off.z)) < SQ(60.0f)) {
for (j = 0; j < 50; j++) {
spF4.x = sTwinrovaPtr->actor.world.pos.x + Rand_CenteredFloat(35.0f);
spF4.y = sTwinrovaPtr->actor.world.pos.y + Rand_CenteredFloat(70.0f);
spF4.z = sTwinrovaPtr->actor.world.pos.z + Rand_CenteredFloat(35.0f);
spE8.x = Rand_CenteredFloat(20.0f);
spE8.y = Rand_CenteredFloat(20.0f);
spE8.z = Rand_CenteredFloat(20.0f);
spDC.x = 0.0f;
spDC.y = 0.0f;
spDC.z = 0.0f;
BossTw_AddFlameEffect(globalCtx, &spF4, &spE8, &spDC, Rand_ZeroFloat(10.0f) + 25.0f,
eff->work[EFF_ARGS]);
}
sTwinrovaPtr->twinrovaStun = 1;
globalCtx->envCtx.unk_D8 = 1.0f;
eff->type = TWEFF_NONE;
}
}
} else if (eff->type == TWEFF_MERGEFLAME) {
sp11C.x = 0.0f;
sp11C.y = eff->pos.y;
sp11C.z = eff->workf[EFF_DIST];
Matrix_RotateY(sTwinrovaPtr->workf[UNK_F9] + eff->workf[EFF_ROLL], MTXMODE_NEW);
Matrix_MultVec3f(&sp11C, &eff->pos);
if (eff->work[EFF_UNKS1] != 0) {
eff->alpha -= 60;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
} else {
eff->alpha += 60;
if (eff->alpha >= 255) {
eff->alpha = 255;
eff->work[EFF_UNKS1]++;
}
}
} else if (eff->type == TWEFF_SHLD_DEFL) {
eff->work[EFF_UNKS1]++;
sp11C.x = 0.0f;
sp11C.y = 0.0f;
sp11C.z = -eff->workf[EFF_DIST];
Matrix_RotateY((sShieldHitYaw / 32768.0f) * M_PI, MTXMODE_NEW);
Matrix_RotateX(-0.2f, MTXMODE_APPLY);
Matrix_RotateZ(eff->workf[EFF_ROLL], MTXMODE_APPLY);
Matrix_RotateY(eff->workf[EFF_YAW], MTXMODE_APPLY);
Matrix_MultVec3f(&sp11C, &eff->pos);
eff->pos.x += sShieldHitPos.x;
eff->pos.y += sShieldHitPos.y;
eff->pos.z += sShieldHitPos.z;
if (eff->work[EFF_UNKS1] < 10) {
Math_ApproachF(&eff->workf[EFF_DIST], 50.0f, 0.5f, 100.0f);
} else {
Math_ApproachF(&eff->workf[EFF_YAW], 0.0f, 0.5f, 10.0f);
Math_ApproachF(&eff->workf[EFF_DIST], 1000.0f, 1.0f, 10.0f);
if (eff->work[EFF_UNKS1] >= 0x10) {
if ((eff->work[EFF_UNKS1] == 16) && (sp113 == 0)) {
sp113 = 1;
spD0 = eff->pos;
if (eff->pos.y > 40.0f) {
spD0.y = 220.0f;
} else {
spD0.y = -50.0f;
}
sTwinrovaPtr->groundBlastPos.y = phi_f0 = BossTw_GetFloorY(&spD0);
if (phi_f0 >= 0.0f) {
if (sTwinrovaPtr->groundBlastPos.y != 35.0f) {
sTwinrovaPtr->groundBlastPos.x = eff->pos.x;
sTwinrovaPtr->groundBlastPos.z = eff->pos.z;
BossTw_SpawnGroundBlast(sTwinrovaPtr, globalCtx, eff->work[EFF_ARGS]);
}
}
}
eff->alpha -= 300;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
}
BossTw_AddFlameEffect(globalCtx, &eff->pos, &sZeroVector, &sZeroVector, 10, eff->work[EFF_ARGS]);
} else if (eff->type == TWEFF_SHLD_HIT) {
eff->work[EFF_UNKS1]++;
sp11C.x = 0.0f;
sp11C.y = 0.0f;
sp11C.z = -eff->workf[EFF_DIST];
Matrix_RotateY((sShieldHitYaw / 32768.0f) * M_PI, MTXMODE_NEW);
Matrix_RotateX(-0.2f, MTXMODE_APPLY);
Matrix_RotateZ(eff->workf[EFF_ROLL], MTXMODE_APPLY);
Matrix_RotateY(eff->workf[EFF_YAW], MTXMODE_APPLY);
Matrix_MultVec3f(&sp11C, &eff->pos);
eff->pos.x += sShieldHitPos.x;
eff->pos.y += sShieldHitPos.y;
eff->pos.z += sShieldHitPos.z;
if (eff->work[EFF_UNKS1] < 5) {
Math_ApproachF(&eff->workf[EFF_DIST], 40.0f, 0.5f, 100.0f);
} else {
Math_ApproachF(&eff->workf[EFF_DIST], 0.0f, 0.2f, 5.0f);
if (eff->work[EFF_UNKS1] >= 11) {
eff->alpha -= 30;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
}
BossTw_AddFlameEffect(globalCtx, &eff->pos, &sZeroVector, &sZeroVector, 10, eff->work[EFF_ARGS]);
} else if (eff->type == 4) {
if (eff->work[EFF_UNKS1] == 0) {
Math_ApproachF(&eff->workf[EFF_SCALE], eff->workf[EFF_DIST], 0.05f, 1.0f);
if (eff->frame >= 16) {
eff->alpha -= 10;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
} else {
Math_ApproachF(&eff->workf[EFF_SCALE], eff->workf[EFF_DIST], 0.1f, 2.0f);
eff->alpha -= 15;
if (eff->alpha <= 0) {
eff->alpha = 0;
eff->type = TWEFF_NONE;
}
}
} else if (eff->type == TWEFF_PLYR_FRZ) {
if (eff->work[EFF_ARGS] < eff->frame) {
phi_f0 = 1.0f;
if (eff->target != NULL || sGroundBlastType == 1) {
phi_f0 *= 3.0f;
}
Math_ApproachF(&eff->workf[EFF_SCALE], 0.0f, 1.0f, 0.0005f * phi_f0);
if (eff->workf[EFF_SCALE] == 0.0f) {
eff->type = TWEFF_NONE;
if (eff->target == NULL) {
player->stateFlags2 &= ~0x8000;
sFreezeState = 0;
}
}
} else {
if (sGroundBlastType == 1) {
eff->frame = 100;
}
Math_ApproachF(&eff->workf[EFF_DIST], 0.8f, 0.2f, 0.04f);
if (eff->target == NULL) {
Math_ApproachF(&eff->workf[EFF_SCALE], 0.012f, 1.0f, 0.002f);
eff->workf[EFF_ROLL] += eff->workf[EFF_DIST];
if (eff->workf[EFF_ROLL] >= 0.8f) {
eff->workf[EFF_ROLL] -= 0.8f;
player->stateFlags2 |= 0x8000;
} else {
player->stateFlags2 &= ~0x8000;
}
if ((sKotakePtr->workf[UNK_F11] > 10.0f) && (sKotakePtr->workf[UNK_F11] < 200.0f)) {
eff->frame = 100;
}
if (!(globalCtx->gameplayFrames & 1)) {
globalCtx->damagePlayer(globalCtx, -1);
}
} else {
Math_ApproachF(&eff->workf[EFF_SCALE], 0.042f, 1.0f, 0.002f);
}
if ((eff->workf[EFF_DIST] > 0.4f) && ((eff->frame & 7) == 0)) {
spA6 = Rand_ZeroFloat(17.9f);
if (eff->target == NULL) {
spC0.x = player->bodyPartsPos[spA6].x + Rand_CenteredFloat(5.0f);
spC0.y = player->bodyPartsPos[spA6].y + Rand_CenteredFloat(5.0f);
spC0.z = player->bodyPartsPos[spA6].z + Rand_CenteredFloat(5.0f);
phi_f22 = 10.0f;
} else {
unk44 = eff->target;
spC0.x = unk44->world.pos.x + Rand_CenteredFloat(40.0f);
spC0.y = unk44->world.pos.y + Rand_CenteredFloat(40.0f);
spC0.z = unk44->world.pos.z + Rand_CenteredFloat(40.0f);
phi_f22 = 20.0f;
}
spB4.x = 0.0f;
spB4.y = 0.0f;
spB4.z = 0.0f;
spA8.x = 0.0f;
spA8.y = 0.1f;
spA8.z = 0.0f;
BossTw_AddDmgCloud(globalCtx, 3, &spC0, &spB4, &spA8, phi_f22 + Rand_ZeroFloat(phi_f22 * 0.5f),
0, 0, 150);
}
}
}
}
eff++;
}
}
static s32 sRandSeed0;
static s32 sRandSeed1;
static s32 sRandSeed2;
void BossTw_InitRand(s32 seed0, s32 seed1, s32 seed2) {
sRandSeed0 = seed0;
sRandSeed1 = seed1;
sRandSeed2 = seed2;
}
f32 BossTw_RandZeroOne(void) {
f32 rand;
// Wichmann-Hill algorithm
sRandSeed0 = (sRandSeed0 * 171) % 30269;
sRandSeed1 = (sRandSeed1 * 172) % 30307;
sRandSeed2 = (sRandSeed2 * 170) % 30323;
rand = (sRandSeed0 / 30269.0f) + (sRandSeed1 / 30307.0f) + (sRandSeed2 / 30323.0f);
while (rand >= 1.0f) {
rand -= 1.0f;
}
return fabsf(rand);
}
void BossTw_DrawEffects(GlobalContext* globalCtx) {
u8 sp18F = 0;
s16 i;
s16 j;
s32 pad;
Player* player = GET_PLAYER(globalCtx);
s16 phi_s4;
BossTwEffect* currentEffect = globalCtx->specialEffects;
BossTwEffect* effectHead;
GraphicsContext* gfxCtx = globalCtx->state.gfxCtx;
effectHead = currentEffect;
OPEN_DISPS(gfxCtx);
func_80093D84(globalCtx->state.gfxCtx);
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (currentEffect->type == 1) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (sp18F == 0) {
gSPDisplayList(POLY_XLU_DISP++, object_tw_DL_01A528);
sp18F++;
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, currentEffect->color.r, currentEffect->color.g,
currentEffect->color.b, currentEffect->alpha);
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE], 1.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, object_tw_DL_01A5A8);
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
sp18F = 0;
currentEffect = effectHead;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (currentEffect->type == 3) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (sp18F == 0) {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A998));
sp18F++;
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, currentEffect->alpha);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (currentEffect->frame * 3) & 0x7F,
(currentEffect->frame * 15) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE], 1.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AB00));
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
sp18F = 0;
currentEffect = effectHead;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (currentEffect->type == 2) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (sp18F == 0) {
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 215, 255, 128);
sp18F++;
}
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 20, 0, currentEffect->alpha);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (currentEffect->frame * 3) & 0x7F,
(currentEffect->frame * 15) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE], 1.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A430));
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
sp18F = 0;
currentEffect = effectHead;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (currentEffect->type == 4) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (sp18F == 0) {
sp18F++;
}
gSPSegment(POLY_XLU_DISP++, 0xD,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, currentEffect->frame & 0x7F,
(currentEffect->frame * 8) & 0xFF, 0x20, 0x40, 1,
(currentEffect->frame * -2) & 0x7F, 0, 0x10, 0x10));
if (currentEffect->work[EFF_ARGS] == 1) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 65, 0, currentEffect->alpha);
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 255, 0, 128);
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, currentEffect->alpha);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 255, 255, 128);
}
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
if (currentEffect->work[EFF_UNKS1] == 0) {
Matrix_Translate(0.0f, 0.0f, 60.0f, MTXMODE_APPLY);
} else {
Matrix_Translate(0.0f, 0.0f, 0.0f, MTXMODE_APPLY);
}
Matrix_RotateZ(currentEffect->workf[EFF_ROLL], MTXMODE_APPLY);
Matrix_RotateX(M_PI / 2.0f, MTXMODE_APPLY);
Matrix_Scale(currentEffect->workf[EFF_SCALE], 1.0f, currentEffect->workf[EFF_SCALE], MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gDPSetRenderMode(POLY_XLU_DISP++, G_RM_PASS, G_RM_AA_ZB_XLU_SURF2);
gSPClearGeometryMode(POLY_XLU_DISP++, G_CULL_BACK | G_FOG);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A790));
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
sp18F = 0;
currentEffect = effectHead;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
Actor* actor;
Vec3f off;
if (currentEffect->type == TWEFF_PLYR_FRZ) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (sp18F == 0) {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AA50));
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, 255);
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, 0, 0, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
sp18F++;
BossTw_InitRand(1, 0x71AC, 0x263A);
}
actor = currentEffect->target;
phi_s4 = actor == NULL ? 70 : 20;
for (j = 0; j < phi_s4; j++) {
off.x = (BossTw_RandZeroOne() - 0.5f) * 30.0f;
off.y = currentEffect->workf[EFF_DIST] * j;
off.z = (BossTw_RandZeroOne() - 0.5f) * 30.0f;
if (actor != NULL) {
Matrix_Translate(actor->world.pos.x + off.x, actor->world.pos.y + off.y, actor->world.pos.z + off.z,
MTXMODE_NEW);
} else {
Matrix_Translate(player->actor.world.pos.x + off.x, player->actor.world.pos.y + off.y,
player->actor.world.pos.z + off.z, MTXMODE_NEW);
}
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE],
currentEffect->workf[EFF_SCALE], MTXMODE_APPLY);
Matrix_RotateY(BossTw_RandZeroOne() * M_PI, MTXMODE_APPLY);
Matrix_RotateX((BossTw_RandZeroOne() - 0.5f) * M_PI * 0.5f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AB00));
}
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
sp18F = 0;
currentEffect = effectHead;
for (i = 0; i < ARRAY_COUNT(sTwEffects); i++) {
if (currentEffect->type >= 6) {
FrameInterpolation_RecordOpenChild(currentEffect, currentEffect->epoch);
if (currentEffect->work[EFF_ARGS] == 0) {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 195, 225, 235, currentEffect->alpha);
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A998));
} else {
gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 200, 20, 0, currentEffect->alpha);
gDPPipeSync(POLY_XLU_DISP++);
gDPSetEnvColor(POLY_XLU_DISP++, 255, 215, 255, 128);
}
gSPSegment(POLY_XLU_DISP++, 8,
Gfx_TwoTexScroll(globalCtx->state.gfxCtx, 0, (currentEffect->frame * 3) & 0x7F,
(-currentEffect->frame * 15) & 0xFF, 0x20, 0x40, 1, 0, 0, 0x20, 0x20));
Matrix_Translate(currentEffect->pos.x, currentEffect->pos.y, currentEffect->pos.z, MTXMODE_NEW);
Matrix_ReplaceRotation(&globalCtx->billboardMtxF);
Matrix_Scale(currentEffect->workf[EFF_SCALE], currentEffect->workf[EFF_SCALE], 1.0f, MTXMODE_APPLY);
gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
if (currentEffect->work[EFF_ARGS] == 0) {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01AB00));
} else {
gSPDisplayList(POLY_XLU_DISP++, SEGMENTED_TO_VIRTUAL(object_tw_DL_01A430));
}
FrameInterpolation_RecordCloseChild();
}
currentEffect++;
}
CLOSE_DISPS(gfxCtx);
}
void BossTw_TwinrovaSetupArriveAtTarget(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaArriveAtTarget;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, -3.0f);
this->work[CS_TIMER_1] = Rand_ZeroFloat(100.0f);
this->timers[1] = 25;
this->rotateSpeed = 0.0f;
}
void BossTw_TwinrovaArriveAtTarget(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->actor.world.pos.x, this->targetPos.x, 0.1f, fabsf(this->actor.velocity.x) * 1.5f);
Math_ApproachF(&this->actor.world.pos.y, this->targetPos.y, 0.1f, fabsf(this->actor.velocity.y) * 1.5f);
Math_ApproachF(&this->targetPos.y, 380.0f, 1.0f, 2.0f);
Math_ApproachF(&this->actor.world.pos.z, this->targetPos.z, 0.1f, fabsf(this->actor.velocity.z) * 1.5f);
if (this->timers[1] == 1) {
BossTw_TwinrovaSetupChargeBlast(this, globalCtx);
}
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, this->rotateSpeed);
Math_ApproachF(&this->rotateSpeed, 4096.0f, 1.0f, 350.0f);
}
void BossTw_TwinrovaSetupChargeBlast(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaChargeBlast;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_036FBC, -5.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_036FBC);
this->csState1 = 0;
}
void BossTw_TwinrovaChargeBlast(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->actor.world.pos.x, this->targetPos.x, 0.03f, fabsf(this->actor.velocity.x) * 1.5f);
Math_ApproachF(&this->actor.world.pos.y, this->targetPos.y, 0.03f, fabsf(this->actor.velocity.y) * 1.5f);
Math_ApproachF(&this->actor.world.pos.z, this->targetPos.z, 0.03f, fabsf(this->actor.velocity.z) * 1.5f);
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, 0x1000);
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
if ((s8)this->actor.colChkInfo.health < 10) {
sTwinrovaBlastType = Rand_ZeroFloat(1.99f);
} else {
if (++sFixedBlatSeq >= 4) {
sFixedBlatSeq = 1;
sFixedBlastType = !sFixedBlastType;
}
sTwinrovaBlastType = sFixedBlastType;
}
BossTw_TwinrovaSetupShootBlast(this, globalCtx);
}
}
void BossTw_TwinrovaSetupShootBlast(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaShootBlast;
if (sTwinrovaBlastType == 0) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_022700, 0.0f);
} else {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_023750, 0.0f);
}
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_023750);
}
void BossTw_TwinrovaShootBlast(BossTw* this, GlobalContext* globalCtx) {
BossTw* twMagic;
Vec3f* magicSpawnPos;
s32 magicParams;
s16 i;
SkelAnime_Update(&this->skelAnime);
if (Animation_OnFrame(&this->skelAnime, 8.0f)) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_THROW_MASIC);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_SHOOTVC);
}
if (Animation_OnFrame(&this->skelAnime, 12.0f)) {
if (sTwinrovaBlastType != 0) {
magicParams = TW_FIRE_BLAST;
magicSpawnPos = &this->rightScepterPos;
} else {
magicParams = TW_ICE_BLAST;
magicSpawnPos = &this->leftScepterPos;
}
twMagic =
(BossTw*)Actor_SpawnAsChild(&globalCtx->actorCtx, &this->actor, globalCtx, ACTOR_BOSS_TW, magicSpawnPos->x,
magicSpawnPos->y, magicSpawnPos->z, 0, 0, 0, magicParams);
if (twMagic != NULL) {
twMagic->blastType = magicParams == TW_ICE_BLAST ? 0 : 1;
}
sEnvType = twMagic->blastType + 1;
{
Vec3f velocity = { 0.0f, 0.0f, 0.0f };
Vec3f accel = { 0.0f, 0.0f, 0.0f };
for (i = 0; i < 100; i++) {
velocity.x = Rand_CenteredFloat(30.0f);
velocity.y = Rand_CenteredFloat(30.0f);
velocity.z = Rand_CenteredFloat(30.0f);
BossTw_AddDotEffect(globalCtx, magicSpawnPos, &velocity, &accel, (s16)Rand_ZeroFloat(2.0f) + 11,
twMagic->blastType, 75);
}
}
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
BossTw_TwinrovaSetupDoneBlastShoot(this, globalCtx);
}
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, 0x1000);
}
void BossTw_TwinrovaSetupDoneBlastShoot(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaDoneBlastShoot;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, -10.0f);
this->timers[1] = 60;
}
void BossTw_TwinrovaDoneBlastShoot(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
if (this->timers[1] == 0 && D_8094C870 == 0) {
if (sTwinrovaPtr->timers[2] == 0) {
BossTw_TwinrovaSetupFly(this, globalCtx);
} else {
BossTw_TwinrovaSetupLaugh(this, globalCtx);
}
}
D_8094C870 = 0;
Math_ApproachS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 5, 0x1000);
}
void BossTw_TwinrovaDamage(BossTw* this, GlobalContext* globalCtx, u8 damage) {
if (this->actionFunc != BossTw_TwinrovaStun) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_0338F0, -15.0f);
this->timers[0] = 150;
this->timers[1] = 20;
this->csState1 = 0;
this->actor.velocity.y = 0.0f;
} else {
this->work[FOG_TIMER] = 10;
this->work[INVINC_TIMER] = 20;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_024374, -3.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_024374);
this->csState1 = 1;
if ((s8)(this->actor.colChkInfo.health -= damage) < 0) {
this->actor.colChkInfo.health = 0;
}
if ((s8)this->actor.colChkInfo.health <= 0) {
BossTw_TwinrovaSetupDeathCS(this, globalCtx);
Enemy_StartFinishingBlow(globalCtx, &this->actor);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DEAD);
return;
}
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DAMAGE2);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_CUTBODY);
}
this->actionFunc = BossTw_TwinrovaStun;
}
void BossTw_TwinrovaStun(BossTw* this, GlobalContext* globalCtx) {
s16 cloudType;
this->unk_5F8 = 1;
this->actor.flags |= ACTOR_FLAG_10;
cloudType = sTwinrovaBlastType == 0 ? 3 : 2;
if ((this->work[CS_TIMER_1] % 8) == 0) {
Vec3f pos;
Vec3f velocity;
Vec3f accel;
pos.x = this->actor.world.pos.x + Rand_CenteredFloat(20.0f);
pos.y = this->actor.world.pos.y + Rand_CenteredFloat(40.0f) + 20;
pos.z = this->actor.world.pos.z + Rand_CenteredFloat(20.0f);
velocity.x = 0.0f;
velocity.y = 0.0f;
velocity.z = 0.0f;
accel.x = 0.0f;
accel.y = 0.1f;
accel.z = 0.0f;
BossTw_AddDmgCloud(globalCtx, cloudType, &pos, &velocity, &accel, Rand_ZeroFloat(5.0f) + 10.0f, 0, 0, 150);
}
SkelAnime_Update(&this->skelAnime);
this->work[UNK_S8] += 20;
if (this->work[UNK_S8] > 255) {
this->work[UNK_S8] = 255;
}
Math_ApproachF(&this->workf[UNK_F12], 0.0f, 1.0f, 0.05f);
this->actor.world.pos.y += this->actor.velocity.y;
Math_ApproachF(&this->actor.velocity.y, -5.0f, 1.0f, 0.5f);
this->actor.world.pos.y -= 30.0f;
Actor_UpdateBgCheckInfo(globalCtx, &this->actor, 50.0f, 50.0f, 100.0f, 4);
this->actor.world.pos.y += 30.0f;
if (this->csState1 == 0) {
if (this->timers[1] == 0) {
this->csState1 = 1;
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_0343B4);
Animation_Change(&this->skelAnime, &object_tw_Anim_0343B4, 1.0f, 0.0f, this->workf[ANIM_SW_TGT], 3, 0.0f);
}
} else if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
this->workf[ANIM_SW_TGT] = 1000.0f;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_035030, 0.0f);
}
if (this->actor.bgCheckFlags & 1) {
this->actor.velocity.y = 0.0f;
}
if (this->timers[0] == 0) {
BossTw_TwinrovaSetupGetUp(this, globalCtx);
}
}
void BossTw_TwinrovaSetupGetUp(BossTw* this, GlobalContext* globalCtx) {
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_035988, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_035988);
this->actionFunc = BossTw_TwinrovaGetUp;
this->timers[0] = 50;
}
void BossTw_TwinrovaGetUp(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
Math_ApproachF(&this->actor.world.pos.y, this->targetPos.y, 0.05f, 5.0f);
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
this->workf[ANIM_SW_TGT] = 1000.0f;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, 0.0f);
}
if (this->timers[0] == 0) {
BossTw_TwinrovaSetupFly(this, globalCtx);
}
}
void BossTw_TwinrovaSetupFly(BossTw* this, GlobalContext* globalCtx) {
f32 xDiff;
f32 zDiff;
f32 yDiff;
f32 xzDist;
Player* player = GET_PLAYER(globalCtx);
do {
this->work[TW_PLLR_IDX] += (s16)(((s16)Rand_ZeroFloat(2.99f)) + 1);
this->work[TW_PLLR_IDX] %= 4;
this->targetPos = sTwinrovaPillarPos[this->work[TW_PLLR_IDX]];
xDiff = this->targetPos.x - player->actor.world.pos.x;
zDiff = this->targetPos.z - player->actor.world.pos.z;
xzDist = SQ(xDiff) + SQ(zDiff);
} while (!(xzDist > SQ(300.0f)));
this->targetPos.y = 480.0f;
xDiff = this->targetPos.x - this->actor.world.pos.x;
yDiff = this->targetPos.y - this->actor.world.pos.y;
zDiff = this->targetPos.z - this->actor.world.pos.z;
this->actionFunc = BossTw_TwinrovaFly;
this->rotateSpeed = 0.0f;
this->actor.speedXZ = 0.0f;
this->actor.world.rot.y = Math_FAtan2F(xDiff, zDiff) * (32768 / M_PI);
xzDist = sqrtf(SQ(xDiff) + SQ(zDiff));
this->actor.world.rot.x = Math_FAtan2F(yDiff, xzDist) * (32768 / M_PI);
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, -10.0f);
}
void BossTw_TwinrovaFly(BossTw* this, GlobalContext* globalCtx) {
f32 xDiff;
f32 yDiff;
f32 zDiff;
s32 pad;
f32 yaw;
f32 xzDist;
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_FLY - SFX_FLAG);
SkelAnime_Update(&this->skelAnime);
xDiff = this->targetPos.x - this->actor.world.pos.x;
yDiff = this->targetPos.y - this->actor.world.pos.y;
zDiff = this->targetPos.z - this->actor.world.pos.z;
// Convert from radians to degrees, then degrees to binary angle
yaw = (s16)(Math_FAtan2F(xDiff, zDiff) * ((180.0f / M_PI) * (65536.0f / 360.0f)));
xzDist = sqrtf(SQ(xDiff) + SQ(zDiff));
Math_ApproachS(&this->actor.world.rot.x,
(f32)(s16)(Math_FAtan2F(yDiff, xzDist) * ((180.0f / M_PI) * (65536.0f / 360.0f))), 0xA,
this->rotateSpeed);
Math_ApproachS(&this->actor.world.rot.y, yaw, 0xA, this->rotateSpeed);
Math_ApproachS(&this->actor.shape.rot.y, yaw, 0xA, this->rotateSpeed);
Math_ApproachF(&this->rotateSpeed, 2000.0f, 1.0f, 100.0f);
Math_ApproachF(&this->actor.speedXZ, 30.0f, 1.0f, 2.0f);
func_8002D908(&this->actor);
Math_ApproachF(&this->actor.world.pos.x, this->targetPos.x, 0.1f, fabsf(this->actor.velocity.x) * 1.5f);
Math_ApproachF(&this->actor.world.pos.y, this->targetPos.y, 0.1f, fabsf(this->actor.velocity.y) * 1.5f);
Math_ApproachF(&this->targetPos.y, 380.0f, 1.0f, 2.0f);
Math_ApproachF(&this->actor.world.pos.z, this->targetPos.z, 0.1f, fabsf(this->actor.velocity.z) * 1.5f);
if (xzDist < 200.0f) {
BossTw_TwinrovaSetupArriveAtTarget(this, globalCtx);
}
}
void BossTw_TwinrovaSetupSpin(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaSpin;
Animation_MorphToLoop(&this->skelAnime, &object_tw_Anim_032BF8, 0.0f);
this->timers[0] = 20;
this->actor.speedXZ = 0.0f;
}
void BossTw_TwinrovaSpin(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
if (this->timers[0] != 0) {
this->collider.base.colType = COLTYPE_METAL;
this->actor.shape.rot.y -= 0x3000;
if ((this->timers[0] % 4) == 0) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_ROLL);
}
} else {
BossTw_TwinrovaSetupFly(this, globalCtx);
}
}
void BossTw_TwinrovaSetupLaugh(BossTw* this, GlobalContext* globalCtx) {
this->actionFunc = BossTw_TwinrovaLaugh;
Animation_MorphToPlayOnce(&this->skelAnime, &object_tw_Anim_03A2D0, 0.0f);
this->workf[ANIM_SW_TGT] = Animation_GetLastFrame(&object_tw_Anim_03A2D0);
this->actor.speedXZ = 0.0f;
}
void BossTw_TwinrovaLaugh(BossTw* this, GlobalContext* globalCtx) {
SkelAnime_Update(&this->skelAnime);
if (Animation_OnFrame(&this->skelAnime, 10.0f)) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_LAUGH);
}
if (Animation_OnFrame(&this->skelAnime, this->workf[ANIM_SW_TGT])) {
BossTw_TwinrovaSetupFly(this, globalCtx);
}
}
void BossTw_Reset(void) {
sTwInitalized = false;
memset(sTwEffects, 0, sizeof(sTwEffects));
}